Experiment - Figma Plugin

Design Cop

Building a plugin to police my own design files - auditing unlinked colours and text styles so I didn’t have to.

2024

I was deep in a Figma file for Qween Design System 2.0 when I realised I’d been doing the same thing for the third time that week: hunting down a colour, opening the variables panel, scrolling through tokens, finding the right one, linking it, moving on. Then doing it again. And again.

The colours and text styles in my designs weren’t connected to the DS. Not because I didn’t care because I was working fast, pasting in components, tweaking shades, and the connections were getting lost along the way. Manually auditing every node was tedious. Missing a few was guaranteed.

So I built a plugin to do it for me. It’s called Design Cop.

What It Does

Design Cop scans your Figma file - current selection or full page - and finds every colour and text style that isn’t linked to your Design System. For each issue, it suggests the nearest matching DS token and lets you link it in one click. Where it finds exact matches, it can batch-link the whole lot at once.

Audit, suggest and auto-link unlinked colours and text to your DS.

That’s the elevator version. The interesting part is everything that had to go right underneath it.

The Performance Problem

The first version of Design Cop took 30+ seconds to scan a file. That’s not a plugin, that’s a punishment.

It was doing three things wrong. It scanned every page in the file, even ones I didn’t care about. It walked the node tree recursively, which meant blowing the stack on large files. And worst of all, it made an individual async API call to resolve every single style reference, sometimes 3,000+ calls for a moderately complex file.

The fix was three changes stacked together: stop scanning pages entirely and only look at what’s selected or on the current page; deduplicate style IDs before resolving them, so 3,262 calls collapsed to ~30 unique ones; and replace the recursive walker with an iterative stack-based one. The result was near-instant on most files. The moment it dropped from “go make coffee” to “blink and miss it” was easily the most satisfying part of building this.

Making It Work for Any Design System

The first version was hardcoded to Qween DS. Every token name, every collection, baked in. It worked, but only for one team.

I rewrote it to be generic. Instead of hardcoding tokens, the plugin dynamically discovers all connected library variable collections in the file at scan time, resolves each colour variable, and builds a live token map. Material, IBM Carbon, Atlassian, your custom enterprise DS, if it’s variable-based or uses paint styles, Design Cop works with it.

The Alias Chain

Figma variables aren’t usually simple colour values. They’re aliases that point to other aliases that eventually point to a real colour. Qween DS has a three-level chain

Component token → Semantic token → Primitive → actual hex value

The plugin needs to walk this entire chain to know what colour a token actually represents. Easy in theory. Less easy when a sync version of a Figma API silently fails because dynamic-page plugins require the async variant and you don’t get an error, you just get nothing back.

Finding that bug was the kind of debugging where you stare at the code, then at the file, then at the docs, then back at the code, until you finally console.log the right thing and the whole picture snaps into focus. None of it was in the documentation in any obvious way. I had to run code against a live file to understand what Figma was actually doing under the hood.

Showing the Right Tokens

Even after fixing alias resolution, the suggestions were wrong in a different way. The plugin was confidently recommending things like Primitives/Secondary/Brown/300 - colours that exist in the system but aren’t meant to be used directly.

I added a filter: the plugin peeks at variable names in each collection. If they use role-based semantic naming (colour/text/primary), they’re included. Raw primitive collections get skipped automatically.

Matching Colours Properly

Colour matching uses perceptual distance in LAB colour space, Delta-E-not simple hex comparison. Two colours that look identical to your eye can have very different RGB values, and two colours with similar RGB can look noticeably different. LAB is closer to how humans actually perceive colour, so the rankings make more sense.

Matches are tiered: exact, close, near. And critically, when multiple tokens tie for the closest match, the plugin doesn’t guess. It shows “Multiple DS colours match” and asks the designer to choose. Confidently picking the wrong one would erode trust faster than not picking at all.

The Dead Button Lesson

An early version had a “Link All Styles” button at the top of the issues panel. It showed up regardless of whether there were actually any exact matches to link. Most of the time, clicking it did nothing.

A dead button isn’t a small UX issue. It teaches users the plugin is unreliable, and once they learn that, they stop trusting any of its buttons. The fix was small: the button only appears when there’s at least one exact match. Hiding is better than disabling, and disabling is better than no-op.

Honesty About Constraints

Some nodes inside component instances can’t be edited directly, Figma simply doesn’t allow it. Early on, the plugin would try, fail, and show “✗ Failed”, which made it look broken.

Now it detects these cases and shows “ In component” instead. Same outcome, but the user knows it’s a Figma constraint, not a Design Cop bug. Tools that are honest about their limits get more trust than tools that pretend everything is fine.

Reflections

The hardest part of building this wasn’t the UI or the matching logic. It was understanding how Figma’s variable system actually behaves at runtime. The alias chain, the sync vs async API gotcha, the distinction between primitive and semantic collections, none of it is obvious from reading the docs. You learn it by running code against a live file and watching things go wrong in specific ways.

The plugin is fully local. No backend, no network requests, no user data. Just a tool that walks your file, finds the gaps, and helps you close them.

Design Cop is on Figma Community now. Built originally for the Qween DS team, open to anyone whose Design System follows semantic naming conventions.

If you’ve ever rage-quit a Figma file at 11pm because half your colours are unlinked, this is for you.

View on Figma Community ↗