Tier 0Foundations · type floors, hit targets and focus visibility from the token graph. Binds all four tiers; native tiers apply their platform floors on top.--fs-base · --shadow-focus · --component-bg-active · --border-hover

A11y minimums

Size and visibility floors: type scale minimums, hit-target sizes per input class, focus indicators per surface. Web floors here; iOS holds 44pt, Material 48dp, TV 24px type.
01 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 Caption · 0.875rem (12–13px) regular 0.875rem · 400 Metadata, file dates, sizes, button labels. AA at --text-secondary.
--fs-base Body copy · 1rem (14–15px) regular, app default 1rem · 400 Default body weight. Equals the root size, matching app.put.io's fontSize 1.
--fs-md Subheading · 1.125rem (~16px) medium 1.125rem · 500 Subheadings, dense UI emphasis. Mobile body minimum is 16px (prevents iOS auto-zoom on inputs).
--fs-lg Heading · 1.5rem (21–22.5px) medium 1.5rem · 500 Section headings. At 21px and weight 500 this remains normal text under WCAG; use 4.5:1. Large text starts at 24px regular or 18.7px bold.
TV minimum 10-foot reading · 24px+ 24px · 500 floor 10-foot UX: nothing below 24px on a TV surface (see TV foundations).
02 Hit-target sizes WCAG 2.5.8 AA · 2.5.5 AAA · iOS HIG · Material
Pointer (desktop / web)
xs
24×24 · WCAG 2.5.8 AA minimum, subject to its exceptions
sm
28×28 · exceeds the AA minimum with 4px breathing room
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: use the platform floor: 44×44pt on Apple surfaces and 48×48dp on Material surfaces. Smaller visible controls need invisible padding to reach that floor. Use ::before with negative inset, or list-item rows whose padding brings the full target above the minimum.
03 Focus visibility WCAG 2.4.7 · 2.4.11
Input · focus
border → --border-hover
+ 1px --border-hover ring (≥3:1, no yellow strobe)
Button · focus
var(--shadow-focus)
soft 3px brand-yellow halo · 35% alpha
Link · focus 2px outline + 3px offset
Same yellow as links themselves
D-pad focus · Generic TV / Roku
Focused row
--component-bg-active + --border-hover · no halo, scale or shadow
tvOS uses system lift; Android TV uses Compose scale + elevation
04 Rules & common mistakes
Use --yellow-solid as text only on dark
On dark surfaces it's 12.1:1 AAA. On light backgrounds it's 1.5:1, invisible. For yellow text on light, use --yellow-text-secondary (5.07: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 low-contrast green/red/info labels
At 12–14px these labels are normal text and need 4.5:1 under WCAG 2.2. Use the default or primary variant, or choose a label/fill pair that clears that ratio.
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.