FSD Frontend System Design

Part IV β€” The Reach Β· Lesson 8.2

Keyboard Accessibility

In one lineEverything clickable must be reachable by Tab, activatable by Enter or Space, and visibly focused.

Where you've seen itA dialog that opens fine, then leaves Tab cycling through the page behind it while Escape does nothing.

  • Time16 min
  • DiagramTab order path over a layout + the key contract per widget
  • Chapter8 Β· Accessibility

Diagram

diagram
TAB ORDER FOLLOWS THE DOM, NOT THE CSS

  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
  β”‚ [1 Skip to content]                         β”‚
  β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”   β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”    β”‚
  β”‚ β”‚ [2 Logo] β”‚   β”‚ [3 Search] [4 Sign in]β”‚    β”‚
  β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜   β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜    β”‚
  β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”‚
  β”‚ β”‚ main                β”‚  β”‚ sidebar       β”‚  β”‚
  β”‚ β”‚  [5 Filter]         β”‚  β”‚  [7 Related]  β”‚  β”‚
  β”‚ β”‚  [6 Buy now]        β”‚  β”‚               β”‚  β”‚
  β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β”‚
  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

  If CSS (order, flex-direction: row-reverse, grid placement)
  moves things visually, the tab order no longer matches what
  the user sees. Fix the DOM, not the tabindex.
diagram
THE KEY CONTRACT β€” what users expect, per widget

  button          Enter, Space          activate
  link            Enter                 navigate
  checkbox        Space                 toggle
  radio group     Arrows                move + select (one tab stop)
  select / menu   Arrows, Home, End     move Β· Enter select Β· Esc close
  dialog          Esc                   close, focus returns to trigger
  tabs            Arrows                switch Β· Tab moves OUT of the set
  tree/grid       Arrows                two-dimensional movement

  Build a custom widget and you inherit ALL of its expected keys.
diagram
tabindex β€” three values, two of them fine

   0   in the natural tab order         βœ“ custom interactive elements
  -1   focusable by script only         βœ“ dialogs, error summaries,
                                          roving-tabindex items
  >0   jumps the queue                  βœ— never β€” breaks the whole page

How it works

  1. Use <button> and <a href>. They're focusable, activatable, and announced correctly with zero code.
  2. If you must use a div, you owe it role, tabindex="0", and both keydown handlers β€” Enter and Space for buttons.
  3. Never remove focus outlines. Restyle them instead; :focus-visible shows a ring for keyboard users without showing it on mouse clicks.
  4. Add a skip link as the first focusable element so keyboard users can bypass the nav.
  5. Use roving tabindex for composite widgets (toolbars, radio groups, tabs): one tab stop for the group, arrows to move within it.
css
/* keep the ring for keyboard users; hide it for mouse clicks */
:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; }

Trade-offs

DoDon't
<button onClick><div onClick>
:focus-visible restyleoutline: none
Roving tabindex for groups12 tab stops in a toolbar
DOM order = visual ordertabindex="5" to patch order

Interview angle

"How do you test keyboard accessibility?"

  • Put the mouse away and Tab through the whole flow: everything interactive must be reachable, activatable, and visibly focused.
  • Check the order matches the visual layout, since CSS can reorder without changing the DOM.
  • For each custom widget, verify the expected keys β€” Escape on dialogs and arrows on composite widgets are the usual gaps.

Recap

  • Reachable, activatable, visible β€” the three keyboard requirements.
  • Tab order comes from the DOM; positive tabindex is always the wrong fix.
  • Custom widgets inherit the full key contract of the thing they imitate.