Foundations

Focus and keyboard

The focus ring, the pointer and keyboard modality switch, and the keyboard model from Base UI

The ring

Every focusable primitive draws the same ring on :focus-visible. @nyte-ai/ui/styles.css restates the rule globally, so ad-hoc focusables in an app land on the same ring.

:focus-visible {
  outline: var(--nyte-control-focus-width) solid var(--nyte-color-focus-ring);
  outline-offset: var(--nyte-control-focus-offset);
}

Input and Textarea use a 2px box-shadow in --nyte-color-focus-ring plus a --nyte-color-ring border instead of an outline, so the ring hugs the field's radius.

Focus modality

Chromium matches :focus-visible on every text field focus, pointer clicks included, so the ring color is the gate. The host marks the document with data-nyte-focus-modality="pointer" when the last focus move came from a pointer and "keyboard" when it came from a key. @nyte-ai/ui/styles.css sets --nyte-color-focus-ring: transparent while the attribute reads pointer.

Start the document at pointer, so a window that autofocuses a field does not open ringed. Inside a text field, count only Tab as a keyboard move, since letters and Cmd+A are edits. Elsewhere, count any non-modifier key. Listen in the capture phase, so the modality is settled before a handler moves focus.

A host that skips this gets a ring on click as well as on Tab, which is fine. Hiding the ring on keyboard focus is not.

Keyboard model

Base UI owns keyboard behavior and the primitives do not intercept it.

ComponentKeys
ButtonEnter and Space activate. A render-replaced non-button needs nativeButton={false} to keep this.
Dialog, Alert dialogFocus moves into the popup on open and returns to the trigger on close. Tab is trapped inside. Escape closes a Dialog. Alert dialog ignores outside press and Escape unless its dismissal props say otherwise.
Dropdown menuArrow Down and Up move the highlight, Home and End jump, type-ahead matches item text, Enter and Space activate, Escape closes, Arrow Right opens a submenu and Arrow Left closes it.
InputNative.

The full key tables are on the component pages, reproduced from Base UI.

Disabled

A disabled control drops to --nyte-control-disabled-opacity (0.56) and, for Button, sets pointer-events: none. When a button enters a loading state after activation, pass focusableWhenDisabled so focus stays on it. Otherwise focus falls to <body> and a screen reader loses its place.