Keyboard Navigation
How @diffgazer/ui's headless-friendly component philosophy works with keyboard handling.
Philosophy
@diffgazer/ui components are headless-friendly primitives. They render the UI layer and expose controlled props/events/ARIA hooks so keyboard behavior can be composed at the app level.
Some components include local keyboard behavior, and some package-mode entries import hooks from @diffgazer/keys. Others expose control points such as highlighted state, selection state, and onKeyDown. Check each component's dependency metadata before choosing copy mode or package mode.
Why this approach
- Simple and predictable -- state and interactions stay explicit
- Declared navigation policy -- built-in keyboard behavior and external hooks are documented per component
- No conflicts -- component behavior doesn't fight app-level shortcuts
- Full control -- you decide which keys do what, in which contexts
- Composes with
@diffgazer/keys-- same primitives work with your own handlers or keys recipes
Without keyboard support
Not every context needs full keyboard navigation. A menu in a toolbar might only need click support, but verify the component's own behavior before removing keyboard handlers:
Items are clickable and selectable. Components with built-in navigation can still respond to keys; components without it need an onKeyDown handler if keyboard navigation is required.
Controlling keyboard state
Menu includes local arrow-key, j/k, Home/End, Enter/Space, and typeahead behavior. Control highlighted only when the app needs to coordinate highlight state outside the menu:
Use onKeyDown only to compose extra app-level shortcuts. Call your handler first and leave the event unprevented when the component's local keyboard behavior should still run.
Keyboard-Aware Components
The following components expose keyboard integration points:
j/k-- vim aliases for ArrowDown/ArrowUp in the list composites that own the whole keyboard:Menu,NavigationList,CheckboxGroup,RadioGroup,DiffView, and an openSelectoption list. Where typeahead is enabled,j/knever start a query -- they move the highlight on an empty buffer and extend a query already in progress, so first-letter jumps to items starting with j or k are unavailable. Composites whose focus lives in a text field (CommandPaletteinput,Select's search input) stay arrows-only so the characters type.- highlight state /
highlighted-- controls which item appears highlighted onKeyDown-- receives keyboard events for app-level handlersselectedId-- the currently selected item; forCommandPalette, usehighlightedbecause command palettes track highlight rather than selectiononSelect-- called when an item is selected (via click or Enter/Space)onEnter-- Diffgazer preview/commit extension that separates Enter activation from Space selection in components that support itonNavigationBoundaryReached-- lets an app hand focus to an adjacent zone when non-wrapping navigation hits an edge. The callback receives direction, native event, and key so vertical app zones can ignore horizontal APG arrow keys.autoFocus-- moves focus into a composite component when it becomes the active interaction targetfocused(NavigationList only) -- whether item highlight visuals are shown while the list is active
Accessibility
Components ship with proper ARIA attributes regardless of whether you add keyboard handling. Roles, labels, and states are built in.
For components without built-in keyboard behavior, compose onKeyDown or @diffgazer/keys hooks so users can navigate and activate items without a mouse.