Accessibility & type audit

WCAG 2.2 contrast computed against the actual tokens in tokens.css. AAA = ≥7:1 (normal text best practice). AA = ≥4.5:1 normal / ≥3:1 large text (≥18px regular or ≥14px medium+). "Large" covers our uppercase button labels at default size.
01 Contrast matrix — dark mode (app default) tokens.css · .dark
Foreground Background Ratio Where it's used
--text --app-bg 15.5 AAA   Body, headlines, file names
--text --component-bg 13.5 AAA   Text inside modals, popovers, sheets
--text-secondary --app-bg 6.95 AA   Metadata, hints, captions, dates
--text-secondary --component-bg 6.04 AA   Secondary text in elevated surfaces
--solid --app-bg 3.66 Large only   Use for disabled / placeholder only — never body text
--yellow-solid --app-bg 12.2 AAA   Links, brand accents, codes — dark mode only
--yellow-solid --app-bg (light) 1.49 FAIL   Never use yellow as text on light. Use --yellow-text-secondary instead.
--green-text-secondary --app-bg 8.22 AAA   Success messages, healthy states
--red-text-secondary --app-bg 6.27 AA   Error messages, destructive labels
--red-text-secondary --red-component-bg 5.44 AA   Red text on red tint — confirm modal glyph
02 Button label contrast Buttons/Button.tsx parity
Primary · yellow
#000 on --yellow-solid
14.1:1 · AAA at any size
Success · green
#fff on --green-solid
3.2:1 · AA-large only — never use xs (24px)
Danger · red
#fff on --red-solid
3.9:1 · AA-large only — never use xs
Info · neutral
#fff on --solid
3.7:1 · AA-large only · prefer default
Default · neutral
--text on --component-bg
13.5:1 · AAA at any size
Invert · for emphasis
--app-bg on --text
15.5:1 · AAA at any size
WCAG considers a button label "large text" (3:1 minimum) when it's ≥14px medium-weight uppercase. Our defaults (md / lg) hit that. Green / red / info / invert buttons must never use the xs (24px) size — at 12px the label drops out of the large-text rule and the contrast becomes a fail.
03 Type size floors tokens · --fs-*
Token Sample Specs Notes
--fs-xs Caption · 12px 12px · 500 · uppercase · letter-spacing 0.08em Floor for labels, eyebrows, captions. Never body text. Always uppercase + medium weight at this size.
--fs-sm Secondary body · 13px regular 13px · 400 Metadata, file dates, sizes. AA at --text-secondary.
--fs-base Body copy · 14px regular — app default 14px · 400 Default body weight. All body text uses this.
--fs-md Subheading · 16px medium 16px · 500 Subheadings, dense UI emphasis. Mobile body minimum is 16px (prevents iOS auto-zoom on inputs).
--fs-lg Lead copy · 18px regular 18px · 400 Long-form lead. At this size, AA threshold drops from 4.5 → 3 (large-text rule).
TV minimum 10-foot reading · 24px+ 24px · 500 floor 10-foot UX: nothing below 24px on a TV surface (see TV foundations).
04 Hit-target sizes WCAG 2.5.5 · iOS HIG · Material
Pointer (desktop / web)
xs
24×24 · below WCAG AA — toolbar only
sm
28×28 · AA min (24 hit area + 4 spacing)
md
32×32 · default for dense UI
lg
36×36 · default for primary actions
Touch (mobile / iOS / Android)
36
Below 44pt — fine if margin-padded
44
44×44 · iOS HIG minimum
48
48×48 · Material accessibility floor
Rule: on mobile, any interactive element below 44×44 needs invisible padding to reach it. Use ::before with negative inset, or list-item rows with 12px vertical padding + 32px icon = 56px total.
05 Focus visibility WCAG 2.4.7 · 2.4.11
Input · focus
border → --border-hover
+ 1px box-shadow ring same color
Button · focus
box-shadow: var(--shadow-focus)
3px yellow halo · 35% alpha
Link · focus 2px outline + 3px offset
Same yellow as links themselves
D-pad / TV focus
Focused row
scale(1.06) + yellow halo + shadow
See tv-focus preview
06 Rules & common mistakes
Use --yellow-solid as text only on dark
On dark surfaces it's 12.2:1 AAA. On light backgrounds it's 1.5:1 — invisible. For yellow text on light, use --yellow-text-secondary (4.98:1 AA).
Never put yellow text on white
If you need a yellow accent in light mode, use the yellow-solid as background, not as foreground. Black on yellow = AAA either way.
Default = 36px buttons, sm = 28px
Anything below 28px should be icon-button only with a tooltip — never a labelled action. Below 24px is for inline toolbar affordances at most.
No xs (24px) green/red/info buttons
At 12px the label drops out of WCAG's "large text" rule and white-on-color falls below 4.5:1 AA. Use the default or primary variant instead.
Pair color with shape or text
Transfer health uses color + label + dot (green/yellow/red). Status is never communicated by color alone — covered for color-blindness.
No color-only error states
Invalid form fields get a red border and a text error message below. Failed transfers get a red dot, a red label, and an icon — three signals.
Body weight: regular 400
Body text is GT America Regular at 14px. Medium 500 is for subheadings, button labels, emphasis. Bold 700 reserved for headlines and numerics where weight earns scan-ability.
No black 900 in body
Black weight is for the wordmark and hero display sizes (96px+). At body size it reads as visual shouting and breaks rhythm. Three weights in product: 400 / 500 / 700.