Tier 0Foundations · what focus must communicate, binding all four tiers. The ring drawn here is the web binding: iOS uses the system focus treatment, Android M3 state layers, tvOS and Android TV the TV fill, Roku a SceneGraph focus-bitmap fill.--border-hover · --radius-lg · --component-bg-active · --shadow-focus
Focus system, three contexts, two answers
put.io has three focus contexts: keyboard (web), pointer (web, no ring), D-pad (TV). Web keyboard focus is an opaque neutral double ring.
TV is different: every TV app fills the surface with --component-bg-active and steps the border to --border-hover: no halo, no scale, no shadow.
Keyboard · web
Tab / arrows over buttons, inputs, links
A 2px --bg gap and a 2px --solid-hover ring around any button, at least 3:1 on page and panels. Sits outside the border, never replaces it. Triggered by :focus-visible, not :focus.
Pointer · web
Mouse hover & click
No halo. Hover lifts background to --yellow-solid-hover on primary, or --component-bg-hover on default. Cursor carries the intent.
D-pad · TV
Roku · web TV · tvOS · Android TV
tile
focused
A fill, not a lift: --component-bg-active + --border-hover. No scale, no halo, no shadow, matching list-item.tsx / action-button.tsx. A focused primary button keeps its yellow fill. Roku draws it with a SceneGraph focus bitmap.
tvOS and Android TV ship one React Native app with the system lift off.
Spec, values lifted from system/tokens.css
Ring color--shadow-focus-colorTranslucent brand yellow, hsla(47, 100%, 65%, 0.35). Kept only as the shadcn --ring color; no put.io focus state draws it, since it falls under 3:1 on the light page.
Ring · button (web)--shadow-focusA 2px --bg gap, then a 2px --solid-hover ring via box-shadow: at least 3:1 against the page and panels in both modes. Border stays unchanged.
Ring · input (web)--field-ringQuiet, not yellow: border shifts to step-10 --solid-hover (--field-border-focus) + a 1px ring of the same color, ≥3:1 against the fill, the page, and panels; hover stops at step-8 --border-hover. Error/success states swap to red/green.
Focus · TV--component-bg-active10-foot focus is a surface fill, plus --border-hover on anything that already has a border. Yellow appears only on a focused primary button, which keeps its fill.
Trigger:focus-visibleNever :focus: that paints rings on clicks users don't want.