How I Built a Production Figma Plugin Solo: Every Technical Decision
Building Aulys meant making hard tradeoffs between Figma's plugin API constraints, a 60fps performance budget, and WCAG accuracy. Here's every decision I made — and the ones I got wrong the first time.
In early 2022, I was doing accessibility audits the same way every designer does: manually. Tab through the page. Check color contrast in Stark. Export a list of violations. Hand it to the developer. Repeat the whole cycle two sprints later when half the fixes introduced new issues.
I kept thinking: this is automatable. Not the judgment calls — those still need a human. But the scanning, the annotation, the fix suggestions? That's pattern recognition. That's exactly what a plugin should do. So I built Aulys.
The Constraint That Shaped Everything: No Background Threads
Figma's plugin API runs your code in a sandboxed JavaScript environment on the main thread. There's no Web Worker access from the plugin sandbox. No background processing. Your plugin shares the same call stack as Figma's rendering engine.
This means if your plugin does heavy computation — say, traversing a deep component tree and running WCAG contrast calculations across every text node — you will block Figma's UI thread. The canvas freezes. Users notice. They close your plugin.
My first version of Aulys tried to scan the entire page in one pass. On a frame with 200+ nodes, it would lock Figma for 3–4 seconds. Unacceptable. The fix was chunked traversal: process nodes in batches of 50, yield control between batches using async/await and microtask scheduling. Scan time went from 3s blocking to 3s non-blocking — same duration, but Figma stayed responsive throughout.
The WCAG Accuracy Problem
WCAG 2.2 contrast calculation sounds simple: compute relative luminance of foreground and background, divide, check against the threshold (4.5:1 for normal text, 3:1 for large). The math is four lines of code. The hard part is everything around the math.
- Figma layers composite differently than browsers. A text node sitting over a semi-transparent fill over a gradient over a background image requires you to flatten those layers to get the real perceived contrast — Figma's API doesn't do this for you.
- Components and instances can override fills at multiple levels. A text node inside a nested instance inherits fills through up to 6 layers of composition. I had to walk the full parent chain to resolve the effective background color.
- Auto layout and constraints affect which fill is actually 'behind' a given text node. A text sitting in a card with padding is not visually on the frame background — it's on the card fill.
- Gradients don't have a single contrast value. WCAG says to use the worst-case point of contrast across the gradient for the specific text position. I sample 5 points across the gradient at the text's bounding box location.
Getting this right took three months of iteration. My V1 had a false positive rate of about 18% — it would flag text as failing that was actually passing, because it was calculating contrast against the wrong background layer. By V2, after the layer-flattening logic, that dropped to under 3%.
The Decision I Got Wrong First: Drag-to-Reorder Violations
My first version of the violations panel let you drag-to-reorder issues. I thought designers would want to prioritize — 'let me fix the critical ones first, move the warnings to the bottom.' It seemed like good UX.
In beta testing, nobody used it. Worse, three users specifically asked me to remove it because it made the list feel less authoritative — 'if I can rearrange them, does the order actually mean something? Is severity a real ranking or just my preference?'
The drag handle introduced ambiguity where there should have been clarity. WCAG violations have objective severity — Critical (fails SC), Warning (borderline), Info (best practice). Letting users reorder implied the order was subjective.
I removed drag-to-reorder in V2. The list is now sorted by severity, then by node tree order. Non-negotiable. Users can filter by severity level, but they can't reorder the ranking. Beta testers rated the violations list 23% more trustworthy after this change (measured via a 5-point Likert scale in exit surveys).
Plugin Panel UX: The 60fps Budget
Figma renders the plugin panel as an iframe. The panel itself is standard web tech — React, CSS, the whole stack. But users expect Figma-native feel: snappy, precise, no janky transitions.
I kept a strict 60fps budget for all interactions. Everything interactive is GPU-composited (transform and opacity only, no layout-triggering properties in animations). The scan progress bar uses a CSS animation rather than JavaScript-driven updates. Tab switching uses CSS class toggling with a 150ms transition — fast enough to feel instant, slow enough to not feel abrupt.
What I Would Do Differently
- Start with WCAG 2.1 AA only. I tried to support 2.2 AAA from day one, which tripled the rule surface area and slowed shipping by 6 weeks. AA coverage alone covers 97% of what teams actually need.
- Build the settings panel earlier. I added font-size threshold customization in V3 — it was a top-5 user request from month one. I delayed it because I thought 'designers should just follow the spec.' They can't always; type sizes are often a brand constraint.
- Charge from beta. Giving it away for free in beta trained users to expect it free. Transitioning to paid was harder than it needed to be and required re-explaining value to people who had stopped noticing it.
What It Taught Me About Product Design
Building Aulys solo — concept, design, engineering, go-to-market — compressed years of PM/engineering collaboration into a single experience. I now understand, viscerally, why engineers push back on certain design requests. Not because they're lazy, but because the implementation cost is real and sometimes the design assumption is wrong.
The best thing a product designer can do is understand constraints from the inside. You don't have to write production code. But you should understand threading models, API limitations, rendering performance, and state management well enough to ask the right questions. That understanding is the difference between a designer who hands off specs and a designer who ships products.
Aulys is in active beta
30+ designers are currently testing it. If you work on a product team and accessibility is part of your workflow, I'd love your feedback. The plugin is available at aulys-app.vercel.app.