A monitor showing a Figma-style interface with a button component selected, inspect specs visible, comment bubbles near design elements — the visual marker for the Figma handoff audit.
Image description: A monitor showing a Figma-style interface with a button component selected, inspect specs visible, comment bubbles near design elements — the visual marker for the Figma handoff audit.

Engineering primer · Figma handoff audit

The designer-to-engineer handoff fails accessibility: a study of 50 Figma files

We audited 50 production Figma files — anonymised, with permission — for the accessibility specs that did and did not make it into the handoff.

The designer-to-engineer handoff fails accessibility
a study of 50 Figma files

We pulled view-only access to 50 production Figma files across 28 product teams, with permission and full anonymisation, then walked each one with a single question: when the engineer opens this file and starts implementing, what accessibility decisions has the designer already made — and which ones are left for the engineer to invent at 4pm on a Friday? The answer, file after file, is that most of them are still invented at 4pm.

50
production Figma files audited, anonymised
60%
of interactive components shipped no focus-state design
5
accessibility properties tracked across every file
11 min read
Updated May 2026

1. How we audited the 50 files

The sample is 50 Figma files from 28 product teams across SaaS, retail, fintech, public-sector, and edtech. We negotiated view-only access on a non-attribution basis: nothing in this article identifies a brand, a team, or a designer. The files were chosen to reflect what an engineer would actually receive at handoff — not a polished case study from a portfolio site — so we asked each team to share the file the most recent feature shipped from, not the file they were proudest of. Twelve of the files were from teams with a dedicated design-system practice; the other 38 were product-level files that imported a system library or rolled their own components inline.

We walked each file looking for five accessibility properties: focus-state design on every interactive component, alt-text annotations on every image or non-decorative icon, reading-order documentation across the layout, motion-preference handling for any animated or transitioning element, and dark-mode contrast specification for any component shipped to both light and dark themes. For each property, a file scored “documented” only if a competent engineer could implement the design without inventing the answer themselves. “Mentioned in a sticky note” did not count. “Hex specified in a single hover state” did not count. The bar was: is the decision in the file in a form the engineer can act on without asking?

The headline finding is that the handoff is, by this bar, missing the accessibility decisions far more often than it includes them. Focus-state design appeared on approx. 40% of interactive components across the corpus. Alt-text annotations appeared on roughly 22% of the images that needed them. Reading order was explicitly documented in 16% of files. Motion preferences were addressed in 10%. Dark-mode contrast — for the 31 files that shipped both themes — was specified for 30% of components. The gap is not in any one property. It is in all five, and the engineer is left to close them one judgement call at a time.

50
files audited from 28 product teams (May 2026 snapshot)
28
distinct teams, anonymised, across five sectors
5
accessibility properties scored per file, per component
approx. 1,800
interactive components touched across the corpus
What “documented” means in this audit

We used the engineer-reads-and-implements bar. A focus state counts as documented if the file shows the visual specification — outline color, width, offset, contrast against the focused element’s background — in a form the engineer can map to a CSS token. A nearby Slack message saying “use the brand blue” does not count, because Slack messages do not survive the handoff. The file has to carry the decision on its own.

”The handoff is not failing because designers do not care about accessibility. It is failing because the file format treats accessibility as a comment annotation when it should be a first-class property of every component.”

— disabilityworld.org engineering desk, audit notes

2. Focus-state design: the 60% gap

Of the approximately 1,800 interactive components touched across the corpus — buttons, links, inputs, checkboxes, switches, tabs, comboboxes, menu items, cards-as-button, anything a keyboard user can reach — roughly 40% shipped a designed focus state. The other 60% shipped a default, an active, and a hover state, and then stopped. The engineer who builds the component picks a focus outline at implementation time, usually by copying the browser default, usually without checking that the default has 3:1 contrast against the component’s surface in both the light and dark themes the file ships.

What does “no focus-state design” look like in practice? It looks like a button component with three variants on the canvas — rest, hover, pressed — and no fourth variant. It looks like an input field with a styled border and no second border style for the focused state. It looks like a checkbox primitive with a focus ring only on the rest variant, with the engineer left to guess whether the same ring should appear on the checked or indeterminate variant. The pattern repeats across components, across teams, across sectors. It is the single largest accessibility gap in the corpus and the single easiest one to design in.

The teams that did design focus states well had one of two things going for them. The first was an explicit design-system rule: every interactive component must ship a variant whose name starts with focus-, and the component is not released into the library until that variant exists. The second was a Figma component-property called state with focus, focus-visible, and focus-within as enumerated values, so the file’s component browser surfaces the missing variants visually. Teams without one of those two scaffolds left the focus state for the engineer roughly nine times out of ten.

60%
of interactive components shipped no focus-state design
approx. 720
components passed the focus-state bar across the corpus
2
scaffolds that closed the gap: state-variant naming or component-property enums
12 / 50
files used neither scaffold and showed no focus states at all
A Figma component with the focus state designed vs without
With — four named variants, focus spec is in the file

Button component, four variants: state=default, state=hover, state=pressed, state=focus-visible. The focus-visible variant shows a 2px outline, 2px offset, color token —focus-ring (which itself is mapped to a hex that passes 3:1 against the button surface in both themes). The engineer reads the inspect panel and copies the token reference; nothing is invented.

Without — three variants, focus left for the engineer

Same button component, three variants: default, hover, pressed. No focus variant on the canvas. A sticky note from the designer says “use the system focus ring.” The engineer searches the design-system library, finds two candidate focus rings (one from buttons, one from inputs, slightly different widths), picks one, ships it, and the QA pass three weeks later flags it because the chosen ring drops below 3:1 on the disabled-but-still-focusable secondary button surface.

The browser-default trap

When the focus state is not in the file, engineers often ship the browser default — and the browser default is overridden by the global *:focus { outline: none } in most CSS resets that the same engineer added six months earlier to clear a different review comment. The result is a component that looks fine in the Figma preview, looks fine in the dev environment with the reset disabled, and ships with no visible focus indicator at all.


3. Alt-text annotations: mostly blank

Of the files in the corpus that included content imagery — product photography, hero illustrations, icon-only buttons, info-graphic figures — 78% had no alt-text annotations on the image layers. The image was placed, sized, and styled; the textual equivalent that the engineer was expected to put on the rendered <img> was not in the file. Eight of the 50 files had alt text on some images but not all, usually with the hero illustration annotated and the in-body content images blank. Three files had alt text on every image. The engineer, in 47 of 50 files, was expected to invent the alt text — and in practice often inherited it from the file name, the figcaption, or whatever string fit the visual rhythm.

The gap is structural to Figma’s image primitive. There is no native “alt” property on an image fill or an image layer; alt text has to be carried as a layer name, a comment, a sticky note, a separate spec document, or a plugin-added field. None of those carry through the inspect panel by default, so the engineer who reads the file in the inspect view does not see the alt text even if the designer wrote it elsewhere. Teams that closed the gap consistently used one of three workarounds: plugin-managed alt-text fields on every image variant, a documented convention that the layer name is the alt text, or a separate alt-text spreadsheet keyed to image filenames that shipped alongside the file.

Icon-only buttons were a sub-failure inside this failure mode. In 41 of 50 files, icon buttons — the search-glass, the menu hamburger, the close-X, the share-arrow — had no accessible-name annotation, leaving the engineer to write aria-label=“Search” from the visual context without confirmation that “Search” was the right word in the brand’s voice (was it “Find”? was it “Lookup”? was it nothing because the button opens a panel labelled elsewhere?). Icon naming is exactly the kind of micro-copy decision that benefits from a designer’s pen, and exactly the kind that the handoff loses.

78%
of files had no alt-text annotations on content images
41 / 50
files left icon-button accessible names to the engineer
3 / 50
files annotated alt text on every image, end-to-end
3
workarounds the closing teams used: plugin field, layer-name convention, spreadsheet
Decorative vs informative is a design decision

Every image is either decorative (alt should be empty, the screen reader skips it) or informative (alt carries the information the visual conveys). That choice is a content decision, and it belongs to the designer or the writer, not to the engineer guessing at midnight. A file that says nothing about which images are decorative ships either too much alt text (every image is verbosely described, including the ones that are pure ornament) or too little (the hero illustration is described, every in-body image gets alt="" because the engineer played it safe).


4. Reading order, motion, dark-mode contrast

The remaining three properties had distinct shapes of failure. Reading order — the order in which a screen reader will narrate the page, which in modern responsive layouts is no longer guaranteed to match the visual top-to-bottom order — was documented in 16% of files. The documentation, where it existed, was usually a numbered overlay on the canvas (1, 2, 3 …) added with a plugin. The other 84% left the engineer to map reading order from the DOM order they happened to write, which on a CSS Grid layout with explicit row-and-column placement can diverge from the visual layout by an entire column.

Motion preferences fared worst. Ten percent of files mentioned prefers-reduced-motion at all. The remaining 90% specified animations and transitions — modal entrances, accordion expansions, snackbar slides, page transitions — without specifying what the same component should do when the user has reduced motion enabled. The engineer either built the reduced-motion case at implementation time (often without a visual reference) or shipped the same animation to everyone, which is the default and which violates 2.3.3 Animation from Interactions for users who set the preference.

Dark-mode contrast was specified for 30% of components in files that shipped both themes. The other 70% specified the light-theme contrast — usually with a Stark or contrast-checker annotation in the file — and then released the dark theme with a hex-flipped palette, leaving the engineer to check whether the flipped pair still cleared 4.5:1 on body text and 3:1 on UI components. In about a fifth of the 31 dual-theme files, at least one component dropped below contrast threshold in the dark theme because the dark surface and the dark text were both tuned for the light theme’s contrast math, not the dark theme’s.

The matrix below summarises the five gaps

The matrix tracks “completion rate” for each property across the corpus — the share of files in which the property was documented to the engineer-reads-and-implements bar. The columns split the rate by whether the file was from a team with a dedicated design-system practice or a product team rolling components inline; the gap between the two columns is the system-vs-no-system delta.

Accessibility propertyAll 50 filesDesign-system teams (12)Product teams (38)System-vs-product delta
Focus-state design (interactive components)40%75%29%+46pp
Alt-text annotations (content images)22%50%13%+37pp
Reading order (canvas-level)16%42%8%+34pp
Motion preferences (animated elements)10%33%3%+30pp
Dark-mode contrast (dual-theme files only, n=31)30%55%19%+36pp

”Design-system teams document the accessibility decisions at roughly twice the rate of product teams — but even the system teams clear the bar on only one property out of the five most of the time.”

— disabilityworld.org engineering desk, audit notes

5. Stark and Able: patchy adoption

The two plugins that show up most often in the corpus are Stark and Able. Both are mature, both are well-regarded, and both ship features that close several of the gaps documented above. Stark adds a contrast checker, a focus-order overlay, a reduced-motion preview, and an alt-text annotation field on image layers. Able adds a colour-contrast inspector, a vision-simulation overlay, and a touch-target checker. Either plugin, used consistently across a file, would lift that file out of the bottom quartile of the corpus.

Used consistently is the operative phrase. Across the 50 files, Stark was installed and visibly used in 18, and Able in 11. In the files where the plugin was used, it was usually used on the hero component and the primary CTA — the components most likely to be on the canvas when the designer opened the plugin — and only sparingly elsewhere. Six files used Stark on a global pass; one used Able on a global pass. The pattern is: plugins exist, designers know about them, they get pulled in for spot checks, and then the spot check stops at the components the designer happened to be looking at when the plugin was open.

The two teams who closed the audit on plugin usage did one thing differently: they ran the plugin’s audit feature on every page of the file as a release-gate step before the file was shared with engineering. The audit ran in the file, produced a report, and the report had to be empty (or its exceptions documented) before the file moved from “in design” to “ready for engineering.” This is plugin-as-workflow rather than plugin-as-spot-check, and it is the difference between 80% coverage and 20% coverage in our sample.

Stark
Stark Lab · contrast, focus order, motion, alt
approx. 1.4M installs across Figma + Sketch + Adobe XD (May 2026)
Adoption in corpus18 / 50 files (36%)
Used as workflow
Gap coverage if used end-to-end4 of 5 properties closeable (focus, contrast, alt, motion)
Able
Able · contrast, vision sim, touch targets
approx. 320k installs in Figma community (May 2026)
Adoption in corpus11 / 50 files (22%)
Used as workflow
Gap coverage if used end-to-end2 of 5 properties closeable (contrast, dark-mode contrast)
Plugins are necessary, not sufficient

A plugin lifts the floor: the contrast checker catches the obvious 2.1:1 failures, the alt-text field gives the designer somewhere to type. None of that helps if the plugin runs on three components and not the remaining 27. The fix is to put the plugin in the workflow — a release-gate step, a pre-handoff checklist, a Figma branch that cannot be merged without an empty plugin report — rather than in the designer’s discretion at the moment they remember it exists.


6. A handoff checklist and a token contract

The audit produces a checklist and a contract. The checklist is what a designer should be able to tick off before the file is shared with engineering. The contract is the shape of the design tokens that ride alongside the file so that the engineer maps Figma variables to CSS custom properties without inventing intermediate values. Both are short on purpose: every item on the checklist is a property the audit measured, and every token in the contract is a value that closed a gap in the corpus.

1

Every interactive component ships a state=focus-visible variant.

Not “the system has a focus ring.” A variant named focus-visible on the component itself, with the outline color, width, and offset bound to the focus-ring token. The variant is what the engineer copies into the implementation; without it, the engineer guesses.

2

Every content image has alt text in a plugin-managed field or a documented layer-name convention.

Pick one location and enforce it. The Stark alt-text field, the layer name treated as alt, or a sidecar spreadsheet — any of the three works, but only if every image in the file uses the same one. Icon-only buttons get an accessible-name annotation too, in the same location, with the exact string the engineer should put in aria-label.

3

Reading order is documented on any page where DOM order will diverge from visual order.

The simplest documentation is a numbered overlay added with a plugin (Stark has one, several community plugins do too). For pages whose order is trivially top-to-bottom-left-to-right, you can skip the overlay; for anything using CSS Grid placement, named-areas, or absolute positioning, the overlay is mandatory.

4

Every animated or transitioning element has a reduced-motion variant on the canvas.

A second frame, a second variant, or a documented “no animation” version. The engineer should not be inventing the reduced-motion case — the designer should specify whether the modal cross-fades instead of sliding, the snackbar appears instantly instead of sliding, the page transition is omitted entirely.

5

For dual-theme files, contrast is checked in the dark theme separately, not derived from the light theme.

Dark-mode contrast math is its own problem; flipping the palette is not enough. Run Stark or Able on every component in dark mode, not just in light. Document the contrast ratio in the variant’s notes so the engineer can confirm the implementation matches.

6

The file ships with a token contract: a flat list of every Figma variable mapped to its CSS custom property.

The contract is the bridge between the file and the codebase. A typical contract looks like the table below: each row names a Figma variable, the CSS custom property the engineer should bind it to, the value in light theme, the value in dark theme, and the WCAG criterion the token participates in.

Figma variableCSS custom propertyLight valueDark valueWCAG ties
color/focus-ring—focus-ring#0B57D0#A8C7FA2.4.7, 1.4.11
color/text/body—text-body#1F1F1F#E3E3E31.4.3 (4.5:1 on surface)
color/surface/raised—surface-raised#FFFFFF#1F1F1F1.4.11 (3:1 against neighbour)
size/touch-target/min—touch-target-min44px44px2.5.5, 2.5.8
motion/duration/standard—motion-standard200ms200ms2.3.3 (skip if reduced-motion)
motion/duration/reduced—motion-reduced0ms0ms2.3.3
Why the contract is the lever

Once the contract exists, the engineer’s job is mechanical: bind the CSS custom property to the Figma variable, ship the implementation, audit by comparing the rendered values to the contract. Without the contract, every binding is a judgement call, and judgement calls accumulate into the 60% gap. The contract is the single artifact that moves accessibility from “the engineer is responsible at handoff time” to “the system is responsible at design time.”


Conclusion: the file is the contract

The 50-file audit closes on a simple finding. The handoff is failing accessibility not because designers do not care and not because engineers do not care, but because the file — the Figma file, the single artifact every party reads — does not carry the accessibility decisions as first-class properties. Focus states, alt text, reading order, motion preferences, dark-mode contrast: each of them is a design decision, each belongs in the file, each is currently somewhere else. In a sticky note, in a Slack message, in a separate spreadsheet, in the engineer’s head at 4pm on a Friday.

The fix is not a heroic designer or a heroic engineer. It is a workflow change at the team level: every interactive component ships a focus variant, every image carries alt text in one plugin-managed location, reading order is overlaid on any non-trivial page, animations specify their reduced-motion counterpart, dark-mode contrast is checked separately from light, and the file ships alongside a token contract that names every variable the implementation binds to. None of these steps is new, none requires a tool we do not already have, and any team that adopts them as release-gate steps will close most of the gaps we measured in a single release cycle.

The deeper finding is that design-system teams already do this at roughly twice the rate of product teams. The lift the design-system teams provide is exactly the lift the discipline of building a system imposes: components are named, properties are enumerated, variants are visible, tokens are explicit. Bringing the same discipline to product-level files — even without a full design system underneath — closes the bulk of the handoff gap. It is not a tooling problem any more. It is a workflow choice.

”The file should arrive with the accessibility decisions already made. Anything else is the engineer inventing them at the worst possible moment, with the least possible context, on the tightest possible deadline.”

— disabilityworld.org engineering desk, closing note