Any custom interactive widget you build must support the keyboard, not just mouse/touch. This lesson covers handling keydown events and the common keyboard interaction patterns expected by users and screen readers alike.
Listening for Specific Keys
Keyboard handling in Lit uses the same @keydown=${handler} binding as any other event, reading event.key to identify which key was pressed. The modern event.key property gives a readable string ('Enter', 'Escape', 'ArrowDown') rather than the older, less readable numeric keyCode.
For any custom widget that isn't a native interactive element (a custom dropdown, a custom tab list), you're responsible for replicating the exact keyboard behavior users and assistive technology expect from that widget pattern — this is a core part of making a custom component genuinely accessible, not an optional extra.
Escape closes the menu and returns focus to the trigger button; Enter/Space toggle it — exactly the behavior users expect from a native-feeling dropdown.
Arrow keys move between tabs, Home/End jump to first/last
Modal dialog
Escape closes, Tab is trapped within the dialog while open
Combobox/autocomplete
Arrow keys navigate suggestions, Enter selects, Escape closes
Roving tabindex for Composite Widgets
For a composite widget with multiple focusable items (like a tab list or a custom listbox), the standard accessible pattern is 'roving tabindex': only the currently active item has tabindex="0", and every other item has tabindex="-1", so pressing Tab moves focus in and out of the whole widget as one stop, while arrow keys move focus between items inside it.
Only one tab is ever reachable via a plain Tab key press at a time; arrow keys handle movement within the group itself.
Trapping Focus Inside an Open Dialog
When a modal dialog is open, Tab/Shift+Tab should cycle only through focusable elements inside the dialog, not escape to the rest of the page. The native <dialog> element with .showModal() handles this automatically and is strongly preferred over hand-rolled focus trapping whenever it fits your design.
Common Mistakes
Building a custom interactive widget that only responds to mouse clicks, with no keyboard support at all.
Checking the deprecated event.keyCode instead of the modern, more readable event.key.
Forgetting event.preventDefault() for Space on custom buttons, causing an unwanted page scroll.
Implementing a composite widget without roving tabindex, forcing keyboard users to Tab through every single item individually.
Key Takeaways
Keyboard handling uses the same @keydown binding, checking event.key for the specific key pressed.
Custom, non-native interactive widgets must replicate the exact keyboard behavior users expect for that widget pattern.
Roving tabindex is the standard pattern for composite widgets with multiple internally navigable items.
Prefer the native <dialog> element's built-in focus trapping over hand-rolled solutions whenever possible.
Pro Tip
Before building a custom interactive widget from scratch, check the WAI-ARIA Authoring Practices Guide for that exact widget pattern (tabs, listbox, combobox, dialog) — it documents the precise expected keyboard behavior, so you're implementing an established standard rather than guessing.
You've completed the forms and accessibility series. Next, learn how Lit integrates with TypeScript in depth.