Skip to main content

Focus management patterns

Focus management patterns define how keyboard navigation and focus behavior work across components. These patterns ensure accessible, predictable interactions for all users, including those who rely on keyboard navigation.

Component patterns

Modal dialog


  Confirm Action
  
    

Are you sure?

Behavior:

  • Focus traps within dialog
  • Focus moves to first interactive element on open
  • Escape closes dialog
  • Focus returns to trigger element on close

Common mistakes:

  • Not trapping focus, allowing Tab to exit modal
  • Not restoring focus to trigger on close
  • Missing aria-modal="true"

Alert dialog (critical)

For destructive or critical confirmations where Escape should NOT close.


  Delete Item?
  

This action cannot be undone.

Key difference: Focus the LEAST destructive option (Cancel) by default.

Dropdown menu


  {items.map((item, index) => (
    
      {item.label}
    
  ))}

Keyboard navigation:

  • ↑/↓: move between items (wraps at boundaries)
  • Enter / Space: activate item
  • Escape: close menu
  • Tab: exit menu (does NOT navigate items)
  • Home / End: jump to first/last item
  • Type character: jump to item starting with that letter

Tabs

{tabs.map((tab, index) => ( ))}
{tabs.map((tab, index) => ( ))}

Keyboard navigation:

  • ←/→: move between tabs (wraps)
  • Usually automatic activation (arrow changes tab)
  • Tab key: exit tablist to panel content

Toolbar

{tools.map((tool, index) => ( ))}

Key behavior: Toolbar is ONE tab stop. Use arrow keys to move between items.

Combobox / select

{isOpen && (
    {options.map((option) => (
  • {option.label}
  • ))}
)}

Key difference: Arrow keys do NOT wrap: stop at first/last option.

Skip links


  Skip to main content


{/* Later in the page */}
{/* Content */}

Requirements:

  • Must be first focusable element on page
  • Hidden until focused
  • Target needs tabIndex="-1" to receive programmatic focus

Focus rules

Visibility rules

RuleRequirementWCAG
Visible focus indicatorAll interactive elements must show focus2.4.7 (AA)
Use focus-visibleShow focus only on keyboard nav, not clicksBest practice
Focus contrastFocus indicator must have 3:1 contrast2.4.11 (AAA)
// Good — visible focus
className="focus:ring-2 focus:ring-offset-2"
className="focus-visible:ring-2 focus-visible:ring-offset-2"

// Bad — hidden focus
className="focus:outline-none"  // Without replacement

Order rules

RuleRequirementWCAG
Logical tab orderMust follow visual / reading order2.4.3 (A)
No positive tabindexNever use tabindex="1", "2", etc.2.4.3 (A)
Skip links firstSkip links must be first focusable2.4.1 (A)
// Good
tabIndex="0"   // Focusable in natural order
tabIndex="-1"  // Programmatic focus only

// Bad
tabIndex="5"   // Creates unpredictable order
tabIndex="99"  // Maintenance nightmare

Focus trap rules

RuleRequirementWCAG
Modal focus trapFocus must cycle within modal2.4.3 (A)
Focus returnReturn focus to trigger on close2.4.3 (A)
Escape closesEscape should close overlays (except critical)Best practice

Landmark regions

Standard landmark regions for skip link navigation:

NameRoleHTML ElementSkip Link
mainmain<main>"Skip to main content"
navigationnavigation<nav>"Skip to navigation"
searchsearch<search>"Skip to search"
bannerbanner<header>:
contentinfocontentinfo<footer>:

MCP tool usage

Get focus pattern

// Get patterns for modal components
get_focus_pattern({ componentType: 'modal' })

// Get specific pattern
get_focus_pattern({ name: 'alert-dialog' })

// Include rules and landmarks
get_focus_pattern({
  componentType: 'modal',
  includeRules: true,
  includeLandmarks: true
})

Validate focus management

// Basic validation
validate_focus_management({
  code: '...'
})

// With component type context
validate_focus_management({
  code: '',
  componentType: 'dropdown'
})

// Strict mode (warnings become errors)
validate_focus_management({
  code: '...',
  strict: true
})

Validation checks

IssueSeverityFix
Missing focus indicatorErrorAdd focus:ring-2 or equivalent
Positive tabindexErrorUse tabindex="0" or -1
Modal without focus trapErrorUse focus trap library + aria-modal
Dialog missing aria-modalWarningAdd aria-modal="true"
Tab missing aria-selectedErrorAdd aria-selected attribute
Click without keyboardWarningAdd role="button" + keyboard handler
autoFocus usageWarningEnsure intentional and appropriate

Quick reference

Keyboard navigation by component

ComponentNavigationWrap?Activation
ModalTabTrapEnter / Space
MenuArrowYesEnter / Space
TabsArrowYesAuto or Enter
ToolbarArrowYesEnter / Space
ComboboxArrowNoEnter
TreeArrowNoEnter

Required ARIA by component

ComponentRequired attributes
Modalrole="dialog", aria-modal, aria-labelledby
Alert Dialogrole="alertdialog", aria-modal, aria-labelledby, aria-describedby
Menurole="menu", aria-labelledby
Menu Itemrole="menuitem"
Tabsrole="tablist", aria-orientation
Tabrole="tab", aria-selected, aria-controls
Tab Panelrole="tabpanel", aria-labelledby
Comboboxrole="combobox", aria-haspopup, aria-expanded, aria-controls

Decision guide

Building interactive component?
├── Is it an overlay?
│   ├── Modal / blocking? → Use focus trap + aria-modal
│   └── Non-modal? → No trap, but restore focus on close
├── Is it a list of options?
│   ├── Actions? → role="menu" + arrow keys + wrap
│   ├── Selection? → role="listbox" + arrow keys + no wrap
│   └── Navigation? → role="tablist" + arrow keys + wrap
├── Is it a button group?
│   └── Toolbar? → role="toolbar" + arrow keys + one tab stop
└── Is it page-level?
    └── Add skip links (first focusable element)

Was this page helpful?

Last reviewed by @jschuyler
All systems operationalDocsDevelopersPlatformPricing
PrivacyTerms© 2026 fndd, LLC