Focus

A terminal has no hover and no pointer to fall back on, so what has focus is the whole of what the reader can act on.

export function Restart() {
  const focus = useFocus({ autoFocus: true });

  useInput((event) => {
    if (event.name === 'enter') { activate(); return true; }
    return false;
  }, { focusId: focus.id });

  return <box id={focus.id} role="button" bold={focus.focused}>Restart</box>;
}

Or without a hook, on any node:

<box focusable id="cell-a1" onKey={(e) => {  }} />

Order and direction

Tab order is registration order within a scope, unless a node states order. Directional navigation is geometric, because “the thing to the right” is a real question a table, a menu and a dashboard all ask, and none can answer it from document order. Distance along the requested axis is weighted more heavily than drift across it, so right from a table cell finds the next column rather than a distant button.

Scopes and traps

useFocusScope({ trap: true, restore: true, autoFocus: true });

A modal traps: tab cannot leave while it is active. restore puts focus back where it was when the scope deactivates, which is what makes closing a dialog feel like nothing happened.

Scopes are inherited. A control inside a dialog registers in the dialog’s scope without being told about it - useFocus reads the nearest enclosing scope, and so does a focusable prop. This matters more than it sounds: a trap filters the tab order down to its own scope, so a control filed in the global one is not merely out of order, it is unreachable, and the dialog looks broken in a way that nothing reports. Pass scopeId explicitly only to override.

autoFocus claims focus; it does not steal it. A control marked autoFocus takes focus only if nothing in its scope has it yet. A prompt dialog is the case that forces the rule: it has an auto-focused text field and a default button, and whichever mounted last would otherwise win - which is how the dialog that exists to ask for text ends up with the text field unfocused.


Back to top

MIT licensed. Pre-1.0 - the surface is still moving.