Design system
Docs
Desktop

Button and focus

components/ui.tsx: Button, IconButton, ToggleIconButton, Hint, StatusDot, Kbd, and the two focus rings

Import
import {
  Button,
  focus,
  formatTimeAgo,
  Hint,
  HintIconButton,
  HintToggleIconButton,
  IconButton,
  Kbd,
  srOnly,
  StatusDot,
  ToggleIconButton,
} from "../components/ui.tsx";

File: packages/desktop/src/renderer/src/components/ui.tsx. "Small shared controls, styled on the palette."

focus

Two StyleX styles, and the only two focus treatments the desktop uses. From the file's comment:

Keyboard focus must be visible to navigate at all: ring for standalone controls, ringInset for rows and menu items that sit flush inside a scroll container or popover, where an outset ring would clip.

t.focusRing — not :focus-visible — is what keeps these off the mouse. Chromium matches :focus-visible on every text field focus, so a plain ring lands on any input the user clicked into; focus-modality.ts drops the color to transparent until focus arrives by keyboard.

StyleOutline
focus.ring2px solid t.focusRing, offset 1px, on :focus-visible
focus.ringInset2px solid t.focusRing, offset −2px, on :focus-visible

Rule: every focusable in the desktop must apply one of these. A control that draws its own outline is a defect. The mechanism that gates the color is in Foundations › Focus.

srOnly

A visually-hidden style (1px clipped box) for text that must be announced but not seen. Used for tool-call state ("Running", "Failed"), live regions, and labels on controls that read by shape.

Button

Wraps @nyte-ai/ui/button with render={<button type={type} {...rest} />}.

PropTypeDefaultDescription
variant"primary" | "secondary" | "ghost" | "danger""secondary"Fill treatment on t.fill*.
iconIconNameLeading 14px glyph from Icons.
typebutton type"button"
disabledbooleanForwarded to Base UI.
…rest<button> props minus className/styleSpread onto the rendered element.

Note the default variant is secondary, the opposite of @nyte-ai/ui's Button. Primary is opt-in because most desktop buttons sit in toolbars and footers.

<Button variant="primary" onClick={save}>Save</Button>
<Button icon="refresh" onClick={refresh}>Refresh GitHub</Button>
<Button variant="danger" disabled={pending}>{pending ? "Deleting…" : "Delete"}</Button>

IconButton

A ghost square button for one glyph.

PropTypeDefaultDescription
iconIconName | ReactElementA named glyph, or a custom element such as PanelToggleIcon.
labelstringRequired. Becomes aria-label and title.
sizenumber15Glyph size.
compactbooleanfalseSmaller box for tab strips and panel headers.

ToggleIconButton

Same as IconButton on @nyte-ai/ui/toggle, so it carries aria-pressed.

PropTypeDescription
pressedbooleanControlled state.
onPressedChange(pressed: boolean) => void

aria-pressed and onClick are removed from the accepted rest props; Base UI Toggle owns them.

Hint, HintIconButton, HintToggleIconButton

Hint is the desktop tooltip on @nyte-ai/ui/tooltip: positionMethod="fixed", sideOffset 6, collisionPadding 8.

PropTypeDefault
contentReactNode
triggerReactElement
sideTooltip.Positioner.Props["side"]"bottom"
alignTooltip.Positioner.Props["align"]"center"

HintIconButton and HintToggleIconButton wrap the two buttons in a Hint whose content defaults to the button's label, and drop the native title so the tooltip is the only hint. Props: the button's props plus hint, hintSide, hintAlign.

Tooltip keyboard and focus behavior is Base UI's: opens on hover after the delay and on keyboard focus, closes on Escape, never traps focus.

StatusDot

A role="img" span with an aria-label for a session's mark.

markLabelDraws
idleIdledot
workingRunningdot + Spinner
waitingNeeds attentiondot
retryRetryingdot + Spinner
failedFaileddot

The switch over SessionMark is exhaustive with a never guard; adding a mark is a type error until the label and style exist.

Kbd

<Kbd keys={["⌘", "K"]} />
<Kbd keys={["⌘", "N"]} plain />

Renders <kbd> with one <span> per key. plain removes the keycap box for use inside menu meta columns. Kbd is display only; the shortcut it shows is bound elsewhere (see the client action table).

formatTimeAgo(timestamp, now?)

"now", Nm, Nh, Nd, then a short date. Used for session rows and palette results.