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 colour 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 colour. 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 colour.
Text coloured run by run is handled properly
A text layer with three colours in it is reported once per colour, with the run quoted on the right. Applying a token binds only the runs carrying that colour 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 Library 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
Libraries, ignore rules and dismissals 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.
Settings
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 Colour on a library that is only an icon set, and its colours 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. Ignore 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.
Anything matching a rule passes every check, whatever else is true of it.
| Rule | Matches on | Use it for |
|---|---|---|
| Library | Part of a library name | An illustration or marketing library that is not product UI |
| 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 |
| Colour | Hex codes, comma separated | A handful of approved one-offs |
| Typography | Part of a text style name | A legacy style being phased out |
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, libraries, ignore rules, 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; the quieter Scan beside it disappears while it runs |
Reading the result
Five tabs. Component comes first because a stale or missing component is a bigger problem than one raw colour.
One percentage: compliant layers over checked layers. Underneath, the same figure per check, then exactly what the scan ran with: the libraries, the ignore rules and anything dismissed.
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 dismiss |
| 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, where local is not a listed source | Publish it, or tick local |
| No longer published | Figma has no record of the main component | Nothing here. See below |
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 dismiss 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.
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 |
| Dismiss | 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 colour 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 colours, 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.
Dismissing
Dismissal is a judgement you are recording, so it is written down: the row is listed on the Score tab with a reason field, it counts as on-system, and it stays dismissed 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
- Colours 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 a Library ignore 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 a Component ignore rule, which matches on name and needs no library.
“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 Library tab: the library has to be listed and ticked for that check. Exact name match. If it is a text or colour 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 ignore rule is the only one that works when the library cannot be identified.
- 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 refresh icon clears cached library lookups after a library changes.
- 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.