Focus Management

Keyboard UX

Focus Management Patterns

Use these patterns to keep keyboard and assistive-tech navigation predictable across modals, popovers, and long content pages.

Focus Rules

Apply these rules to all interactive flows.

  • Keep a visible focus ring on every interactive control.
  • Restore focus to the trigger after closing modal/sheet/popover.
  • Do not trap keyboard users in non-modal surfaces.
  • Add a skip link for large docs and dashboard pages.

Manual Test Pass

Quick QA flow before merge or release.

Press `Tab` through the full page and verify visual ring continuity.

Open and close each dialog/sheet; ensure focus returns to the initiating control.

Check that `Esc` closes expected overlays and leaves focus in a valid place.

Modal Focus Restore Pattern

TSX
1import { useRef } from "react"
2import {
3 Button,
4 Modal,
5 ModalContent,
6 ModalHeader,
7 ModalTitle,
8 ModalDescription,
9 ModalTrigger
10} from "@glinui/ui"
11
12export function DeleteDialog() {
13 const triggerRef = useRef<HTMLButtonElement | null>(null)
14
15 return (
16 <Modal>
17 <ModalTrigger asChild>
18 <Button ref={triggerRef} variant="destructive">
19 Delete project
20 </Button>
21 </ModalTrigger>
22 <ModalContent
23 onCloseAutoFocus={(event) => {
24 event.preventDefault()
25 triggerRef.current?.focus()
26 }}
27 >
28 <ModalHeader>
29 <ModalTitle>Delete project</ModalTitle>
30 <ModalDescription>This action cannot be undone.</ModalDescription>
31 </ModalHeader>
32 </ModalContent>
33 </Modal>
34 )
35}

Skip Link Pattern

TSX
1<a
2 href="#main-content"
3 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"
4>
5 Skip to content
6</a>

Related Guides