v0.5.1-alpha.2 is outsee the release →
All guides
[ DOCS ]

Styles

docs/styles.md on GitHub →

Workbench language

The dashboard is a VS Code-inspired developer workbench, not an official reusable VS Code component package. It uses the structure, density and neutral semantics of VS Code Dark Modern and Light Modern while preserving Aether's routes, run-state colours and capabilities. Adjoining panes are flat and quiet: do not add saturated accent colours, arbitrary gradients, blurred cards or elevated nested panels.

The UI uses the native system stack: system-ui, Ubuntu, Droid Sans, sans-serif at 13px with a 1.4 line height; supporting copy is 12px. JetBrainsMono NFM is retained for terminal output, commands and code. VT323 remains only for the Aether wordmark and the original startup splash. Do not use the brand face for body copy.

Semantic palette

web/src/index.css is authoritative. Light and dark are semantic Light Modern and Dark Modern modes, not separate feature palettes. The theme preference remains system, light or dark, with system following prefers-color-scheme live.

Token Light Modern Dark Modern Use
--background #ffffff #1f1f1f Editor and main view
--sidebar #f8f8f8 #181818 Titlebar, activity/sidebar chrome, panel and statusbar
--card #ffffff #1f1f1f Main workbench surface
--popover #ffffff #202020 Actual widgets and floating surfaces
--foreground #3b3b3b #cccccc Primary text
--muted-foreground #616161 #9d9d9d Description and secondary text
--field-placeholder #767676 #989898 Placeholder text
--border #e5e5e5 #2b2b2b Pane seams and quiet separators
--input #949494 #7a7a7a Contrast-tuned field boundary
--primary #367f77 #367f77 Subdued brand teal for interactive fills and focus
--accent-soft-foreground #367f77 #6ee7d6 Text on soft accent surfaces; dark mode uses the landing-page mint where it has enough contrast
--success-soft-foreground #1f521f #89d185 Text on success-soft surfaces; light uses the readable success foreground
--warning-soft-foreground #5c4500 #f5d08a Text on warning-soft surfaces; light and dark use the readable warning foreground
--danger-soft-foreground #a1260d #f48771 Text on danger-soft surfaces; VS Code error foregrounds keep the label readable
--primary-hover #306f69 #306f69 Primary hover
--toolbar-hover #f2f2f2 #2a2d2e Flat toolbar and row hover
--selection #e8e8e8 #17413d Selected rows and text
--selection-foreground #000000 #ffffff Text on selection

@theme exposes the semantic utilities bg-selection, text-selection-foreground, bg-primary-hover and bg-toolbar-hover alongside the existing background, sidebar, card and field utilities. --input is intentionally stronger than the reference field-border colours where needed to keep the boundary discernible against its field surface. Pane seams retain the quieter Modern values and do not need input-border contrast. The subdued brand teal is separate from run status and member attribution. HeroUI aliases consume these semantics; they do not define a second palette. Member colours are the only arbitrary server data applied inline, on avatars and attribution rails while text remains token-based.

Geometry and responsive behavior

Use compact workbench geometry rather than landing-page ornament:

  • 35px title and command bar, 48px activity rail with 24px icons, and a preferred 260px workspace/run sidebar constrained to 200-520px.
  • 35px view and section headers; the run detail's combined metadata, tab and action strip stays 36px, while dock headers use a min-h-9 strip whose actions can wrap to another row; 22px status rows and 22-28px list rows according to real content.
  • 26px fields and buttons, 22px compact tools, 12px form gaps, 4px label gaps, 16px content gutters and 12px compact gutters.
  • Adjoining panes, sections, rows, run cards and tab strips have zero radius. Compact controls and chips use 2px; fields, buttons, popups and dialogs use at most 4px. Full circles are reserved for actual avatars, status dots, radio controls and switch knobs.
  • Radius tokens are limited to 0, 2px and 4px; content panels have no shadows. Restrained shadows are limited to actual floating menus, quick input and dialogs. Headers stay 13-16px, with no promotional 20-24px titles or oversized cards.

At 390px every operation remains available through compact navigation or overflow, stacked forms, bounded dialogs and tree-to-file navigation. The main page never gains horizontal overflow; text and code may scroll inside their own surfaces. Coarse pointer controls may grow to 32px where required.

Shell, palette and focus

The shell keeps a persistent 48px activity rail for existing navigation, with capability gates, accessible labels and tooltips, an active 2px indicator and overflow when destinations do not fit. The adjacent workspace/run sidebar keeps its persisted splitter behavior and uses compact group rows. At 1000px and narrower that pane collapses into the persistent rail, which exposes Expand sidebar without changing the stored preference. At 640px and narrower its expanded pane overlays the center from the rail's right edge.

The browser and Electron titlebar is a real 35px command center. It names the active workspace and opens the existing palette through togglePalette(true). The command palette is mounted exactly once as an independent AppShell host, never as a hidden status-slot contributor. Quick input is top-centered directly under the titlebar, max 600px, with compact rows and no giant scrim-heavy card. The status Slot remains mounted once for team refresh and other live contributors, including shortcuts. At narrow widths secondary status details use a bounded, keyboard-reachable popup while connection and theme controls stay available.

focusRing and field remain signature-compatible shared utilities. Preserve their keyboard outline, inset behavior for full-bleed rows, readable placeholders, disabled and read-only states, menu roving focus, typeahead, portalling and viewport flipping. Shared primitives retain their props, events, refs and accessibility contracts.

Run state and startup motion

The six --state-* tokens remain the status vocabulary for a run's presentation state. Domain status enums remain unchanged.

Token Light Dark
--state-working #2b6cb0 #3794ff
--state-waiting #8b6c00 #cca700
--state-needs-attention #bc4b00 #e2c08d
--state-failed #c72e0f #f85149
--state-done #2ea043 #89d185
--state-idle #6e7681 #858585

StateIndicator uses bouncing dots for working runs in cards, headers and lists. The labeled run-status chip reserves the full width of all three dots before its text; compact unlabeled surfaces keep the fixed dot box so state changes do not shift their columns. Sidebar rows pulse one dot and palette rows remain static. The steering signal, working dots and sidebar pulse stop moving under prefers-reduced-motion: reduce; state meaning remains available as text and labels. Loading spinners and delayed skeletons remain functional feedback.

The desktop first-launch splash is a finite branded handoff, not a loading screen. Its dark sky, grain, clouds, twinkling field and shooting stars stay visible for at least 600ms and leave by the 2500ms cap, followed by a 260ms fade. The mark enters over 700ms. Shooting-star trails use opacity and translate3d only: the recovered 35, 42 and 30 degree trajectories run for 1.35s, 1.55s and 1.7s with staggered entry. The splash is session-only, unmounts after the fade, and is skipped under prefers-reduced-motion: reduce.

Accessibility and component contracts

src/components/ui/heroui.tsx is the only entry point for the HeroUI v3 Chip and Tooltip wrappers. Chips carry status and metadata, while run and dock strips keep their custom manual keyboard semantics. A Tooltip opens on keyboard focus and pointer hover with a shared 300ms hover delay, points aria-describedby at the control, and supplements rather than replaces an accessible name. Focusable controls use Tooltip descriptions instead of title; title remains for non-focusable paths, timestamps and breakdowns. Actor marks that need a name use role="img" and an aria-label, rather than leaving a named generic span.

Yes-or-no confirmations use the AlertDialog primitive, not a dismissible Dialog. AlertDialogAction is destructive unless the caller says otherwise. When a confirmation reports a failure, it prevents the default on that click and closes on the answer instead, so the refusal stays observable rather than being unmounted with the dialog. Controls that remain reachable while unavailable use aria-disabled, guard their own handlers and retain their tab stop instead of using disabled.

Team status contributions use the 22px compact-row geometry. Presence derives online members from non-offline roster entries, while watcher marks use the same named actor avatars; an empty roster or watcher set contributes nothing.