Interaction states

Guide index · Surfaces · Components

Shared philosophy

Action, selection, keyboard focus, and business status are four independent channels. Blue is the emphasized action or tab. Violet identifies navigation, cyan identifies focus, and neutral surfaces with bold labels identify graphical selection. Data rows add a persistent primary-text ▸ glyph; the terminal uses that glyph for navigation and rows. Status is wording, with a semantic color only as support.

Resolve combined states in this order: disabled prevents activation; busy prevents a duplicate activation; an error supplies its message; focus adds its indicator; selection keeps its marker; hover or press then adjusts an enabled face. None of these removes a required status label. An invalid focused field retains its danger edge and uses the lighter surface.focus fill.

State is never color alone. A check, dot, thumb, marker, or explicit word still has to be present. Focus moves when the person moves it, when an overlay opens, or when a disclosure hides the focused control. Refresh, incoming logs, and status polling do not take focus. Content that is already on screen stays in place, and freshness is labeled separately from record status.

Platform implementation

Desktop and mobile are one graphical system. They share faces, roles, state rules, type families, type sizes and weights, and icons at the same sizes and strokes. Both use icons. Layout, padding, and component target dimensions adapt to available space and touch input; mobile has no hover. Typography and iconography diverge between the graphical system and the terminal, as specified on their own pages. The terminal keeps the same channels and expresses them with one cell size, glyphs, and foreground color.

Desktop

Controls use contoured faces and the shared gradients. Hover and press change the enabled face without moving or resizing it. Keyboard focus recolors the existing contour cyan and does not add a second ring. Use the existing 1px contour at 0 offset, never an outer stroke. When an edge must retain on, checked, or invalid meaning, preserve that semantic edge and use the opaque lighter surface.focus fill (#52677E) for focus instead. Apply this exception consistently to pressed toggles, on switches, checked or indeterminate checkboxes, checked radios, and invalid fields; keep their glyphs and error text visible. Pointer and keyboard are both first-class.

Mobile

Mobile uses the desktop faces, colors, and states. It has no hover, so press is the only transient face. Targets use the 48px touch size. Focus uses the same cyan contour when an external keyboard or switch device is present. Labels stay visible, and nothing depends on a hover tooltip. Text follows the host text scale, as specified in typography.

Terminal

The terminal has no hover, press, gradient, or chamfer. Brackets, ▸, [x], (•), [on], [!], and (disabled) carry the structure that desktop draws with edges and bars. Foreground color keeps the same roles: pale text for a selected action, cyan for focus, violet for navigation, primary text for persistent ▸ selection, and semantic colors for status words. A cell background stays the surface color. Focus recolors only the control label cyan, preserves its value and selection glyph, and adds no prefix or substitute cyan frame. Nerd Font is required and guaranteed; do not replace ▸ with >. Every platform shows one nearby sentence explaining why the control is disabled. Keep that reason identical across platforms and expose it as an accessible description. Only when the sentence will not fit may the terminal append “(disabled)”; never append “(unavailable)”.

StateDesktop and mobileTerminal
FocusExisting 1px cyan contour at 0 offset; semantic-edge exceptions use surface.focusCyan control label; unchanged value and persistent selection glyph; no prefix or cyan frame
Selected navigationNeutral selection face, violet leading edge, and bold violet label▸ in primary text in a fixed one-cell slot; violet navigation label
Selected dataPersistent ▸ glyph and neutral selection face▸ in primary text in a fixed one-cell slot
Selected action or tabControl face, blue edge or top markerPale bold label with | Label \ endcaps; actions use brackets
HoverDesktop hover face. Mobile has no hoverNone
PressPressed face. Mobile uses this as its transient faceNone
DisabledFlat face, readable muted label, nearby reasonReadable label plus a nearby reason; (disabled) only when the sentence will not fit
BusyEnabled face, reserved width, operation-specific replacement labelOperation-specific replacement label, supplemented by the quadrant-block spinner
CheckboxEmpty, pale check, or dash on the dark face[ ] [x] [-]
RadioEmpty, or a centered pale dot( ) (•)
Switch32×20 track and 16px pale thumb, inside the full target[off] [on]
InvalidDanger edge plus the associated messageDanger cue plus [!] and the message
Invalid and focusedDanger edge and cyan focus togetherDanger cue and cyan focus together

Independent state channels

Each row below is one step of the precedence order: the channel by itself, then the combinations that have to stay readable together. Desktop and mobile use the same faces. Mobile omits hover and uses the 48px target. The terminal keeps the same order and draws it with glyphs and foreground color.

Disabled

Change the draft before saving.

Busy

Enabled face. The operation-specific label replaces the idle label, and the indicator has a reserved slot.

Busy and disabled

Wait for the current save to finish before saving again.

Error

Enter a single token.

Focus

Cyan replaces the contour. The secondary face stays.

Selected

Neutral selection face, violet leading edge, and bold violet label, with no focus cue.

Hover

Hover face only. The blue edge and label stay.

Pressed

Pressed face. The control does not move or resize.

Selected and focused

Selection face and violet label stay. Cyan focus is a separate outline.

Invalid and focused

Enter a single token.

Standard graphical controls

This table is the desktop face contract. Mobile uses the same faces, at the 48px target, and omits hover. The terminal uses the glyph mapping in Platform implementation for this same set.

State Face Boundary and text
Default primary Control gradient 1px blue edge; primary text, weight 600
Default secondary Same control gradient 1px essential neutral edge; primary text, weight 500
Hover Control-hover gradient Keep primary/secondary edge role and label
Pressed Control-pressed gradient Same edge; no movement or size change
Keyboard focus Current state face Replace the existing contour border with cyan; do not add an outer ring
Disabled Flat content Quiet edge, full-opacity secondary text; no hover/press change
Busy Existing enabled face Reserved progress slot and specific label; no duplicate activation
Quiet action Transparent Primary text at rest; cyan underlined links remain distinct; no persistent filled face
Destructive Danger surface Danger edge and pale/danger label; explicit destructive verb
Warning · default / hoverWarning surface; unchanged on hoverWarning edge and label; explicit warning verb
Warning · pressWarning surfacePreserve edge and label; underline label during press
Warning · focusLighter --focus-fill: #52677EPreserve warning edge and label; no outer ring
Warning · disabledDisabled surfaceQuiet edge and secondary text; no hover/press change; explain availability

Both primary and secondary standard buttons remain dark and translucent-looking. Hierarchy comes from boundary, weight and placement. Do not make the primary brighter than a selected tab. A quiet action is the explicit unfilled exception.

Selected content tabs use the default control gradient, pale text and 3px blue top marker. On desktop, hover may use the hover gradient but does not remove selection. Inactive tabs use dark content and secondary text. A focused inactive tab has cyan focus without selecting itself. A selected tab with focus keeps its blue top marker independently of its cyan contour. Mobile has no tab hover. The terminal renders the selected tab as a pale bold label between a square left join and one diagonal top-right end (| Overview \), and focus as cyan, without painting a blue cell. Inactive tabs use secondary text without brackets. All terminal controls keep their containing background. Primary actions are bold pale labels in brackets; secondary actions use the essential edge color. Focus recolors only the label cyan, preserving values and selection glyphs.

Default

Primary: blue edge, weight 600. Secondary: neutral edge, weight 500. Both use the control face.

Hover

Hover face only. Edges and labels stay.

Pressed

Pressed face. No movement or size change.

Focus

Cyan replaces the contour. The current face stays.

Disabled

Change the draft before saving.

Busy

Enabled face, specific label, reserved indicator slot.

Quiet

No filled face. Focus reveals the existing 1px contour in cyan at 0 offset.

Destructive

Danger face, danger edge, and an explicit verb.

Selected tab

Control face, pale label, blue top marker. Activity stays inactive.

Selected tab, hover

Hover face. The blue marker and selection remain.

Focused inactive tab

Activity has cyan focus and is not selected. Overview stays selected.

Selected tab with focus

The blue selection marker survives; the existing 1px contour turns cyan.

Toggle

Off, on, and on with focus. The blue edge keeps the on value; focus replaces the face with surface.focus.

Focus and keyboard

Use semantic native controls and visible focus for every keyboard-operable element. Focus recolors the existing 1px contour cyan at 0 offset, including diagonal cuts, without an outer stroke. Keep the contour visible through decorative clipping. Reserve a transparent 1px contour for quiet actions and links so focus does not change geometry. Preserve semantic value/error edges with the lighter surface.focus fill exception. Ensure at least 3:1 against adjacent colors. Selection is not a focus indicator.

Use a single tab stop for a tab strip and roving focus among enabled tabs. Arrows move focus, Home/End reach ends, Enter/Space activates manually. Pointer activation focuses and selects. Manual activation prevents arrow exploration from unexpectedly loading data. Form text inputs keep their native editing keys.

Opening menus and dialogs moves focus into them. Escape dismisses a dismissible overlay and returns focus to its trigger. If a disclosure hides focused content, return focus to its heading control first. Reflow, incoming logs and status polling do not take focus. Desktop and mobile share this contour. The terminal uses cyan foreground or a cyan frame, and it has no pointer hover.

Button

Cyan replaces the existing edge. No second ring.

Link

Open log

The reserved 1px contour turns cyan at 0 offset.

Tab strip

One tab stop. Arrows move focus. Enter or Space selects. Activity has focus; Overview stays selected.

Field

The existing 1px edge turns cyan at 0 offset.

Row

Tokens
Preferences

Tokens is focused and not selected. Preferences is selected and not focused.

Selection

Navigation is violet and focus is cyan. Graphical selected destinations use a neutral selection face, violet leading edge, and bold violet label. Selected data rows add a persistent primary-text ▸ glyph in a reserved slot. The terminal uses that glyph for navigation and rows. Selected-and-focused examples below are the canonical combined state; selection remains after focus moves.

A card is not implicitly clickable. If selection is needed, provide an explicit selection control and selection semantics. Do not nest action buttons inside a clickable-card button. If a list row is an activation control, its behavior and keyboard target must be explicit while retaining the contextual selection style. Desktop and mobile share the neutral selection face for navigation and the persistent row glyph for data. The terminal preserves the primary-text ▸ glyph while focus recolors the control label cyan; values remain unchanged.

Navigation

Preferences is selected. Tokens is focused. Logs is both. Violet identifies navigation; the selection face and bold label identify the current destination.

Data

gateway-1
gateway-2
gateway-3Ready10.0.0.3

The persistent ▸ glyph is selection. Cyan outline is focus. gateway-1 stays selected after focus moves.

Gateway

The card is not a control.

Fields and errors

Field hover uses the named --field-hover-edge: #91A0B7. Read-only fields retain the input face, essential edge, and primary text; they stay keyboard-focusable, selectable, and copyable, prevent editing, and expose native readonly semantics (or aria-readonly="true" for a custom control). Focus recolors the existing 1px contour cyan at 0 offset. Include a visible Read-only label.

An enabled field uses the flat input surface and essential neutral boundary. Focus recolors the existing 1px edge cyan at 0 offset; validation errors use a danger edge plus a visible associated error. Invalid focus preserves that edge and uses surface.focus. Required state is expressed in the label/hint. Placeholders are secondary hints, not labels.

Keep a checkbox, radio, or switch value distinct from hover and focus. A checked control uses a dark blue face, a blue boundary, and a pale check or thumb. A radio has a centered pale dot. A switch uses a chamfered 32×20 control track with 4px cuts, not a pill, and a 16px pale thumb inside the full target. Desktop and mobile share these faces. Mobile omits hover and uses the 48px target. The terminal keeps the same values and draws [ ], [x], [-], ( ), (•), [off], and [on]. Always expose the state independently of color.

Default

Shown in activity lists.

Hover

The edge uses --field-hover-edge: #91A0B7. The value is unchanged.

Focus

The existing 1px edge turns cyan at 0 offset.

Invalid

Enter a single token.

Invalid and focused

Enter a single token.

Disabled

Wait for the record to load before editing.

Read-only

Assigned by the source. Not editable.

Unchecked

Empty mark on the input face.

Hover

The edge strengthens. The value stays unchecked.

Checked

Dark face, blue edge, pale check.

Checked, hover

Hover face. The check stays.

Mixed

Include partial logs

A pale dash. This is a value, not a hover.

Focused

Lighter surface.focus fill preserves the blue edge and pale check.

Disabled

Wait for the record to load before changing log inclusion.

Invalid

Select a source.

Danger edge, with the message beside it.

Invalid and focused

Select a source.

Danger edge and lighter surface.focus fill together.

Group

One option is checked. Applies to the next run.

Unchecked

Empty mark.

Hover

The edge strengthens. The value stays unchecked.

Checked

Dark face, blue edge, pale dot.

Checked, hover

Hover face. The dot stays.

Focused

Lighter surface.focus fill preserves the blue edge and pale dot.

Disabled

Wait for the record to load before changing the run mode.

Off

The thumb sits at the start. That position is the value.

On

Dark track, blue edge, pale thumb at the end.

On, focused

Lighter surface.focus fill preserves the blue edge and thumb position.

Off, disabled

Connect to the service before changing auto-refresh.

On, disabled

Connect to the service before changing auto-refresh.

Loading, retention and feedback

Busy actions replace their idle label with operation-specific wording, such as “Save” becoming “Saving changes”. Reserve enough width for both labels and the indicator so the control does not move or resize. Desktop and mobile supplement the words with the four-segment spinner; the terminal uses the same words and the quadrant-block spinner. For an unresolved submission, keep “Accepted, outcome pending” or “Outcome uncertain” visible outside collapsible content. Do not unlock a replacement action because a spinner stopped.

Retain existing rows during refresh and label snapshot freshness separately from record status. Empty means a successful query returned no content; loading and unavailable get their own text. Invalid JSON remains editable and visible; guided mutation may be blocked while the last valid value remains preserved. Desktop and mobile share these messages. The terminal uses the same words, with [!] instead of a graphical edge.

Busy

The operation-specific label replaces the idle label. Stopping the indicator does not unlock a replacement action.

Outcome pending

AcceptedOutcome pending

Both stay outside the disclosure.

Submission details

Received by the queue. The external result has not arrived.

Refreshing

Snapshot 10:41 · refreshing

gateway-1Ready
gateway-2Idle

Rows stay. Freshness is separate from Ready and Idle.

Loading

Loading records…

Empty

No records match this query.

Unavailable

Records are unavailable. The last snapshot is still shown above.

Invalid JSON

{ "title": "Research notes"

Line 1: expected a closing brace.

The text stays editable. Guided edits stay blocked, and the last valid value is kept.