Find the drift.
Fix it in place.
Select anything on the canvas and find out, layer by layer, what is using your design system and what quietly is not, then fix it without leaving the panel.
What it does
The plugin walks everything inside your selection, including every level of nested component, and asks four questions of each layer:
Is every fill and stroke a token from a library you listed, including each gradient stop?
Are corner radii, gaps and padding bound to numeric tokens?
Is the text on a library text style, at the right size and line height?
Is the instance current, from a listed library, and still published?
Everything it finds is grouped, counted, scored, and, where possible, fixable in one click.
What makes it different
It reads the deepest nested component
Most audits stop at the top layer. This one descends through instances inside instances inside instances, verified to sixty levels, and knows which component each layer actually came from, so a color inherited from a library component is labelled as such instead of being blamed on you.
It tells you where a value came from
Under every row is the library the value belongs to. Hover it and it says whether the value was changed on this instance or comes straight from the component. Both count, but they mean different things when you decide what to do.
Gradients are judged stop by stop
A gradient is not one color. Each stop is checked on its own and tagged
Grad 1, Grad 2, so one bound stop cannot vouch for the
rest. Applying a paint style replaces the whole fill and answers every stop at
once; binding a variable touches only the stop carrying that color.
Text colored run by run is handled properly
A text layer with three colors in it is reported once per color, with the run quoted on the right. Applying a token binds only the runs carrying that color and leaves the others alone.
The picker only offers what fits
A corner radius is offered radius tokens. A gap is offered spacing tokens. Radius, gap, padding and font size are all plain numbers to Figma, so without this filter a 10 px font-size token wins a 10 px corner on raw distance.
Install and first run
- Find it in the Community. Search Design System Scanner in Figma Community, or open Resources Plugins from the toolbar and search there. Press Run.
- Afterwards, from any file. Right-click the canvas Plugins, and it will be in your recently used list.
- Check the Include list. On first run every library enabled on the file is already listed with all four checks ticked. Untick anything that is not a valid source, and remove what you do not use.
- Add a Figma token if you want components and styles traced back to the library they came from. Optional. See The Figma token.
- Select a frame and press Scan.
Where your setup lives
Include, Exclude and excluded items are stored with the file, so a teammate opening it sees the same setup and the same score. Your Figma token is the exception: it lives on your machine only and never travels with the file or with a preset.
Scan Settings
Two tabs. Include says what counts as on-system. Exclude says what is not being asked the question at all.
The list of libraries that count as on-system. A token from a library that is not on this list is flagged, even though it is a perfectly real token. That is the point of the list.
Each row has four checks. Untick Color on a library that is only an icon set, and its colors stop counting as a valid source while its components still do.
[BETA] Core Library and Core Library are two different libraries, and ticking one does not approve the other. Exclude rules are the opposite. Those match on part of a name on purpose.local (this file) means variables and components made in this file
rather than in a library. Tick it if that is legitimate for your work; leave it
off and everything local gets flagged.
local. Everything in a library file is local to it, so without this you would have to list local to scan the library you are standing in. The match is on the file name, which is what a library's name is.Anything matching a rule passes, whatever else is true of it. Two lists, because the two kinds of rule are not the same shape.
Library
Pick a library, then pick which checks the exclusion covers. An illustration library can be excluded from Color, Dimension and Typography while its components still count. Leave all four ticked and it covers everything.
Frame, component, color or text style
These match text you type against a layer.
| Rule | Matches on | Use it for |
|---|---|---|
| Inside Frame/Section | Part of a layer name, and everything inside it | Annotation frames, spec sheets, scratch areas |
| Component | Part of a component name, on the layer or any ancestor | One component you know is an exception |
| Color | Hex codes, comma separated | A handful of approved one-offs |
| Typography | Part of a text style name | A legacy style being phased out |
Rules carry no reason field. A rule is two or three words that already say what it does, so the Score tab and the copied result list each rule with the checks it covers instead, which is the part you cannot read off the rule itself.
Every rule can carry an except pages list, so a rule can be switched off on the one page where it should not apply.
The save icon in the footer copies your whole setup, Include, Exclude, tolerance, to the clipboard as a preset. Paste it into a teammate's Paste Preset field and their settings match yours.
The field applies the preset the moment you paste and clears itself, so the JSON is never left sitting on screen.
The Figma token
Only needed so components and styles can be traced to the library they came from: the plugin API does not expose that, so it is fetched over Figma's REST API instead.
The simplest scope that works is files:read. It
covers every call the plugin makes. If you would rather scope it narrowly you
need library_assets:read, file_metadata:read and
library_content:read together.
Without a token the plugin still works. It simply cannot name libraries, so anything it cannot trace is counted as on-system rather than guessed at, and the warning line tells you how many.
Running a scan
Select something and press Scan. The footer reports progress as it reads variables, styles and layers. The result opens on Score.
| The button reads | Meaning |
|---|---|
| Scan | Nothing to apply on this tab. Press it to scan again |
| Apply N | N layers on this tab can be fixed at once |
| Update N | Same, on the Component tab |
| Scanning… | In progress. A Stop appears beside it |
Stopping
A scan costs one network lookup per distinct component, so a large selection can run for a while and you should not have to wait it out. Press Stop and the scan unwinds at its next checkpoint: the status line says it stopped, no error is shown, and whatever result was already on screen is left exactly as it was. Nothing has been written to the file either way, because a scan only reads.
Reading the result
Five tabs. Component comes first because a stale or missing component is a bigger problem than one raw color.
One percentage: compliant layers over checked layers. Underneath, the same figure per check, then exactly what the scan ran with: what you included, what you excluded, and every excluded item you settled by hand. Each excluded item takes a reason and − puts it back.
Copy result puts the whole thing on the clipboard as a markdown table, ready to paste into a ticket. Add to canvas draws it as a frame next to your work.
| Group | Means | You can |
|---|---|---|
| Update available | The instance is on an older copy than the library publishes | Update it, one row or the whole tab |
| Detached component | A frame whose name matches a published component | Reattach it yourself, or exclude |
| From other library | An instance from a library you have not listed | Replace it, or add that library |
| Defined in this file | A local component, or one copied out of a library | Publish it, tick local, or relink it |
| No longer published | Figma has no record of the main component | Nothing here. See below |
About “copied from”
Copying a component out of a library and pasting it into your file makes a
new local component. Figma keeps no key, no library, nothing to
trace: the link is gone, and updates to the library will never reach it. The
name is the only thing that survives the paste, so a local component sharing a
published component's name reads copied from X, not linked to it, and
is reported whether or not local is ticked.
local means what you built here, not what you took out of the
library and cut loose from it.
About “No longer published”
The component was either deleted from its library or unpublished on purpose,
and no API tells the two apart. Figma gives variables a
deletedButReferenced flag; components get nothing equivalent. So this
group is listed for you to look at and left out of the score entirely
the plugin will not mark you down for something it cannot judge. It is the only
group with no action on its rows.
Each row: the value on the left, a token picker on the right, then exclude and apply. Rows are grouped by surface: Frame › Fill, Shape › Stroke, Text › Fill.
A gradient stop carries a Grad 1 tag. Hover it for the full
wording. Click any row to select those layers on the canvas.
Padding shows one row when a pair agrees (Horizontal padding 16) and two when it does not (Left padding 12, Right padding 16). A row never shows a merged value that is not real.
Every typography row reads 15px/22: the two numbers you check
first. A line height Figma stores as AUTO reads auto; one that varies
within the layer reads mixed.
Icons and illustrations
An icon is a pile of vectors, and every one of them is its own finding. Reported flat, one drawing reads as forty problems. So on the Color and Dimension tabs, findings inside a drawing leave the flat list and gather under the drawing they belong to.
The heading says which of the two it is, then the layer's name. Click the name to select that frame or instance on canvas and zoom to it: the rows underneath select the scattered vectors, the heading selects the one layer they all sit in.
From a vector the plugin climbs to the first frame or component with a name of its own, and stops at any nested component whatever it is called.
It refuses to climb into anything holding text, so a button with an icon inside stays a button rather than becoming a drawing.
With nothing properly named on the way up, the outermost all-vector frame is used, so Frame 627126 reports as one artwork rather than forty vectors.
Size alone decides: 60px or under on both sides is an Icon, larger on either side is an Illustration.
Excluding a whole drawing
Exclude on the heading settles everything inside it at once, on both tabs. It is recorded exactly like any other exclusion: it appears under Excluded Item on the Score tab with a reason field, it counts as on-system rather than vanishing from the total, it survives a rescan, and − on the Score tab puts it back.
Fixing things
| Control | Does | Effect on the score |
|---|---|---|
| Apply | Binds the chosen token on those layers | They move from failing to passing |
| Update | Swaps the instance to the published version | Passes, then the tab re-scans |
| Exclude | Records the finding as accepted, with a reason | Counts as on-system from now on |
| Remove in picker | Strips the value instead of tokenising it | Passes. There is nothing left to judge |
Remove, and when to reach for it
At the bottom of every color and dimension picker is Remove the fill / Remove the stroke / Remove (set to 0). It is offered even when no token was close enough to suggest, which is usually exactly when you want it. A stray stroke nobody meant to add does not need a token; it needs deleting.
Updating a component re-scans
The new version of a component brings its own colors, dimensions and type. So when the last update in a batch lands, the plugin scans the same layers again and the other tabs catch up: rows the new version fixed disappear, rows it broke appear, the rest stay.
Excluding a row
Excluding a finding is a judgement you are recording, so it is written down: the row is listed under Excluded Item on the Score tab with a reason field, it counts as on-system, and it stays excluded across scans until you press to put it back. The reason travels with the copied result.
Fixing outside the plugin
You do not have to use the picker. Fix a layer with Figma's own right-hand panel, apply a library variable or style, and the plugin re-checks that one layer and clears its row, with a line saying how many were fixed on canvas.
Always on
Rules that need no setting and cannot be turned off:
- Hidden layers are skipped, with everything inside them
- Sections are skipped: a section fill is canvas labelling, not design
- Stroke weight is never checked
- Corner radius is judged per corner; mixed corners are expected
- Mixed fonts or sizes in one layer are a failure with no suggestion
- A value inherited from a library component still counts, but the row says so on hover
- Colors are ordered by name so a family stays together:
shape-danger-softbesideshape-danger-solid, andgrey-2beforegrey-10 - Dimensions and type run smallest value to largest inside each group
- A name too long for its row hands the whole thing over on hover
Troubleshooting
“N component(s) could not be read”
Figma would not say which component an instance came from. Everything inside it loses its library, so an Exclude Library rule cannot reach it. The warning names the components. Either give the token access to that library, reopen the file to reload it, or use an Exclude Component rule, which matches on name and needs no library.
A component I copied from a library is flagged
That is the point. Pasting a component out of a library makes a new local component with no link back to it, so library updates will never reach it. The row reads copied from X, not linked to it. Delete it and place a real instance, or exclude the row if the copy is deliberate.
I am editing the library file and everything is flagged
List that library in Include. If the open file's name matches
one of the libraries you listed, its own tokens and components are judged against
that library rather than against local. The match is on the file
name, because a library's name is its file name.
My Scan Settings went back to defaults
The revert icon in the footer does that. It restores everything: cached lookups, Include, Exclude and every excluded item, reasons included. There is no undo. Your Figma token is not affected.
“N item(s) could not be traced to a library”
Usually a missing or under-scoped token. Those items are counted as on-system rather than guessed at, so the score is not punished for the plugin's blind spot.
“N librar(ies) could not be named”
The token lacks file_metadata:read (or files:read).
Libraries show as raw file keys until it does.
A token I know exists is not in the picker
Check the Include tab: the library has to be listed and ticked for that check. Exact name match. If it is a text or color style rather than a variable, the plugin needs the token. Figma has no API to list a library's styles.
Figma still offers the update after I updated in the plugin
Expected. Figma's Library Updates panel tracks your file's
subscription to a library version; the plugin re-points individual instances. There
is no plugin API to accept a library update. Figma.teamLibrary
exposes variables and nothing else. Accepting in Figma afterwards is harmless.
Every pro tip in one place
- Ticking a check on
[BETA] Librarydoes not approveLibrary. Exact names. - A Component exclude rule is the only one that works when the library cannot be identified.
- A library exclusion can cover some checks: keep an illustration library's components, drop its colors.
- Choose a token… with a disabled arrow means nothing was close enough. That is information, not a bug.
- Remove is usually the right answer for a value that should never have been there.
- The revert icon restores everything to defaults, Scan Settings included. There is no undo.
- Stop during a scan is safe: nothing is written and the previous result stays on screen.
- Click an artwork heading to jump to that icon or illustration on canvas.
- One Exclude on a drawing settles every finding inside it, on both tabs.
- Click any row to select those layers on the canvas.
- Hover a truncated name, a library line, or a
Gradtag for the full text. - Presets carry your whole setup and never your token.
- The denominator never moves, so two scans of the same selection are comparable.