Show focus rings for keyboard navigation only #217
- MA
Shows the petrol focus ring for keyboard navigation only. Before this change, buttons, menus and options kept a thick ring after a mouse click, because browsers still match `:focus-visible` when a script moves focus, for example into a menu.
## Change - New `useFocusInput()` in `apps/web/src/focus-input.ts` sets `data-focus-input` on `<html>`: - Any key press sets it to `"keyboard"`, including chords such as VoiceOver quick-nav, Alt+ArrowDown and Ctrl+Arrow. A bare Shift, Control, Alt, Meta, CapsLock or Fn press does not change it. - A pointer press sets it to `"pointer"`. - Hovering does not change it, so a keyboard user whose mouse drifts keeps their ring. This is where it differs from the motion input. - In `theme.css`, while the mode is `pointer`, buttons, selects, summaries, `[role=button|listbox|option]` and `[tabindex]` drop the ring. Text fields keep theirs, because a caret alone is easy to lose. - The hook is called from `Root` and from the dev-only design audit page. - `DESIGN.md` now describes the ring as keyboard-only.
## Verification - `bun test`, `bun run types` and `bun run ci` pass. - The new `e2e/focus-ring.e2e.ts` covers: - a click hides the ring - Tab shows it - a bare modifier press keeps the pointer mode - Alt+ArrowDown switches to keyboard mode - a clicked text field keeps its ring - The new spec plus `e2e/shell.e2e.ts` and `e2e/focus.e2e.ts` pass: 16 of 16 on Chromium. - A code review found two issues, both fixed in `0003b1db`: - Key chords were ignored, so keyboard users navigating with them could lose their ring. - Two e2e tests clicked a button that opens a dialog, which moves focus away from the button being tested.
## Screenshots
The design audit has no interactive menu, so these use a small fixture built from the real `btn btn-ghost` and dropdown classes. It reproduces the pattern that leaves a stuck ring: a mouse click opens a menu and a script then moves focus into it. Captured at 2x on `main` and on this branch.
| Step | Before (`main`) | After (this PR) | | --- | --- | --- | | Menu opened by mouse |  |  | | Option chosen by mouse |  |  | | Tab to the control |  |  |
Before, the menu panel and then the trigger keep the thick petrol ring after mouse use. After, they stay clean, and the ring comes back as soon as a key is pressed.
馃 Generated with [Claude Code](https://claude.com/claude-code)
MaggieAppletonhead-ref-force-pushed 82da27eee3c0last month on GitHub
MaggieAppletonmerged 53488b54e010last month on GitHub
MaggieAppletonhead-ref-deletedlast month on GitHub