swarm-ui design guide

Scope: swarm-ui only (the swarm-level Preact app — not the per-hive dashboard, which has its own older visual language). This doc is the why: the principles behind how swarm-ui looks and behaves, and the concrete rules that follow from them. It deliberately doesn't show what things look like — that's ComponentsPage (/components), the living, always-current demo of every primitive in src/ui/. Code can't go stale the way a doc's screenshots can, so treat ComponentsPage as the source of truth for appearance and this doc as the source of truth for intent. Colour variables specifically are docs/web-ui/css-vars.md's job, not repeated here.

Visual language

Motion

General rule, not case-by-case: every non-essential CSS animation gates on prefers-reduced-motion and pauses when its tab/section is hidden. The dashboard's matrix-rain background (packages/dashboard/ src/home.js/.css) is the existing reference implementation of this pattern — genuinely does both today, confirmed by reading it — even though it lives in the dashboard package rather than swarm-ui; every future swarm-ui animation follows the same pattern, not just whimsy pieces.

Prefer CSS-driven animation over JS-driven where possible, and avoid jarring content swaps (layout shift, hard cuts) where a transition can smooth them instead.

Theming

The mechanical contract (base16 slots, semantic vars, what a page's CSS is and isn't allowed to reference) lives in docs/web-ui/css-vars.md — read that for the how. This section is the policy layered on top:

Data freshness & refresh

The governing question for anything that shows time-sensitive data: would a user returning to this tab expect current data? If yes, it needs a refresh story; a value that's silently gone stale with no way to tell is worse than one that's visibly stale.

Errors

ApiErrorPanel + the RFC 9457 ProblemDetails shape it renders is the canonical error surface for every API failure in swarm-ui, not just its original callers — nobody should build a softer/quieter error UI later. Concretely: full untruncated detail, a copy button, no "something went wrong, try reloading."

This follows from power-user-first, one of the standing principles: swarm-ui instances are mostly self-hosted and operators are techies, so errors should give them what they need to actually diagnose a problem rather than a friendly wall. Power-user-first doesn't mean newcomer-hostile — the UI should still be self-explanatory, warn or ask for confirmation before a destructive/dangerous action, and offer helpful hints — it just shouldn't get in the way of someone who already knows what they're doing.

Empty states

Graceful fallbacks, always — a table with zero rows should show something that represents "no rows" (a real empty-state message), not just bare column headers floating over nothing.

Layout & viewport

Component-first design

Build the src/ui/ primitive before or alongside the first real page that needs it, not after it's been styled inline and left for later — a page reaching for a component that's already there has ready-made blocks to build with, instead of the next contributor duplicating ad-hoc styling that someone then has to hunt down and consolidate. Panel/StatusChip/Table/TextField/SelectField/Button are the primitives that exist; /components always has the current, complete list — this doc won't try to keep a duplicate inventory in sync.

Every new ui/ component gets a demo section on /components the same day it lands — no primitive without a place to see it. One deliberate exception exists today: FormField, the internal label+ control wrapper TextField/SelectField share, isn't itself a primitive a page reaches for directly, so it has no demo of its own — a considered exception, not an oversight this rule missed.

Attention

"Attention-optimized" here means the system should lead the operator's attention to where it actually matters (an error, a state change worth noticing) — not attention-optimized in the engagement/growth sense of maximizing time-on-page.