Core Concepts
Keyboard patterns
A detailed walkthrough of Roving Tabindex, Spatial Navigation, and per-component keyboard interactions.
reopt designUpdated
Roving Tabindex in detail
Roving Tabindex is a pattern that keeps a single Tab Stop inside a composite widget while letting arrow keys navigate between items. The Composite layer manages this automatically.
How it works
- All items in the container start with
tabindex="-1". - Only the currently active item is set to
tabindex="0". - Pressing Tab to enter the container focuses the active item.
- When you move with arrow keys, the previous item becomes
tabindex="-1"and the new item becomestabindex="0". - Pressing Tab to leave the container moves to the next Tab Stop.
tsx
// CompositeZone은 built-in roving tabindex를 활용합니다
import { CompositeZone, CompositeRow, CompositeItem } from "@reopt-ai/opt-ui";
<CompositeZone
aria-label="대시보드 그리드"
focusLoop // 마지막 항목에서 첫 항목으로 순환
focusWrap // 행 끝에서 다음 행으로 자동 이동
>
<CompositeRow>
<CompositeItem>카드 1</CompositeItem>
<CompositeItem>카드 2</CompositeItem>
</CompositeRow>
<CompositeRow>
<CompositeItem>카드 3</CompositeItem>
<CompositeItem>카드 4</CompositeItem>
</CompositeRow>
</CompositeZone>focusLoop vs focusWrap
| Prop | Behavior | Use case |
|---|---|---|
| focusLoop | Arrow keys wrap from the last item back to the first | Toolbar, TabList, sidebar |
| focusWrap | In a Grid, reaching the end of a row moves to the next row automatically | 2D Grid (DashboardGrid, example lists) |
orientation configuration
Use CompositeZone's orientation prop to control which axis arrow keys respond to.
| orientation | Active keys | Example |
|---|---|---|
| "vertical" | ↑ ↓ | Sidebar, accordion, vertical menu |
| "horizontal" | ← → | Toolbar, TabList, Menubar |
| omitted (both) | ↑ ↓ ← → | Grid (DashboardGrid) |
tsx
// 수직 방향만 반응하는 사이드바
<CompositeZone orientation="vertical" focusLoop>
<CompositeItem>홈</CompositeItem>
<CompositeItem>대시보드</CompositeItem>
<CompositeItem>설정</CompositeItem>
</CompositeZone>
// 수평 방향만 반응하는 탭 목록
<TabList orientation="horizontal">
<Tab>개요</Tab>
<Tab>설정</Tab>
<Tab>로그</Tab>
</TabList>Per-component keyboard interactions
| Component | Keyboard | Notes |
|---|---|---|
| CommandPalette | Cmd+K to open, ↑↓ to navigate items, Enter to run, Esc to close | Type to filter |
| DashboardGrid | Tab to enter, ↑↓←→ for 4-way navigation, Enter to select | focusLoop + focusWrap |
| ContentTabs | ←→ to switch tabs, Tab to enter panel, Home/End | Automatic activation |
| FaqAccordion | ↑↓ to navigate items, Enter/Space to toggle | Vertical Composite |
| StatusSelect | Enter to open, ↑↓ to navigate, Enter to select, Esc to close | Listbox pattern |
| SearchCombobox | Type to filter, ↑↓ to navigate, Enter to select | Input-protected (←→) |
| SettingsForm | Tab to move between fields, Space for checkboxes, ←→ for radios | Standard form elements |
| EnvPanel | ↑↓ to navigate items, Enter/Space to toggle | Disclosure-based |
Keyboard checklist
A keyboard accessibility checklist to run through when building a new component.
- Is the Tab order logical? (matches visual order)
- Does it need an arrow-key navigation context (CompositeZone)?
- Do elements that close with Esc restore focus correctly?
- Is every interaction reachable via Enter/Space?
- Is the focus indicator clear? (data-[focus-visible] styles)
- Have aria-labels been applied to containers that need them?
Global shortcuts
| Shortcut | Action | Implementation |
|---|---|---|
| Cmd+K / Ctrl+K | Toggle the command palette | app/layout.tsx |
| Ctrl+Shift+F | Toggle the opt-devtool overlay | app/layout.tsx |
| ↑ ↓ ← → | Spatial Navigation | spatial-nav.ts (global) |