Triage
- Snapshot the page (page export PNG + every variable rebuilt to JSON) before any rewire.
- Lock the page to read-only for non-DS editors during sweep.
- Confirm with team which "OLD" components can be retired.
Gap analysis between the 34 component groups on the Components page (fysBbW10LRJUNgisaBKXJo → 4050:50253) and the locked v3.2 token architecture. Captures token-binding violations, WCAG 2.2 AA conformance issues, structural duplication, and a five-phase remediation plan. Read-only audit — no Figma writes were made.
v3.2 landed in the variable layer on 2026-04-29. The components on this page were authored against earlier token states (v3.0 / v3.1 + remote libraries). This audit measures the gap between component reality today and the v3.2 invariants, plus WCAG 2.2 AA conformance.
Two locked invariants drive the token-side findings:
Global/{black, white, black-alpha/*, white-alpha/*, system/*} and Modes/color/{primary, secondary, neutral, tertiary}/{50..900}. Everything else aliases.Global/scale/* contains exactly [0, 4, 8, 12, 16, 20, 24, 28, 32, 40, 48, 56, 64, 72, 80, 96, 112, 128, 144, 160]. No off-grid stops.Probed via Figma MCP: full canvas metadata (179 KB XML) plus visual screenshots and resolved variable bindings for the ten highest-impact component groups (Buttons, Inputs, Toggle, Modal Dialogs, Bottom sheets, Navigation, Cards, Headers, List Items, Pills, Banners, Notifications).
Top-level frames on canvas 4050:50253, listed roughly by audit priority.
| # | Group | Node | Audit focus |
|---|---|---|---|
| 01 | Buttons | 4050:64556 | CTA radius, contrast, focus, target size |
| 02 | Inputs | 4050:56823 | Labels, errors, placeholder contrast, autocomplete |
| 03 | Select | 4050:57100 | Combobox semantics |
| 04 | Toggle | 4050:57210 | Target size, contrast, non-color affordance |
| 05 | Modal Dialogs | 4050:63769 | Focus trap, dismiss, sizing |
| 06 | Bottom sheets | 4050:62808 | Drag affordance, dismiss, mobile target |
| 07 | Navigation | 4050:62261 | Active-state contrast, chrome interaction |
| 08 | Cards | 4050:53708 | Reuse pattern, spacing scale |
| 09 | Headers | 4050:56038 | Header active-state pattern |
| 10 | Compliance Headers | 4050:56198 | Regulatory chrome variants |
| 11 | List Items | 4050:61483 | Touch targets, dividers, OLD/NEW dedup |
| 12 | Pills · Badges | 4050:55890 · 4050:55972 | Contrast on tinted bg |
| 13 | Banners | 4050:61205 | Inline messaging, dismissal |
| 14 | Notifications | 4050:55720 | Live-region intent, contrast on color |
| 15 | Hero Sections | 4050:57235 | Layout, type scale |
| 16 | Footer · Full-width | 4050:57970 · 4050:58084 | Landmark, link contrast |
| 17 | Search | 4050:56708 | Combobox / listbox semantics |
| 18 | Accordion | 4050:61436 | Disclosure semantics |
| 19 | Pagination | 4050:57684 | aria-current="page", target size |
| 20 | Tournament | 4050:52648 | Domain pattern reuse |
| 21 | Calendar | 4050:62600 | Date-grid keyboard model |
| 22 | Progress indicators | 4050:53162 | Status announcement |
| 23 | Effects · Spacers · Informational | 4050:61012 · 4050:61076 · 4050:61128 | Scaffolding · move to Foundations page |
| 24 | Product Filter | 4050:57890 | Multi-select pattern |
| 25 | Balance | 4050:57773 | Number formatting |
| 26 | iOS Elements | 4050:58251 | Platform chrome |
| 27 | Compliance Header Token System Components | 4050:64853 | Documentation frames · move |
| 28 | Originals Title Animated · Originals Logo | 4050:64921 · 4050:64942 | Branded micro-asset |
| 29 | Table of contents | 4050:53028 | Page-internal navigation |
Whiteframe/Heading 5 new, Whiteframe / Label Regular in Museo Sans) coexists with Open Sauce One body styles inside the same component sheet.Size = Desktop|Mobile and Layout = Logo+CTA | Logo+Title | Logo+Title+Balance axes.Size = sm|md|lg|xl variant.Pill with Size · Trailing · Leading · State.The Cards group is 20,017 px tall — the largest group on the page. Game-tile / promo-tile patterns have grown organically into many one-off variants. Without consolidation, downstream consumers cannot reliably pick one "card" — every product team forks a new variant.
Notifications mixes the "container with leading icon + dismiss + optional CTA" pattern across 3 colors × 4 variants instead of using one component with Status = info|success|warning|error + Action = none|inline|stacked + Dismissible = true|false properties. Tournament rebuilds list-item/header patterns instead of instancing List Items / Tournament / Header — the file already has the master, but Tournament also has hand-built copies.
Compliance Header Token System Components and Originals Title Animated / Originals Logo are documentation/asset frames mixed into the component canvas. They are not reusable components — should move to a separate "Tokens & Assets" page. Spacers and Effects are scaffolding; they belong in a foundations page beside the token tables.
Multiple naming conventions co-exist on the canvas: List Item / Tournament / Header (forward slash, title case) · Variant =Main (space-equals) · User=No, Initials=Yes (CSV-style) · Whiteframe/Heading 5 new (legacy product prefix). Pick one convention and rename the rest.
| Token | Current | v3.2 target |
|---|---|---|
| Extras/CTA Radius | 3 | scale/4 |
| Extras/Input Radius | 3 | scale/4 |
| Base/Radius 3 | 3 | scale/4 |
| Extras/Box Radius | 6 | scale/4 or scale/8 |
| Extras/Card Radius | 12 | scale/12 ✓ |
| Extras/Pill Radius | 100 | scale/96 (or scale/160 for true pill) |
~41 affected radius aliases were already remapped in the variable layer on 2026-04-29; the components on this page still show the old values because component bindings have not been swept.
Typography rounds up per the locked rule.
| Token | Current | v3.2 target (round up) |
|---|---|---|
| Font/Size/X Small | 8 | scale/8 ✓ (deprecate as semantic body) |
| Font/Size/Small | 11 | scale/12 |
| Font/Line Height/Small | 14 | scale/16 |
| Font/Size/Caption | 12 | scale/12 ✓ |
| Font/Size/Label | 15 | scale/16 |
| Font/Size/Heading 5 | 16 | scale/16 ✓ |
| Font/Size/Heading 4 | 17 | scale/20 |
| Font/Size/Heading 3 | 19 | scale/20 |
| Font/Size/Heading 2 | 22 | scale/24 |
| Font/Size/Heading 1 | 28 | scale/28 ✓ |
| Font/Line Height/Heading 1 | 36 | scale/40 |
| Font/Size/Display 2 | 50 | scale/48 |
| Font/Line Height/Display 2 | 60 | scale/64 |
token('space.100') = 8 appears bound on most components — Atlassian-style legacy naming. v3.2 already exposes Semantic/spacing/{xxs..3xl}. Components should consume Semantic spacing tokens, not the literal.
Every probed component (Buttons, Inputs, Modals, Headers, Notifications, List Items) returns variable names from the old library:
Main/Text · Main/Primary · Main/Background · Main/On-Primary
Main/Text-Disabled · Main/Text-Supportive · Main/Disabled CTA
Generic/Stroke 10 · Generic/Stroke 30 · Generic/Dividers
Generic/Input-Border · Generic/Overlay-80 · Generic/IOS-Bar
Lobby Header/Background · Lobby Header/Surface-Primary · Lobby Header/Active-Selected
Lobby Header/On-Surface-Primary · Lobby Header/CTA · Lobby Header/Text ...
Extras/CTA Radius · Extras/Input Radius · Extras/Pill Radius
Extras/Box Radius · Extras/Card Radius
Font/Size/* · Font/Line Height/* · Font/Family/* · Font/Weight/*
Whiteframe/* # Museo Sans family — clearly stale
Base/Base White · Base/Base Black · Base/Base White 10 · Base/Radius 3
System/Error · System/Success · System/Informative · System/Orange · System/Purple
None of these match the v3.2 namespace (Global/*, Modes/*, Theme/*, Semantic/*, Component Packs/*). The variable rename + restructure landed in collections; component bindings did not follow.
Semantic/spacing/{xxs..3xl} — components hold space.100 literal instead.Semantic/icon-size/{sm,md,lg,xl} — icon sizes are free FLOATs.Global/scale/* — radii / sizes still bound to Extras/* and Font/Size/*.Global/elevation/0..4 Effect Styles — components hold raw inline Effect(type: DROP_SHADOW, …).Global/breakpoint/* — zero references on this canvas.This is the largest single gap: the v3.2 abstractions exist but nothing on the component page reaches them.
Open Sauce One (current) · Museo Sans (legacy Whiteframe/*) · SF Pro Text (iOS native — fine for iOS Elements only). Museo Sans appears inside Inputs and Modal Dialogs — these are NOT iOS-only contexts. Audit and remove Museo Sans bindings everywhere except where intentionally legacy.
Tokens currently carrying raw hex outside the two allowed homes (Global / Modes DNA).
| Token | Hex | Should alias |
|---|---|---|
| Generic/Stroke 10 | #0000001a | Global/color/black-alpha/10 |
| Generic/Stroke 30 | #0000004d | Global/color/black-alpha/30 |
| Generic/Input-Border | #b7b8bc | Modes/color/neutral/300 |
| Generic/Dividers | #b7b8bc | Modes/color/neutral/300 |
| Base/Base White 10 | #ffffff1a | Global/color/white-alpha/10 |
| Base/Base White 30 | #ffffff4d | Global/color/white-alpha/30 |
| Base/Base Black 10 | #0000001a | Global/color/black-alpha/10 |
| Base/Base-12 | #000000 | Global/color/black |
| System/Error | #e92636 | Global/color/system/error/500 |
| System/Success | #00c76b | Global/color/system/success/500 |
| System/Informative | #0082eb | Global/color/system/info/500 |
| System/Orange | #f9ae41 | Global/color/system/warning/500 |
| System/Purple | #a864ff | no v3.2 home — add tertiary slot or move to brand DNA |
| Lobby Header/Active-Selected | #1c1d20 | Modes/color/neutral/900 → primary/500 (per rule) |
| Lobby Header/Surface-Primary | #5b5c60 | Modes/color/neutral/600 |
| Lobby Header/Surface-1 | #98989c | Modes/color/neutral/400 |
| Main/Disabled CTA | #98989c | Modes/color/neutral/400 |
| Main/On-Disabled CTA | #b7b8bc | Modes/color/neutral/300 |
| Main/Text-Disabled | #b7b8bc | Modes/color/neutral/300 |
| Main/Text-Supportive | #98989c | Modes/color/neutral/400 |
| Main/Text | #1c1d20 | Modes/color/neutral/900 |
| Default/SystemBlue/Light | #007AFF | iOS native — leave or add Modes/color/native/ios-blue |
Computed from the hex values returned by get_variable_defs. AA thresholds: 4.5:1 normal text, 3:1 large text (≥18pt or 14pt bold) and UI components.
| Pairing | Foreground | Background | Ratio | Verdict |
|---|---|---|---|---|
| Disabled CTA label | #b7b8bc | #98989c | 1.27:1 | Critical |
| Notification / Success body | #f8f9fd | #00c76b | 2.05:1 | Critical |
| Notification / Info (small) | #f8f9fd | #0082eb | 3.59:1 | High Large only |
| Notification / Warning | #f8f9fd | #f9ae41 | 1.93:1 | Critical |
| Notification / Purple promo | #f8f9fd | #a864ff | 2.94:1 | High Large only |
| Text-Supportive | #98989c | #f8f9fd | 2.74:1 | High Helper text fails |
| Lobby Header / On-Surface-1 | #f8f9fd | #98989c | 2.74:1 | High White text on light grey |
| Header / Active-Selected on Surface-Primary | #1c1d20 | #5b5c60 | 3.74:1 | Medium Visually muddy |
| Header / On-Surface-Primary white | #f8f9fd | #5b5c60 | 5.83:1 | ✓ Passes |
| Notification / Error body | #f8f9fd | #e92636 | 5.04:1 | ✓ Passes |
| Body text on page | #1c1d20 | #f8f9fd | 15.5:1 | ✓ Passes AAA |
Every failing surface either needs (a) the foreground swapped to neutral/900 (dark) for warm/light bodies (success, warning, info, purple) or (b) the surface stop deepened to a 700/800 shade. Error red is the only AA-passing surface today.
Font/Size/X Small = 8 px is below any reasonable readable minimum. Used in Headers (X Small Bold). Recommend retiring from semantic use; keep only for non-essential decorative numerals if at all.Font/Size/Small = 11 px is borderline. WCAG doesn't set a minimum but mobile platforms recommend 12 px. Round to 12.WCAG 2.2 SC 2.5.8 (AA) requires interactive targets ≥ 24×24 CSS px (with exceptions for inline links and dense lists). Mobile lift to 44×44 (Apple HIG) / 48 dp (Material).
| Element | Estimated | Verdict |
|---|---|---|
| Toggle drag thumb | ~16 px | High below 24×24 |
| Notification dismiss × | ~16 px | High wrap in 24×24 hit area |
| Modal dismiss × | ~16 px | High wrap in 24×24 hit area |
| Micro Button | ~24×24 or below | High measurement required |
| Page Header chevrons | ~24×24 | Medium borderline |
| Pagination numerals | small | Medium measurement required |
| Button / Default | ~40 px | ✓ Passes |
| List Item rows (mobile) | ~44 px | ✓ Passes |
Components do not show a defined focus state in any screenshot probed. There is no Theme/focus-ring or Global/focus/* token in the variable defs returned. Without a focus token, every interactive primitive (Buttons, Pills, Inputs, Toggle, List Items, Pagination, Tabs) lacks a designed :focus-visible style.
WCAG 2.2 adds 2.4.11 Focus Not Obscured (Minimum) and 2.4.13 Focus Appearance at AA — focus must be ≥ 2 px outline at 3:1 contrast against both component and adjacent background. None of this is currently encoded.
Global/focus/ring-color → Modes/color/border/focus → Modes/color/primary/500Global/focus/ring-width → Global/scale/4 (used as 2px stroke)Global/focus/ring-offset → Global/scale/4Add an explicit State = Focus variant to every interactive component. Verify the focus ring resolves to 3:1 against both surface and adjacent neighbours per brand mode.
System/Error border only. Helper text presence is good — but if helper text is omitted in production, error state would be color-only (fails 1.4.1 Use of Color).aria-current="page" requirement and a non-color visual (underline / weight).role="grid", arrow-key model, aria-selected. No "today" indicator visible — required for orientation.aria-expanded mapping documented.aria-live polite/assertive mapping per status.prefers-reduced-motion reduced variant exists. Add motion tokens or a static fallback.Variable defs returned zero Global/breakpoint/* consumption across all probed components. v3.2 declares breakpoint/{mobile, tablet, desktop, desktop-wide} but they are unused.
Headers / Main Header (Responsive) and Main Header (Mobile) are siblings, not breakpoint variants.Headers / Grid Header (Responsive) is a third sibling for grid contexts.v3.2 ships Light + Dark in Theme. Components on this canvas are presented in Light only. There is no Dark-mode preview frame to validate token resolution. Add a Dark variant frame per major component group, or a single "Component canvas (Dark)" page.
| # | Issue | Severity | Affected groups |
|---|---|---|---|
| A | Notification text-on-color fails AA (success, warning, info, purple) | Critical | Notifications, Banners, inline alerts |
| B | Components still bind legacy library tokens (Main/*, Generic/*, Lobby Header/*, Extras/*, Whiteframe/*) — v3.2 namespace not adopted | Critical | All 34 groups |
| C | Off-grid radii (3, 6, 100) and typography (11, 15, 17, 19, 22, 36, 50, 60) | High | All groups via shared tokens |
| D | Raw hex in non-Global / non-DNA tokens | High | All groups |
| E | No focus tokens / focus states defined (WCAG 2.4.7 / 2.4.11 / 2.4.13) | High | All interactive components |
| F | Touch target sizes below 24×24 (Toggle thumb, Notification ×, Modal ×, Micro Button) | High | Toggle, Notifications, Modals, Buttons |
| G | OLD/NEW component duplication shipped | High | List Items, Navigation |
| H | Headers split into 4 components for one logical pattern | Medium | Headers |
| I | Pills split into 4 components for one family | Medium | Pills |
| J | Banners has 8+ size variants of same template | Medium | Banners |
| K | Cards group has uncontrolled growth (20,017 px) | Medium | Cards |
| L | Font/Size/X Small = 8 px retained as semantic size | Medium | Headers, Footer (fine print) |
| M | Three font families coexist outside iOS scope | Medium | Inputs, Modal Dialogs |
| N | No breakpoint tokens consumed | Medium | All groups |
| O | No Dark-mode preview frames | Medium | All groups |
| P | Documentation/asset frames mixed with components | Low | Compliance Header Token System, Originals Title/Logo, Spacers, Effects |
| Q | Naming conventions inconsistent | Low | All groups |
| R | Color-only state encoding | Medium | Toggle, Inputs, Pagination |
Modes/color/header/active-selected must not alias the same neutral stop as Modes/color/header/surface-primary for a brand. Pin active-selected to primary/500 instead — this prevents invisible nav active state on coloured headers (Snabarre blue, Mobilespin red).
Today's bindings on the Components page:
Lobby Header/Surface-Primary = #5b5c60 (≈ neutral/600)Lobby Header/Active-Selected = #1c1d20 (≈ neutral/900)Two stops apart — passes the rule by hex, but contrast is weak (3.74:1) so the active state is visually muddy. Re-point active-selected to primary/500 per the rule.
Main/Text → Modes/color/text/primary (alias neutral/900)Main/Primary → Modes/color/brand/primary (alias primary/500)Main/Background → Modes/color/surface/pageGeneric/Stroke 10 → Modes/color/border/subtle (alias Global/color/black-alpha/10)Generic/Stroke 30 → Modes/color/border/strong (alias Global/color/black-alpha/30)Lobby Header/* → Modes/color/header/*Extras/CTA Radius → alias Global/scale/4Extras/Box Radius → alias Global/scale/4 (or 8) — confirm with teamExtras/Pill Radius → alias Global/scale/96Font/Size/Heading {1..5} → alias Global/typography/size/{N} rounded upSystem/Error → alias Global/color/system/error/500 · same for success/info/warning (orange→warning, purple → tertiary or new home)getLocalVariablesAsync audit to confirm zero raw hex outside Global / Modes DNA.neutral/900. Verify contrast ≥ 4.5:1 per pairing.Global/focus/ring-color, /ring-width, /ring-offset) and apply a State = Focus variant on every interactive component.X Small (8 px): deprecate from semantic typography. Migrate consumers to Small (12 px) minimum.Layout + Size props.Size + Leading + Trailing + State props.Size = sm|md|lg|xl|fullwidth.Status × Action × Dismissible props instead of 12 frames.Card / Game Tile, / Promo, / Payment) and dedupe within each family.Size = Mobile | Tablet | Desktop variant to Headers, Hero Sections, Footer, Cards, Banners.Theme = Dark).breakpoint/desktop = 1024 / desktop-wide = 1440.Domain / Component / Variant. Variant property names use PascalCase, variant values use Title Case.Semantic/spacing/* and Semantic/icon-size/* everywhere space.100 and free-FLOAT icon sizes appear.Global/elevation/0..4 Effect Styles in place of inline Effect(type: DROP_SHADOW, …).Whiteframe/* (Museo Sans) bindings outside scoped legacy areas.Second-pass structural audit run via the Figma Plugin API on 2026-04-30, after the user asked us to look harder at paddings, auto-layout, and element grouping. We walked the 11 highest-impact component masters and tagged every frame for layout-mode, padding values, item-spacing, sizing modes, asymmetry, group-vs-frame, and nesting depth. The aggregate is below; the table beneath it has per-group counts.
The audit walked ~70,683 nodes across 28,695 frame-like containers in: Buttons, Inputs, Toggle, Modal Dialogs, Navigation, Cards, Headers, List Items, Banners, Pills, Notifications.
| Finding | Count | % of frames | Severity | WCAG link |
|---|---|---|---|---|
Frames with layoutMode = NONE (no auto-layout) | 9,363 | ~33% | Critical | 1.3.2 Meaningful Sequence — z-order export |
| Frames with off-grid padding (not a 4-pt scale stop) | 4,373 | ~15% | High | v3.2 invariant · 1.4.10 Reflow risk |
Frames with off-grid itemSpacing | 5,022 | ~18% | High | v3.2 invariant |
| Frames with asymmetric padding (top≠bottom or left≠right) | 4,120 | ~14% | Medium | 2.4.7 Focus Visible · 2.4.11 Focus Not Obscured |
Adjacent interactive children with itemSpacing = 0 | 989 | — | Critical | 2.5.8 Target Size (Minimum) |
| GROUP nodes (vs Frames with auto-layout) | 800 | — | High | 1.3.2 Meaningful Sequence |
| GROUP nodes nested inside an auto-layout parent | 429 | — | Critical | 1.3.2 + responsive break |
| Master | Walked | Frames | No auto-layout | Off-grid pad | Off-grid gap | Asym pad | Zero-gap interactive | Groups | Group-in-AL | Max depth |
|---|---|---|---|---|---|---|---|---|---|---|
| Cards | 24,627 | 9,832 | 3,680 | 1,581 | 1,907 | 1,595 | 291 | 310 | 310 | 16 |
| Navigation | 4,718 | 2,036 | 401 | 186 | 160 | 297 | 135 | 9 | — | 12 |
| Headers | 2,200 | 930 | 220 | 94 | 58 | 112 | 51 | 3 | 3 | 11 |
| Modal Dialogs | 1,925 | 720 | 145 | 151 | 153 | 64 | 24 | 33 | 33 | 14 |
| List Items | 1,809 | 771 | 203 | 110 | 257 | 24 | 6 | 20 | 20 | 12 |
| Buttons | 1,234 | 537 | 143 | 24 | 29 | 80 | 46 | 9 | 6 | 9 |
| Banners | 1,191 | 513 | 113 | 176 | 30 | 22 | 11 | 25 | 25 | 15 |
| Notifications | 722 | 290 | 56 | 7 | 25 | 36 | 16 | 7 | 7 | 10 |
| Pills | 579 | 249 | 42 | 20 | 22 | 41 | 16 | 5 | 5 | 9 |
| Inputs | 496 | 215 | 41 | 33 | 78 | 18 | 2 | 3 | 3 | 12 |
| Toggle | 54 | 17 | 5 | 3 | 4 | 1 | 0 | 6 | 6 | 7 |
The top off-grid padding values across the 6 deeply-counted masters (Cards · Headers · List Items · Banners · Pills · Notifications). Bulk-fixable by editing the underlying token alias once each.
| Value (px) | Occurrences | Closest 4-pt stop | Where |
|---|---|---|---|
| 6 | 2,411 | scale/4 or scale/8 | Cards (1,952), Headers (147), List Items (42), Banners (222), Pills (40), Notifications (8) |
| 2 | 1,153 | scale/4 | Cards (783), Banners (308), Headers (58), Pills (2), Notifications (3) |
| 15 | 870 | scale/16 | Cards "Hover Overlay" (15/15/15/15) |
| 5 | 784 | scale/4 | Cards "Info Tag" (2/5/2/5), List Items "Number Badge" (0/5/0/5) |
| 3 | 164 | scale/4 | Cards (120), Banners (44) — Inputs "Payment Method" (3/0/3/0) |
| 60 | 121 | scale/56 or scale/64 | Cards (111), Banners (10) — Banner content padding (12/60/12/60) |
| 18 | 60+ | scale/16 or scale/20 | List Items "Bonus Widget", Pills "Filter Item / Filter Pill" (18/18/18/18) — also recurs in Inputs Credit Card / Amount Field |
| 36 | 57 | scale/32 or scale/40 | Cards (53), Headers (4) |
| 10 | 13+ | scale/8 or scale/12 | Headers Filter chips (6/8/6/8), Inputs "Container" (10/0/10/0) |
| 14 | 40 | scale/12 or scale/16 | List Items, iOS Status Bar (12/14/12/21) |
| 100 | ~4 | scale/96 | Modal Dialogs "Campaign Modal" (100/100/100/100) |
| 26 | ~6 | scale/24 or scale/28 | Modal "Content" (24/26/24/26) — mixes 24 and 26 |
itemSpacing values that recur most| Value (px) | Occurrences | Closest 4-pt stop | Notes |
|---|---|---|---|
| 10 | 608+ | scale/8 or scale/12 | Most common gap across every master |
| 2 | 826 | scale/4 | Cards-heavy |
| 6 | 675 | scale/4 or scale/8 | All masters |
| 50 | ~25 | scale/48 | Section gaps in Buttons, Inputs, Toggle, Pills, Banners |
| 30 | ~10 | scale/28 or scale/32 | Pills, Inputs |
| 962 | 6 | N/A — hack | Headers — abuse of itemSpacing as "push to far edge" instead of primaryAxisAlignItems = SPACE_BETWEEN |
| 142 | 22 | N/A — hack | List Items — same anti-pattern |
| 37 | ~5 | scale/32 or scale/40 | Toggle, Pills, Banners, Notifications, Headers |
All three Toggle states (Active=False, Disabled=False, Active=True, Disabled=False, Active=False, Disabled=True) are layoutMode = NONE. The thumb is positioned absolutely inside the track. This is why hit-target measurement is fragile and why the Toggle screenshot shows inconsistent visual alignment between states. Migrate each state to HORIZONTAL auto-layout with primaryAxisAlignItems = MIN on the off state and MAX on the on state.
9,832 frames, 3,680 without auto-layout (37%), max nesting depth 16, 310 GROUPs all sitting inside auto-layout parents (the worst-of-both pattern), 1,595 frames with asymmetric padding, 1,907 frames with off-grid itemSpacing. The most common off-grid pad is 6 (1,952 occurrences). Rebuild the game-tile family from scratch on auto-layout, with explicit Card / Game Tile, Card / Promo, Card / Payment bases — do not try to in-place fix.
"Campaign Modal" uses [100, 100, 100, 100] padding (off-grid). "Content" mixes 24 with 26. Six "Checckbox old" GROUPs (sic — typo) at depth 11 indicate an in-flight migration that was abandoned. Modal Dialogs is the second-worst structural offender after Cards.
A shared base component appears across Buttons, Inputs, Toggle, Headers, List Items, Notifications with padding [6, 12, 6, 12]. The vertical 6 is off-grid; the horizontal 12 is on-grid. Single fix: edit the base component once, change vertical padding to scale/8; every consumer inherits.
Used on Inputs "Credit Card" / "Amount Field", Pills "Filter Pill" / "Filter Item", List Items "Bonus Widget", Buttons "Social Button". 18 px is off-grid; round up to scale/20 or down to scale/16 per family. Treat as an auditable token: introduce Semantic/spacing/card-inset aliasing the chosen stop.
itemSpacing = 962 and 142Headers and List Items use enormous itemSpacing values to push children to opposite ends of a row. The correct pattern is primaryAxisAlignItems = SPACE_BETWEEN with layoutSizingHorizontal = FILL. Replace 28 instances total. They will break responsive layouts at any width below the hard-coded gap.
A widely-used pattern: [0, 0, 0, 12] on the left half and [0, 12, 0, 0] on the right half — used to put a 12 px gap between two halves of a payment-method row. Replace with a single parent at itemSpacing = 12 and zero padding on the children. Reduces wrapper count and removes the asymmetry signal.
Inputs uses [0, 0, 0, 12] on every error/helper line — a 12 px indent so the message aligns under the input value, not the label. Functionally correct but encoded as asymmetric padding. Move to a stack-level auto-layout with paddingLeft = 12 on the message frame, OR prefix the input + helper as a single column with consistent left padding.
For every GROUP inside auto-layout: convert to a Frame with the same auto-layout direction. Where the GROUP wraps a single instance, unwrap it. The "Old" / "Checckbox old" GROUPs are leftover migration artefacts — delete after the new variant is signed off.
989 instances across the 11 masters where two interactive-looking children (INSTANCE nodes or items named button · cta · chip · tab · item · link · toggle) sit at itemSpacing = 0. WCAG 2.2 SC 2.5.8 (Target Size — Minimum, AA) allows targets smaller than 24×24 only if there is at least 24 px of spacing between centers — zero gap with small tap targets fails by both prongs.
Worst offenders:
itemSpacing on the parent. Bulk-applicable across Buttons, Inputs, Modal Dialogs.SPACE_BETWEEN. 28 instances.[100, 100, 100, 100] with scale/96 or rebuild on a viewport-relative wrapper. Single component.Method: Plugin API walk via use_figma, figma.skipInvisibleInstanceChildren = true, BFS over each master root, recording layoutMode, paddingTop/Right/Bottom/Left, itemSpacing, layoutSizingHorizontal/Vertical, type, depth, and parent-LM context. Off-grid = not in the canonical 20-stop scale set. Read-only — no mutations.
After each phase, verify with read-only Plugin API (use_figma in dry-run mode):
getLocalVariablesAsync() filtered to COLOR; assert every var outside Global/* and Modes/color/{primary,secondary,neutral,tertiary}/* resolves to VARIABLE_ALIAS in every mode. Zero raw hex tolerated.Global/scale/* value is in the canonical 20-stop set; assert every dimension consumer aliases a scale/N.State = Focus variant or :focus-visible styling layer with the focus tokens applied.Size or Status (must be variants, not separate components).Theme = Dark; assert no token resolves to its Light value.getLocalVariablesAsync audit returns 0 violations.