Interaction states
Hover, focus, pressed, disabled, loading and selected.
Every control's states
These states look the same across every component. A new component uses the same treatment, not its own.
| State | What it looks like | How |
|---|---|---|
| Hover | Fills darken a step; quiet controls gain a light fill; links underline. | -hover tokens, --surface-tertiary, 150ms color transition |
| Focus (keyboard) | A 2px ring with a 2px gap, blue (red on danger controls). Mouse clicks draw nothing. | focus-visible:ring-2 ring-offset-2 |
| Pressed | Shrinks to 98% while held. | active:scale-[0.98] |
| Active / on | A filled background on a toggle or chip; the label stays readable. | --surface-tertiary with --text-primary, aria-pressed |
| Disabled | 30% opacity, no pointer. Say why nearby when you can. | disabled:opacity-30 |
| Loading (action running) | A spinner in the control; it can't be pressed again. | loading prop, aria-busy |
| Not ready (data loading) | A skeleton of the same size and shape. | ButtonSkeleton, row and cell skeletons |
| Error | Red border or text plus a message that says what to do. | --error, --danger-text, aria-invalid |
Loading, empty and error
A card never disappears because its data is missing. Loading, failed and empty are three bodies inside the same card frame (icon, title, hint stay put), and a failure offers Retry. Content that arrives late never pushes other content down: its space is reserved from the first paint by a skeleton of the same height. The full pattern is on Loading, empty and error. A selected row is tinted (see Selection and bulk actions).
Rules
The frame stays
A card never disappears because its data is missing. Loading, empty and failed are three bodies inside the same frame (icon, title and hint stay put), and a failure offers Try again. Returning null on error deletes the feature from the page: the person can't tell broken from empty.
Focus always shows
Every control shows a visible ring when reached by keyboard (focus-visible). Never remove an outline without drawing a ring in its place.
Known gaps
Where Bridge OS code differs from this page today. Follow the page, not the gap.
- Bridge OS still has about 300 hand-made buttons, each with its own hover and disabled look, until they move to Button.
Sources
- Bridge OS
components.md→ Button (states,loadingvsButtonSkeleton), FilterChip, ConfirmDialog (busy). - Bridge OS
DESIGN.md→ principles 6 and 7 (cards keep their frame; no layout shift). - Package
button.tsx(the states as built).