Keyboard UX
Focus Management Patterns
Keep keyboard and assistive technology navigation predictable across modals, popovers, and long pages.
Focus rules
Check these on every interactive flow.
Keyboard reference
What users expect from common widgets.
| Keys | Expected behavior | Where |
|---|---|---|
| Tab | Move focus to the next interactive element | Everywhere |
| ShiftTab | Move focus to the previous element | Everywhere |
| Esc | Close the overlay and return focus to its trigger | Dialog, sheet, popover |
| Enter | Activate the focused button or link | Buttons, links |
| Space | Activate a button or toggle a checkbox or switch | Buttons, form controls |
| Arrow keys | Move between items | Menus, tabs, radio groups |
Do and don't
Do
Use :focus-visible styles so keyboard users see the ring but mouse clicks do not flash it.
Send focus to the dialog title or first field when a modal opens.
Don't
Remove the outline without a replacement.
button:focus { outline: none; }Use positive tabindex values to reorder focus. Fix the DOM order instead.
Modal Focus Restore Pattern
The modal traps focus while open. This pattern returns focus to the trigger on close.
import { useRef } from "react"import { Button, Modal, ModalContent, ModalHeader, ModalTitle, ModalDescription, ModalTrigger} from "@glinui/ui"
export function DeleteDialog() { const triggerRef = useRef<HTMLButtonElement | null>(null)
return ( <Modal> <ModalTrigger asChild> <Button ref={triggerRef} variant="destructive"> Delete project </Button> </ModalTrigger> <ModalContent onCloseAutoFocus={(event) => { event.preventDefault() triggerRef.current?.focus() }} > <ModalHeader> <ModalTitle>Delete project</ModalTitle> <ModalDescription>This action cannot be undone.</ModalDescription> </ModalHeader> </ModalContent> </Modal> )}Skip Link Pattern
Let keyboard users jump past repeated navigation.
<a href="#main-content" className="sr-only focus:not-sr-only focus:fixed focus:left-4 focus:top-4 focus:z-50 focus:rounded-md focus:bg-background focus:px-3 focus:py-2"> Skip to content</a>