Accessibility
Keyboard access and correct semantics are requirements here, not extras.
One tab stop, not one per row
The list uses a roving tabindex: the whole list is a single tab stop, and the arrow keys move within it. Tabbing through five hundred rows is the classic accessibility failure of a hand-rolled file browser.
The keyboard contract
| Key | Action |
|---|---|
↑ ↓ | Move between rows (and ← → in grid view) |
Home / End | First / last item |
PageUp / PageDown | Move by a page |
Enter | Open the focused item |
Space | Toggle selection |
Shift + arrow, Shift + click | Extend the selection as a range |
Ctrl / Cmd + A | Select everything visible |
Ctrl / Cmd + click | Toggle a single row |
Backspace | Go up a folder |
Escape | Clear the selection |
| any letter | Jump to the next name starting with it |
Type-ahead accumulates: typing d, a quickly jumps to date.pdf, while pausing starts a fresh search. Repeating a letter cycles through matches.
This mapping lives in createKeyboardNavigator() in @document-explorer/core and is shared by every renderer — the shortcuts cannot drift between React and Angular, because there is only one implementation of them.
Resizing without a mouse
Each column's resize handle is a focusable role="separator". ← and → resize it by 16px, and Enter or Home resets it — so column widths are adjustable without a pointer, which a drag-only implementation would not be.
Roles
| View | Semantics |
|---|---|
| List | role="grid" with row / columnheader / gridcell, and aria-sort on the sorted column |
| Grid | role="listbox" with options — a flat set of selectable cards is a listbox, and option is what carries aria-selected |
| Tree | role="tree" with treeitems carrying aria-level and aria-expanded |
Checkboxes sit inside a gridcell, never as bare children of a row. The grid's check indicator is decorative, because an option may not contain interactive descendants.
Announcements
A single live region reports the current folder and item count after navigation, and the result count after a search — so a screen-reader user hears what changed without focus being moved around.
Reduced motion
The already-minimal transitions are zeroed under prefers-reduced-motion: reduce.
Verification
Every view — list, grid, tree, plus the empty and error states — is checked with axe in the test suite, alongside manual keyboard-only passes. Automated checks catch roughly a third of real accessibility problems, so they run alongside manual testing rather than instead of it.