Skip to content

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 ​

KeyDoes
TabFocus the next focusable component
Shift+TabFocus 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.

Vertical controls are List and an open Select:

KeysMoves the cursor
↓  j  Ctrl+None item forward
↑  k  Ctrl+Pone item back
Home / Endto the first / last enabled item
PageDown / PageUpone viewport
Ctrl+D / Ctrl+Uhalf a viewport

Horizontal controls are Tabs:

KeysMoves the cursor
→  l  Ctrl+None tab forward
←  h  Ctrl+Pone tab back
Home / Endto 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+C and Ctrl+X copy and cut a selection, and bubble when there is none.
  • In the browser, Cmd+C, Cmd+X, and Cmd+V on a Mac, Ctrl+C, Ctrl+X, and Ctrl+V elsewhere, and Shift+Delete off a Mac arrive as Event::Copy, Event::Cut, and Event::Paste, never as keys, so bind those events rather than the keys. Every other Cmd or Super chord is dropped there, so the Mac's Cmd editing chords do nothing in a field.

Each component page lists its editing keys.

Commit and dismiss ​

KeyDoes
Enter  SpacePress a Button, toggle a Checkbox, advance a Cycle, or commit the cursor in List, Select, or Tabs
EnterSubmit an Input
Ctrl+Enter  Ctrl+JSubmit a TextArea, where Enter is a line break
EscRequest 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.