Keyboard
Tab moves between independent controls. Arrows and their Vim aliases move within a control that has items or values to navigate. A panel containing several buttons or checkboxes does not turn them into a list.
The map
Traversal between components
| Key | Does |
|---|---|
Tab | Focus the next focusable component |
Shift+Tab | Focus the previous one |
Traversal belongs to the runtime, not to components. Tab wrapping is set per scope (see Focus, hover, and identity), and apps add their own jumps with focus_key.
For example, in the showcase's Agent settings, Tab chooses a setting and Left/Right or h/l changes its value. In Themes and Notifications, Up/Down or k/j moves among the rows of one List. Buttons and individual checkboxes use Tab/Shift+Tab, not arrows or Vim letters, to move focus.
Navigation inside a component
Vertical controls are List and an open Select:
| Keys | Moves the cursor |
|---|---|
↓ j Ctrl+N | one item forward |
↑ k Ctrl+P | one item back |
Home / End | to the first / last enabled item |
PageDown / PageUp | one viewport |
Ctrl+D / Ctrl+U | half a viewport |
Horizontal controls are Tabs:
| Keys | Moves the cursor |
|---|---|
→ l Ctrl+N | one tab forward |
← h Ctrl+P | one tab back |
Home / End | to the first / last enabled tab |
Cycle uses the same horizontal step keys to change its value, but wraps at both ends. It has no Home/End or paging behavior.
List, Select, and Tabs skip disabled items and clamp at the ends. They consume navigation keys even at a boundary: pressing j at the last List item does not move to the next component. If a List has no item cursor yet, its first navigation key establishes one on the first enabled item.
A closed Select opens on Enter/Space or a vertical step key (Up/Down, k/j, Ctrl+P/N). While open, the first Tab or Shift+Tab closes its panel without moving component focus; the next traversal key moves focus normally.
Scrolling content
ScrollArea uses PageUp/PageDown to scroll one viewport and Home/End to reach the bounds, without changing component focus. Descendants receive these keys first, so a focused List pages its own items. ScrollArea does not claim arrows, Vim letters, or Ctrl navigation chords. Unlike item navigation, a scrolling key that cannot change the offset bubbles to the app. Tab traversal reveals a focused descendant that was offscreen.
Text fields
Input and TextArea route keys by binding, not by effect. A key their editor binds belongs to the field even where it changes nothing (← at the start of the text, Ctrl+K at its end), and a chord the editor does not bind bubbles, so app hotkeys reach your app wherever the cursor is.
- Every unmodified character is text:
j,h, and Space are characters. - Tab, Shift+Tab, Esc, and the function keys pass through, so traversal and dialogs keep working around a focused field. Input also leaves the vertical keys and the editor's chords for them.
- In a terminal,
Ctrl+CandCtrl+Xcopy and cut a selection, and bubble when there is none. - In the browser,
Cmd+C,Cmd+X, andCmd+Von a Mac,Ctrl+C,Ctrl+X, andCtrl+Velsewhere, andShift+Deleteoff a Mac arrive asEvent::Copy,Event::Cut, andEvent::Paste, never as keys, so bind those events rather than the keys. Every otherCmdorSuperchord is dropped there, so the Mac'sCmdediting chords do nothing in a field.
Each component page lists its editing keys.
Commit and dismiss
| Key | Does |
|---|---|
Enter Space | Press a Button, toggle a Checkbox, advance a Cycle, or commit the cursor in List, Select, or Tabs |
Enter | Submit an Input |
Ctrl+Enter Ctrl+J | Submit a TextArea, where Enter is a line break |
Esc | Request dismissal of a Dialog, an open Select, or a Tooltip with an open-change binding |
Dialog's dismiss key is rebindable with dismiss_key, which takes any KeyChord. Chord matching ignores Shift, so its default also accepts Shift+Esc; Ctrl/Alt must match. Select and Tooltip require plain Esc. A read-only Tooltip (open_when) leaves Esc to its ancestors or the app. Components require their action bindings to emit these changes; disabled controls do not activate.
The browser previews on this site also release keyboard capture on Esc. That is the preview host's behavior, separate from component dismissal.
The rules behind the map
Three rules decide whether a component claims a key at all. They matter because they are what keeps your app's own hotkeys working.
Components claim their documented keys. Ctrl+S is not a List key and can reach your save handler. Most component keys are unmodified; the Ctrl navigation chords in the tables above are the exceptions, and horizontal controls do not claim Ctrl+U/D. Dialog dismissal and app focus shortcuts follow their configured KeyChord.
Shift is not item navigation. J is not j; Shift-modified item navigation is left unclaimed. Shift+Tab still traverses backward between controls.
An unhandled key bubbles. A key a component does not recognize is reported as ignored, and travels up to its ancestors and then to your app. So a single-letter hotkey can keep working while a List has focus, except for its vertical navigation letters (j, k). Horizontal controls take h and l instead. Modal scopes prevent unhandled keys from reaching the underlying app.
An ancestor hotkey does not override a key consumed by its child. If your app deliberately needs to override j, check it before calling Ratcn::handle_event (see Host integration). Do not globally translate Vim letters into traversal keys: a custom text editor needs to receive its own text and editing keys first.
Keys outside the map
Typing a letter does not jump to a matching item: there is no typeahead.
A backend key this vocabulary has no place for, such as a key release or a function key beyond F(u8), does not convert into an Event and is ignored. See KeyCode for the full list of what is representable.