Component kitchen sink and token reference
Semantic tokens — OKLCH color space, light & dark mode
Surfaces
background
card
light-muted
muted
heavy-muted
Brand
primary
primary-light
secondary
secondary-light
accent
Status
success
success-light
warning
warning-light
destructive
destructive-light
Two voices — InterVariable for UI, Freight Big Pro for brand & editorial. IBM Plex Mono for coordinates.
Voice 1 · UI · Inter
The operator's console.
Dense UI — tables, forms, buttons, labels. ~95% of the platform and all landing supporting copy.
The quick brown fox jumps over the lazy dog. 1,284 executions completed in 3m 42s average.
Voice 2 · Brand & Editorial · Freight Big Pro
Build agents that run the web.
Landing hero & section titles. In-product hero moments, empty states, onboarding, quotes. One expressive voice across both surfaces.
“A polished power tool that doesn't intimidate.”
Coordinate · IBM Plex Mono
Not a voice — a coordinate system. Identifiers, versions, model slugs,
metadata k:v pairs, cron expressions, URLs & paths, raw enum
values, and tabular numerics. Where precision beats emotion. Use <Mono> from @workspace/ui/components/mono outside of DataTable (which has meta.mono).
Mono
Not mono
Platform type scale — Inter
Editorial tier — Freight Big Pro (use sparingly)
In-context example — empty state
Executions
Nothing to see here — yet.
Kick off your first agent run and this page will fill up with the interesting stuff.
Serif headline + Inter supporting copy + mono overline. The three voices playing together.
Button face is lifted from flow, shadow at flow position — sinks on hover, presses on active
Variants × Sizes
| variant \\ size | sm | default | lg |
|---|---|---|---|
| default | |||
| destructive | |||
| outline | |||
| ghost | |||
| link |
With Icons
Icon Only
States
Common Patterns
Dialog Footer — confirm / cancel
Destructive Confirmation
Toolbar — mixed actions
Tab Toggle — ButtonGroup variant switching
Page Header — primary + secondary actions
Split Button — primary action + dropdown
Icons are a supporting voice — they reinforce an affordance or classify a type. Four working sizes, six semantic colors, and a strict anti-pattern list. See DESIGN.md §Iconography for the canon.
Time components never wear leading icons — the mono typography carries the signal. Cross-link: §Time & Duration.
1 · Size scale
Four working sizes carry 99% of the system. size-3 is not canonical — it survives as drift. size-6 is reserved for rare page-header chrome.
size-312pxsize-3.514pxsize-416pxsize-520pxsize-624pxsize-832px2 · In buttons
Button hands every nested icon size-3.5 and gap-1.5. Never pass size-* or mr-* class overrides.
3 · In tabs
TabsTrigger size="default" and ChromeTab use size-3.5. size="lg" steps up to size-4 because the tab IS the page title.
4 · In menus
DropdownMenuItem and CommandItem both hand icons size-4 and gap-2. Delete rows use text-destructive; everything else inherits text-muted-foreground from the primitive.
5 · In entity and stat cards
Entity full-tile (size-9) holds size-4; compact (size-7) holds size-3.5. Stat card header icon is size-3.5, right-aligned in the muted label row.
6 · Time components never wear icons
Mono typography + tabular-nums + the two-line tooltip carry the signal. The column header / row label / prefix prop name the field.
Bare time component, or prefix prop for the label. Mono font does the work.
Clock/Calendar/Timer prefixing a time component. The column header already says 'Last run'.
7 · Color canon
Icons inherit currentColor. Use semantic tokens. Never text-green-600, text-red-500, and friends.
text-successtext-destructivetext-warningtext-primarytext-admintext-muted-foreground8 · Empty state hero
EmptyMedia variant="icon" renders a size-10 muted tile around a size-8 icon. Do not pass a size class on the icon itself.
9 · Inputs with a leading icon
No built-in SearchInput primitive yet — use an absolutesize-3.5 Search icon with muted foreground and pad the input to match.
10 · Anti-patterns
Four classes of drift the canon forbids. See §Iconography for the full list.
Status badge tint IS the signal — one glyph or none.
Status-tinted badge AND a same-meaning status glyph next to it.
Let the Button primitive supply size-3.5 and gap-1.5.
Overriding size-4 fights the canon. The Button no longer uses size-3 by default — trust it.
Semantic tokens: text-success, text-destructive, text-warning, text-primary, text-admin.
Raw palette colors. No text-green-*, no text-red-*, no hex literals.
One size per visual group — size-3.5 here.
Mixed size-3 and size-4 in the same row. Pick one.
11 · Admin affordance
The element IS the signal — amber button / amber text instead of an extra badge. AdminBadge scales its Shield glyph (xs: size-2.5, sm: size-3, md: size-3.5).
12 · Loaders
Loader2 animate-spin in a status position inherits the surrounding size canon. Keep it the same size as the icon it replaces.
Grouped actions with shared offset shadow
Horizontal — outline
Horizontal — alignment
Vertical
With Text & Separator
Variant groups
Tinted backgrounds with colored text — quiet labels, not loud fills
Variants
Status — filled, distinct from brand colors
The affordance IS the signal — admin elements paint themselves amber. Badge stays as the page-header chip and the fallback for elements that already carry color. See DESIGN.md → Admin Indicators.
Token
--adminSolid amber. Use on raised admin buttons; rarely as a surface.
bg-admin/10Tinted background — strip, hover/active states, ghost surface.
border-admin/30Border tint for the strip and outlined admin elements.
Element coloring (primary pattern)
Buttons
Sidebar items
Properties & links
Form-control primitives (admin prop)
<Input admin /><SelectTrigger admin />Playwright traces for every session.
<FeatureToggle admin /> (threads to <Switch admin />)Controls inside <AdminOnly> accept an admin prop instead of hand-rolling amber classes. The palette, focus ring, placeholder tone, and switch track all flip together. Size and disabled behavior are unchanged.
AdminBadge — fallback & page-header
size="xs" — fallback onlysize="sm" — raresize="md" — page headerThree contexts
Property
Element coloring is the primary signal — render the value in text-admin.
Control
The button paints itself amber. No badge needed.
Page header
One md badge marks the whole page identity.
Admin mode toggle (live demo)
AdminPageGate (toggle-off interstitial)
You turned Admin mode off — internal tools are hidden so it's safe to demo. Re-enable to view this page.
Anti-patterns
Don't bolt a badge onto every admin element
The element's own admin coloring is the signal. Use variant="admin", not <AdminBadge /> stapled next to a default-styled control.
Don't use --admin for warnings
Status colors stay status colors. For "might be dangerous," reach for --warning or --destructive. Admin is a category, not a status.
Don't gate access with color or the badge
Both are signals for admins, not gates. Server-side isAdminUser() route guards still enforce real access.
Don't show admin elements to non-admins
Hide the entire element via <AdminOnly>. Disabled admin features leak product surface.
Chrome-style tabs — active tab uses bg-background against a muted bar surface, with inverted SVG corners
Tabs (Radix) — sub-navigation within a page or resource (Overview / Analytics / Reports). Self-contained, keyboard-accessible, internal state.ChromeTabs — browser-style document tabs (the agent workspace: builder, executions, open execution tabs). Controlled, supports drag-to-reorder, close, sticky pins, add-dropdown.Default framed look — Tabs / ChromeTabs
The canonical chrome-tab visual: transparent strip, active tab with border + stroked inverted corners, content panel with full border + rounded-b-lg, and one shared drop-shadow over the whole tongue-card silhouette. Both Tabs and ChromeTabs default to framed; the panel border auto-paints so consumers don't repeat border border-t-0 rounded-b-lg. Suppress the drop-shadow when the strip and workspace can't share a DOM subtree (agent header pattern) via framedShadow={false}.
Tabs framed (sub-navigation, on bg-light-muted)
Border traces the active tab via the inverted corners and wraps the whole content card. One shared drop-shadow.
ChromeTabs framed (one shared drop-shadow)
Active: exec-92890146 — ChromeTabs wraps strip + workspace and applies the shared drop-shadow over the whole tongue-card silhouette when framed.
Flat variant — framed={false} on inline tabs
Opt out via framed={false} for tabs that live inside an existing card, modal, or section, where a bordered tongue card around the content would double up. The strip gains a bg-muted plate; the panel is opinion-free (consumer decides shape).
With icons
Executions content
Text only
Overview content
With badges
All logs
With disabled tab
Active content
Browser-style tabs — ChromeTabs
Active: exec-92890146 — Drag non-sticky tabs to reorder, close with the X button (or middle-click), open more from the + menu. The first three tabs are sticky and cannot be moved or closed.
Custom layouts — ChromeTab standalone
The same visual, used as a standalone primitive. Reach for this only when neither Tabs nor ChromeTabs fits — e.g. a custom router integration that needs full control over rendering.
Standalone ChromeTab (manual layout)
Content for: executions
On light-muted surface
Executions — uses bg-light-muted for the tab bar, matching the agent header.
Minimal
Users content
Full width
All items
One shell, four sizes (sm / md / lg / full), five fixed slots: header → optional persistent identity strip → optional tab strip → body → footer. Tabbed modals share the chrome-strip + chrome-tab pattern with §Page shell — a modal-with-tabs is structurally a tiny chrome page. See DESIGN.md §Modals for the canon.
Confirm
sm · 448One question, one answer. Destructive footer: outline-Cancel + destructive primary with the verb spelled out.
Form
md · 672Single-purpose form. Header description carries the intent; FieldGroup runs the body; Cancel + Save in the footer.
Tabbed
lg · 896The canonical shape: header → persistent identity strip → chrome tabs → body → footer. Mirrors §Page shell tabbed pages.
List-pick
md · 672Search + scroll + multi-select inside a DialogBody. The toolbar pins to the top, the list scrolls, the count summary lives in the footer.
Sheet (right)
sheet · rightLong-lived, sequential, or sticky-while-page-context-matters. Reach for Sheet over Dialog when the flow has multiple steps.
Loading + error
md · 672Same shell, two states. Loading uses skeleton inside the body. Blocking errors are an in-body banner — never a toast.
Right-action + split footer
lg · 896The Run Agent shape: Pre-fill Select sits in the header right-action slot; Schedule and Advanced cluster left in the footer; Cancel + Run cluster right.
The four sizes
Anything else (sm:max-w-[600px], sm:max-w-5xl, custom max-h-[Nvh]) is a smell.
Inputs, labels, and form patterns
Text Inputs
Input + Button combinations
Content containers with header, body, footer
Total executions
Compact metric tiles — label + icon, value, helper. Used on /overview and /executions and reserved for future summary strips (billing, usage, scheduled). One row, 3 or 4 tiles, top of page.
Anatomy
Strip — 4 tiles · /overview
Strip — 3 tiles · /executions
Loading — full strip skeleton
Future use — billing strip (same shell, different content)
Same shell, swap the helper. Numeric metric → number value + breakdown helper. Set of related items → no value, badge children. Trend tile → same shell, badge as the helper (see kitchen-sink dashboard below).
How a domain entity (Agent Profile, Integration, Notification, API Key, Schedule, …) renders anywhere in the platform. The same identity — icon, name, type, meta — at three densities: full on index pages, compact in side panels and drawers, and chip as an inline reference with a popover. Detail lives in a vertical stack of named body sections — and only those sections — so two cards for different entity types still read as members of the same family.
Anatomy — full variant
EntityCardType) — small uppercased mono label sitting above the title. The icon already telegraphs the type via shape; the overline is the belt-and-braces text label.text-base font-semibold, truncating. The dominant element of the header now that identity info is split across overline + title only.space-y-4) of named section primitives. This card uses PropertyList + FilterRow.EntityCardTimestamps with showCreated renders Created always and Edited when changed (60s threshold). Empty footers don't render at all.Variants — full / compact / chip (hover the chip)
Full
Compact
Chip + popover
Full
Compact
Chip + popover
Body grammar & footer — six body sections plus the audit footer
Every full or popover body is a stack of named sections. Picking from this set is mandatory; ad-hoc divs in an entity body are out. Two densities of PropertyList for key/value, four cluster sections for everything else.
PropertyList density="grid"
PropertyList density="inline"
PillRow
DividedList
DividedList (editable, with actionSlot)
FilterRow (with overflow)
EmptyRow
EntityCardFooter (default)
Edited >60s after create — meta already shows Created.
EntityCardFooter (showCreated)
Custom-meta entity (e.g. Schedule) — footer carries Created too.
Composed example — three sections in one body
The same shell with a stacked body. Sections sit at space-y-4; headers and content within a section sit at space-y-2.
Chip references — hover or click to expand
Chips are entity references inside other content (table cells, log lines, toasts, breadcrumbs). Identity only — icon, type, name. The popover shows the entity's compact card plus an allow-listed subset of body sections, and a footer link that opens the entity in a new tab.
Two underlying surface modes — canvas
(used by Plain, Hero, and Start) and chrome
(used by Tabbed and Detail) — produce the platform page shapes. Canvas pages
get a bg-light-muted well with bg-card surfaces lifting off it. Chrome pages drop the canvas and let a bg-sidebar strip + tabs sit directly above a single bg-background content panel — the active tab is the only seam between chrome and content.
Sidebar carries the page label; the title slot is reserved for the active
tab (Tabbed/Detail), the editorial composition (Hero/Start), or nothing at
all (Plain).
Anatomy — canvas mode vs chrome mode
Canvas mode · Plain & Hero
Three surfaces: chrome → canvas → cards. Page bar is transparent on the canvas, optional, slim. Cards lift off the canvas via border + ~1.5% lightness delta.
Chrome mode · Tabbed & Detail
Two surfaces: chrome → content panel. Tabs sit on
bg-sidebar; the active tab is the only seam between chrome and content.
Actions live inside the tab content because they're tab-scoped.
TabsList
override
!bg-transparent !px-0 so the strip itself is the bar (no nested-bar effect)
TabsContent (they're tab-scoped)
The core shell shapes
A · Plain
Top-level routes with one purpose · sidebar carries the label
No title. The sidebar already says "API Keys" — repeating it in a header strip is wasted real estate. Page bar carries only the primary action; everything else is the content card.
B · Tabbed
Chrome strip + tabs · no canvas layer · tab is the only seam
No canvas layer. The page chrome (bg-sidebar) extends up over the tabs; the active tab is the only seam between
chrome and the white content panel below. Documentation link +
Create button move into a tab-scoped action row at the top of each
tab content — they swap with the active tab anyway. Strip stays pure
navigation.
C · Hero
Dashboard / summary routes · editorial moment replaces the title
No page bar at all. The editorial hero is the first thing you see — a serif headline that adapts to state, mono overline for context, Inter copy with live numbers. Stat strip and content cards stack below.
D · Detail
Resource pages · custom header carries the resource name
Custom resource header. The page bar
grows up onto the chrome (bg-sidebar) and carries: agent selector (resource title) → ChromeTabs (sticky
views + open documents) → primary action (Run). Detail pages may opt
into surface="flat" when the canvas IS the application (Builder, execution canvas).
Title rule
| Page has… | Title slot | Subtitle slot | Examples |
|---|---|---|---|
| Tabs | Drop. Tabs are the identity. | Drop. Use
InfoCard inside tab content if needed.
| Agent Profiles, Admin → Models / Usage |
| Editorial hero | Drop. Hero replaces it. | Drop. Hero supporting copy carries it. | Overview |
| Start composition | Drop. Split editorial prompt replaces it. | Drop. Primary composer and rail copy carry the context. | Create / get started |
| Custom resource header | Resource name · dynamic content | Drop. | Agent, Execution, Batch detail pages |
| Plain top-level route, no tabs | Optional. Prefer dropping — sidebar already says it. | Optional. Treat as a smell. | API Keys, Integrations, Manage Users |
Tabs — pick the API by semantic, not by look
Tabs (Radix)
Internal sub-sections
Self-contained. The user can't open new ones, drag them, or close them. Internal state owns the active tab.
ChromeTabs
Documents / routes
Browser-like. Tabs are openable, draggable, closable. Outer state owns the list.
Both render the same chrome-tab visual — same surface, same shoulder, same inverted corners on the active tab. The split is about what the tabs mean, not how they look.
The Start page shape: a canvas-first decision surface for creating an agent, onboarding a user, or choosing a first workflow. One editorial composition may span the primary composer and the supporting template rail, but everything below returns to compact Inter UI.
Canonical ingredients: composer first, primary-tinted interactive controls, neutral/card surfaces, compact learning cards, and a collapsible supporting rail. The rail dimensions are composition constraints, not global spacing tokens.
Describe a repeatable workflow, then let Astro help turn it into a reliable browser or computer-use agent.
Find every Bay Area dental practice accepting new patients, check appointment availability, and summarize the best options.
or start blank
Learn Asteroid
Get the most out of Asteroid
Pro tip
Start broad
Describe the job in plain English first. Astro can help split it into faster scripted steps.
Guides users when there's no data
A realistic dashboard combining all components
Monitor and manage your agent runs
Monday morning · 14 Apr 2026
A quiet weekend — everything held.
Eight agents ran through 2,840 executions while you were away. Three flagged themselves for review, one finished early.
Total Runs
Success Rate
Avg Duration
Active Agents
| Execution | Agent | Status | Duration | Started | Actions |
|---|---|---|---|---|---|
| exec_a1b2c3 | Insurance Scraper | Completed | | | |
| exec_d4e5f6 | Form Filler v3 | Running | | | |
| exec_g7h8i9 | Data Extractor | Failed | | | |
| exec_j0k1l2 | QA Automation | Completed | | | |
Filter · Failed · last 24h
status=failed 24h| Execution | Agent | Status | Duration | Started | Actions |
|---|---|---|---|---|---|
| Nothing failed yesterday. Every one of the 412 runs in the last 24 hours completed clean. You should take the rest of the afternoon off. | |||||
A realistic execution progress feed with collapsible turns, actions, user messages, and chat input
The AI builder assistant conversation with messages, function calls, reasoning blocks, and rich input
Single rule, applied everywhere a moment-in-time is shown. Always relative on screen, single-letter compact, monospace, tabular-nums. Hover for the absolute moment in the user's timezone plus UTC.
See DESIGN.md §Time & Duration for the convention table and rationale.
Relative time · past
Relative time · future
Absolute time
Tooltip contract — local-with-offset + UTC, mirrored on every time component:
Duration
Prefix prop · one phrase, one font
Pass a `prefix` to render a label inline with the time, in the same mono coordinate font. The whole phrase reads as one unit and the screen-reader label includes the prefix automatically. Prefer this over writing the prefix as plain text next to the component.
Do · Don't
Do
Don't
Single-line labels that can grow arbitrarily long — agent names, profile names, integration names, file names, schedule labels — get the same treatment everywhere: cap the visible width, ellipsize, and mount a tooltip with the full label only when the visible text is actually clipped. The conditional tooltip is the whole point — a tooltip on an already-visible label is noise.
Use <TruncatedText> from @workspace/ui/components/truncated-text for the standard case. Drop down to useIsTruncated from @workspace/ui/hooks/use-is-truncated only when you need to control the tooltip's open state externally (e.g. suppress it while a sibling popover is open). See DESIGN.md §Tables → Truncated text for the full rules.
Standalone · single width cap
Tooltip mounts only on the third row — the first two fit inside their cap. Hover any row to confirm; tooltips on already-visible labels are noise.
Inside a TableLink with trailing affordance
Pair with min-w-0 flex-1 on the text and shrink-0 on the trailing icon so the affordance always renders, even when the name is clipped.
Inside a table cell — the canonical use
The third row's tooltip carries the full label without forcing the column wider. Resize the page narrower — the second row eventually starts truncating too, and its tooltip mounts the moment the measurement crosses over.
Three eras of the Asteroid type system, in order. The older columns use forced preview classes so the comparison stays honest regardless of what the current defaults are.
Era 1 · pre-design-system
Geometric single-voice sans + Plex Mono. No serif at all. UI and display shared one font; hero moments read as “agency” rather than editorial.
Era 2 · design-system v1 · early this branch
Two-voice consolidation. Google-Fonts Inter was subsetted (most OT features dead); Freight Big Pro was aspirational but never actually in the Adobe kit, so the serif fell through to the system stack.
Era 3 · now · current system
Canonical InterVariable unlocks the full OT set (ss02, cv05, cv08, zero). Freight Big Pro is now actually in the Adobe kit, so the live serif matches DESIGN.md instead of falling through to the system stack.
Sans voice — Futura PT → Google Fonts Inter → InterVariable
Era 1 · Futura PT
Il1 0O
Illinois · rollback · fall · 0×O
Era 2 · Google Fonts Inter
Il1 0O
Illinois · rollback · fall · 0×O
Era 3 · InterVariable + tuned
Il1 0O
Illinois · rollback · fall · 0×O
Futura is geometric — clean but anonymous, reads more "brand" than "UI". Google-Fonts Inter is the industry workhorse but its stripped build renders flat. InterVariable with the tuned features is recognizably Inter but with measurable character: I gains serifs (cv08), l a tail (cv05), 1 disambiguates from I / l (ss02), 0 is slashed (zero).
Inter character inventory — Era 2 → Era 3
Brand / editorial voice — Futura (same sans) → Iowan fallback → Freight Big Pro
Era 1 · Futura PT (no dedicated serif)
Build agents that run the web.
A quiet weekend — everything held.
“It just works — and then it keeps working.”
Single-font system: display text shared Futura PT with body. Hero moments read "geometric / agency" rather than "editorial".
Era 2 · Iowan / Palatino / Georgia
Build agents that run the web.
A quiet weekend — everything held.
“It just works — and then it keeps working.”
What was actually rendering during the design-system v1 era. The Adobe Fonts kit never contained Freight Big Pro, so font-serif fell through to the system stack.
Era 3 · Freight Big Pro · current
Build agents that run the web.
A quiet weekend — everything held.
“It just works — and then it keeps working.”
The live Adobe Fonts implementation. Same editorial family the design system was always aiming for, now rendering for real instead of dropping to Iowan.
In context — dashboard hero across the three eras
Era 1 · Monday 14 Apr 2026
A quiet weekend — everything held.
Eight agents ran through 2,847 executions while you were away. Three flagged themselves for review, one finished early.
Era 2 · Monday 14 Apr 2026
A quiet weekend — everything held.
Eight agents ran through 2,847 executions while you were away. Three flagged themselves for review, one finished early.
Era 3 · Monday 14 Apr 2026
A quiet weekend — everything held.
Eight agents ran through 2,847 executions while you were away. Three flagged themselves for review, one finished early.
Correction to an earlier note on this page: Recoleta (Latinotype) is not on Adobe Fonts — it's licensed via MyFonts / Fontspring, so the original .preview-recoleta class was silently falling through to Iowan. The four-column comparison below uses Quincy CF as a free, Fontshare- hosted stand-in from the same warm Cooper-derived humanist-serif category.
Typography archive · serif-candidate bake-off
The four candidates we've taken seriously for the editorial / hero voice. Futura PT was the Era 1 single-voice sans (no dedicated serif at all). Freight Big Pro is the live choice and now loads from the Adobe kit referenced in DESIGN.md. Quincy CF is a free Recoleta-alike on Fontshare. Fraunces remains the strongest non-Adobe alternate we considered. Same sample text, same size, same weight — the only variable is the typeface.
Futura PT · Era 1 · sans
Asteroid
Geometric single-voice sans. Expressive but "agency" — reads as brand wordmark, not editorial writing.
Freight Big Pro · current serif
Asteroid
Loaded via Adobe kit dfp3zre as freight-big-pro. The Joshua Darden classic — high-contrast, warm, editorial.
Quincy CF · free · serif
Asteroid
Free via Fontshare. Closest no-license stand-in for Recoleta (which is licensed-only). Same 1970s-Cooper humanist lineage.
Fraunces · alternate candidate
Asteroid
Free, variable, Google Fonts. SOFT / WONK axes dial in warmth. Strong fallback candidate if we ever want a non-Adobe branch of the same editorial idea.
Editorial headline — "A quiet weekend, everything held."
Futura PT
A quiet weekend — everything held.
Freight Big Pro
A quiet weekend — everything held.
Quincy CF
A quiet weekend — everything held.
Fraunces
A quiet weekend — everything held.
Pangram at reading size — the full alphabet + numerals
Futura PT
The quick brown fox jumps over the lazy dog. 1234567890 & 0123 ABCDEFG abcdefg
Freight Big Pro
The quick brown fox jumps over the lazy dog. 1234567890 & 0123 ABCDEFG abcdefg
Quincy CF
The quick brown fox jumps over the lazy dog. 1234567890 & 0123 ABCDEFG abcdefg
Fraunces
The quick brown fox jumps over the lazy dog. 1234567890 & 0123 ABCDEFG abcdefg