Coffer UI Visual Language
Companion reference for agents touching
frontend/. The authoritative tokens live infrontend/tailwind.config.js; this file documents the conventions.
Source of truth
frontend/tailwind.config.js is the single source of truth for spacing, colour, radius, typography, and container width tokens. Do not introduce parallel CSS variables or ad-hoc inline style values; extend the Tailwind config and reuse the tokens.
Colour palette
All semantic colours are HSL variables (hsl(var(--name))) resolved per theme. The redesign is light-only.
Semantic tokens (defined in tailwind.config.js theme.extend.colors):
background/foreground— page base.card/card-foreground— card surface and text on it.popover/popover-foreground— dropdowns, dialogs.primary/primary-foreground— primary actions ("Add MCP server", "Save").secondary/secondary-foreground— secondary actions, inactive tabs.muted/muted-foreground— disabled / placeholder text.accent/accent-foreground— hover and selected states.destructive/destructive-foreground— delete / remove actions.border,input,ring— surface borders, form input borders, focus ring.status.ok/status.warn/status.err— health pills (used by the resource list and the daemon-offline banner).
Pick from the semantic name, not the underlying hue. Adding a new semantic token is preferred over hard-coding hex / hsl values inside a component.
Typography
fontFamily.sans is the default UI face (system stack, Inter / Roboto fallback). fontFamily.serif is reserved for long-form prose (Source Serif). fontFamily.mono is reserved for code, command snippets, and identifiers (SFMono-Regular / Menlo fallback).
Use Tailwind's built-in type scale (text-sm, text-base, text-lg, text-xl, text-2xl) rather than per-component pixel sizes. Headings inside a screen step through the scale in single increments; do not skip levels.
Spacing
Stay on Tailwind's default 4 px scale (p-4, gap-6, space-y-3, …). The container utility centres content with 2rem padding and a 2xl breakpoint at 1400 px. maxWidth.content (72 rem) caps a workbench page; maxWidth.prose (60 ch) caps long-form text.
Radius
borderRadius.lg / md / sm / xl are derived from --radius so a single root variable retunes the whole UI. Use rounded-lg for cards, rounded-md for buttons / inputs, rounded-sm for inline chips.
Composition rules
- Build screens from shadcn primitives in
frontend/src/components/ui/rather than inventing new wrappers per page. - Empty / loading / error states are first-class — never let a screen render with no content while data resolves. Give each surface its own empty/loading/error treatment (e.g.
components/chat/ChatEmptyState.tsx); there is no sharedEmptyStateprimitive yet — factor one out when a second feature needs the same shape. - Status surfaces (daemon offline, capability disabled, tool-call health) use the
status.*tokens; do not pick rawgreen/amber/emeraldpalette classes. Type sizes come from the built-in scale (text-sm/text-xs/…), never per-componenttext-[Npx].
When in doubt
If a token does not exist yet, extend the Tailwind config in the same PR that needs it and explain the addition in the PR description. Do not inline magic numbers.