Asteroid Design System

Primitives, surfaces and tokens. The spec is DESIGN.md.

Colors

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

queued

queued-light

destructive

destructive-light

Typography

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

92890146v3gpt-5claude-opus-4-7-thinking*/5 * * * *env:prodNODE_TRANSITIONED1,248 tokjoe@example.com

Not mono

Acme CorpCompleted$1,204.50(stat-card rule wins)Created a moment agoSave changes

Type roles: pick the role, not a size

type-titleInvoice reconciler
type-sectionBrowser
type-nameRefund window check
type-labelStart URL
type-helpRuns start from this URL.
type-captionEdited 3h ago
type-capsStatus
type-overlineProfile
type-proseSign in with the profile's secrets, then open the invoices tab.
Monoexec_a1b2c3d4e5f6

Editorial tier — Freight Big Pro (use sparingly)

HeroWelcome to Asteroid.
TitleNo executions yet.
Quote“It just works — and then it keeps working.”

In-context example — empty state

Executions

Nothing to see here — yet.

Kick off your first workflow run and this page will fill up with the interesting stuff.

Serif headline + Inter supporting copy + mono overline. The three voices playing together.

Buttons

Button face is lifted from flow, shadow at flow position — sinks on hover, presses on active

Variants × Sizes

variant \\ sizesmdefaultlg
default
destructive
outline
ghost
link

With Icons

Icon Only

States

rest
disabled
loading pattern
rest
disabled
rest
disabled

Common Patterns

Dialog Footer — confirm / cancel

Destructive Confirmation

Toolbar — mixed actions

Tab Toggle — ButtonGroup variant switching

Page Header — primary + secondary actions

My workflows

Split Button — primary action + dropdown

Iconography

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 §Iconographyfor 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 only for tiny glyphs inside chips, pills and the drawer Next pill. size-6 is reserved for rare page-header chrome.

size-312px
Tiny inline glyph in a 10–12px element: EntityChip, Pill, the drawer Next pill arrow
size-3.514px
Inside a Button · Badge · table cell · card meta · ShellRail row · TabsTrigger · NavTabsTrigger · entity section header · stat card header
size-416px
Inside a DropdownMenuItem · CommandItem
size-520px
Page-header action · section anchor · large standalone control · entity card full tile
size-624px
Page-header brand chrome (rare — logo lockups, 1.5px stroke)
size-832px
Empty-state hero (inside EmptyMedia variant=icon, wrapped in size-10 tile)

2 · 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 and line tabs (NavTabsTrigger, PageTabs) use size-3.5. One tab size everywhere.

Tabs · flat
NavTabs (line)

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.

DropdownMenu
Command

5 · In entity and stat cards

Entity full tile (size-10 rounded-lg) holds size-5; compact (size-7 rounded-md) holds size-3.5. A stacked drawer header uses the same tile as the full card. Stat card header icon is size-3.5, right-aligned in the muted label row.

Entity full tile
Entity compact tile
Active runs
12
4 held · 2 errored

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.

Do
Last run

Bare time component, or prefix prop for the label. Mono font does the work.

Don't

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-foreground

8 · 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.

No agents yet
Create your first agent to start running workloads.

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.

Do
Active

Status badge tint IS the signal — one glyph or none.

Don't
Active

Status-tinted badge AND a same-meaning status glyph next to it.

Do

Let the Button primitive supply size-3.5 and gap-1.5.

Don't

Overriding size-4 fights the canon. The Button no longer uses size-3 by default — trust it.

Do
Succeeded

Semantic tokens: text-success, text-destructive, text-warning, text-primary, text-admin.

Don't
Succeeded

Raw palette colors. No text-green-*, no text-red-*, no hex literals.

Do
12mConfig

One size per visual group — size-3.5 here.

Don't
12mConfig

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).

AdminAdminAdmin

12 · Loaders

Loader2 animate-spin in a status position inherits the surrounding size canon. Keep it the same size as the icon it replaces.

Loading Reconnecting…

Button Groups

Grouped actions with shared offset shadow

Horizontal — outline

Horizontal — alignment

Vertical

With Text & Separator

Page 1 of 10

Variant groups

Badges

Tinted backgrounds with colored text — quiet labels, not loud fills

Variants

DefaultSecondaryErrorOutline

Run status · StatusBadge with the run-status map

QueuedStartingRunningCompletedPausedAwaitingFailedCancelled

Tint and label carry the state. The only glyph is the live dot on states where work is happening now.

Admin Indicators

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

--admin

Solid amber. Use on raised admin buttons; rarely as a surface.

bg-admin/10

Tinted background — strip, hover/active states, ghost surface.

border-admin/30

Border tint for the strip and outlined admin elements.

Element coloring (primary pattern)

Buttons

Rail rows

◇Users◇Models◇Usage

Properties & links

Internal cost$0.0418
Bifrost tracebf-7d2c…
Routing overrideasteroid-max

Form-control primitives (admin prop)

<Input admin />
<SelectTrigger admin />
Tracing

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

Adminsize="xs" — fallback only
Adminsize="sm" — rare
Adminsize="md" — page header

Three contexts

Property

Element coloring is the primary signal — render the value in text-admin.

Internal cost$0.0418
Latency p99412 ms

Control

The button paints itself amber. No badge needed.

Page header

One md badge marks the whole page identity.

Model Management

Admin

Admin mode toggle (live demo)

Admin mode
◇Home◇Workflows◇Executions
◇Users◇Models◇Usage

Workflows

Cross-org workflows visible — internal toolbar above
Toggle Admin mode for this preview

AdminPageGate (toggle-off interstitial)

Admin mode is off

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.

Tabs

Line tabs navigate a page from the canvas header. A SegmentedControl switches sub-views. Framed chrome tabs only nest inside modals and panels.

Three tools. Pick by where the tabs sit, not by look.
  • Line tabs: PageTabs / NavTabs · a page's own navigation, in the canvas header nav row. PageTabs (Tabs appearance="line") switches in-page panels. NavTabs is stateless; triggers render router links.
  • SegmentedControl · sub-views of the active tab, at the right end of the nav row.
  • Framed Tabs (chrome, default) · nested tabs inside a modal or a panel. Never page navigation, never inside an entity drawer.

Page navigation · line tabs in the canvas header

PageTabs (Tabs appearance="line") · Logins

Profiles on the canvas.

NavTabs + SegmentedControl · PageDetailHeader

Health · table view

Sub-views · SegmentedControl

SegmentedControl size="default"

SegmentedControl size="sm"

Nested tabs · framed Tabs

The default chrome look: active tab with stroked inverted corners, content panel with a full border and one shared drop-shadow. Use it inside a modal or a panel. Suppress the shadow with framedShadow={false} when the strip and panel cannot share a DOM subtree.

Tabs framed (inside a panel on bg-light-muted)

Border traces the active tab through the inverted corners and wraps the content card. One shared drop-shadow.

Flat nested tabs · framed={false}

For tabs inside an existing card or section, where a bordered tongue card would double up. The strip gains a bg-muted plate; the consumer shapes the panel.

With icons

Executions content

Text only

Overview content

With badges

All logs

With disabled tab

Active content

Forms and type roles

A form is sections of fields; a field is one label and one control. FormField encodes the anatomy and the aria wiring. Type roles replace ad-hoc sizes and weights. See DESIGN.md §Forms and §Type roles.

Field states

We POST each event here.

(optional)

Admin only.

Placeholders

Yesjane@acme.comFormat example
Yeshttps://portal.acme.comFormat example
YesSearch workflows…Search field, Unicode ellipsis
NoWorkflow nameRepeats the label
NoEnter your API tokenInstruction; use the description
Noe.g. 25 (optional)No e.g.; optional is a prop

FormSections · FormSection · FormField

Browser

Where the agent runs.

Runs start from this URL.

(optional)

Execution

(optional)

How many runs start at once.

Posts to the connected Slack channel.

Label, optional marker and info tip share one row. The description sits between label and control, so the error stays next to the value. Try a concurrency above 50.

Surfaces

Drawers, the graph inspector and large dialogs share one panel anatomy. Drawers stack numbered sections. Status uses one badge with a map per domain. Pick the surface with DESIGN.md §Surfaces.

The rules · first match wins

  1. R1

    Is the user confirming something destructive, irreversible or costly?

  2. R2

    Is the system showing a value the user can see only once?

  3. R3

    Is the user changing exactly one value already on screen?

  4. R4

    Is the user in a workspace, configuring the object they selected?

  5. R5

    Is the user creating, viewing or editing a configuration entity?

  6. R6

    Is it a one-screen task that is not the page's main job?

  7. R7

    Nothing above matched.

The seven surfaces

SurfaceBlocksURLCommits
Inline editNoNoEnter, blur or change
PopoverNoNoChange or Apply
Alert dialogYesNoOne verb
DialogYesNoOne verb
DrawerYes?new= ?id= &section= &edit=1One save bar, drafts kept
Docked panelNoSelectionDraft, Publish commits
Route—YesPer section

One blocking layer

Only an alert dialog may open over a Dialog or drawer, to confirm a destructive action inside it. Creating a dependency slides in within the drawer section, or swaps a Dialog's body. Menus and popovers are not layers.

Entity assignments

EntityViewCreateEditDestroyToday
WorkflowRouteRoute /createInline name, route tabsAlert dialogOK
Workflow nodeDocked panelGraph menuDocked panelAlert dialogMigrates
ExecutionRouteRun: Dialog lg—Alert dialogOK
ScheduleDrawerDrawerDrawerAlert dialogMigrates
BatchRouteRoute wizard—Alert dialogOK
Profile, profile groupDrawerDrawerDrawerAlert dialogOK
SecretProfile sectionSlides in a sectionSlides in a sectionAlert dialogOK
Secret templateDrawerDrawerDrawerAlert dialogOK
EnvironmentDrawerDrawerDrawerAlert dialogOK
IntegrationDrawerDrawerDrawerAlert dialogMigrates
API keyListDrawer—Alert dialogMigrates
Org memberListDrawerInline roleAlert dialogMigrates
TagPopoverInlineInlineAlert dialogMigrates
ClassPass venueRouteDrawerRoute tabsAlert dialogMigrates

Full table with the extra actions (run, retry, show once) in DESIGN.md §Surfaces → Entity assignments.

Docked panel · graph inspector

Browser agent

Runs start from this URL.

PanelHeader with back, type overline, docs link and an inline editable name. Hover the name to edit; Enter commits, Escape reverts.

Drawer · numbered sections, edit mode

Profile
Billing portal · US
Used by 4 workflows
Country
United States
OS
Windows

One list, one section open at a time. Header Edit turns every section into its form; one save bar. Create uses the same sections, with a Next pill on the divider below the open section.

Drawer · view

Same header grammar as the inspector, inside a right sheet.

Steps · routes only

For route wizards such as Batch upload. Completed steps are clickable; upcoming ones are not. Never inside a drawer.

DetailGrid · hint, info, description

Region
us-east
Sticky IP
On· one dedicated US IPFixed at creation
Block ads
Off
Skip media
Off
Shared use
One run at a time
Timeout
min
Advanced tracing
Close popups
Needs Block ads
Profiles
2 profiles
Shared with 3 other workflows on this environment.

Hint: a few words on the value, or why a control is disabled. Info: what the field means, behind the ?. Description: the rare line a user must read. A boolean shows its state dot before the key. A control replaces the value through trailing; units sit beside it.

StatusBadge · status map

QueuedRunningNeeds reviewCompletedFailedCancelledretrying later

One map per domain. Only live states carry a glyph. Unknown values render neutral, humanised.

Empty · size sm

No schedules yet
Schedules run this workflow on a timer.

Concept icons

add
edit
delete
close
more
moreVertical
copy
externalLink
search
filter
settings
back
disclosure
dropdown
drag
refresh
download
upload
run
stop
history
show
hide
locked
help
info
warning
error
success
done
loading

Import from @workspace/ui/lib/icon-concepts. Entity glyphs (for example profile) live in the platform entity registry.

Entity drawer: Logins

The complete R5 drawer for one entity, built like the Logins page in the product. Create, view and edit share four numbered sections. Edit in the header edits them all behind one save bar. The address bar shows the URL each mode writes.

/logins
2 profiles
NameSecretsSticky IPUsed by
Billing portal · US2On4
Claims desk · UK1Off1

Modes and URLs

ModeURLTry it
Create?new=profile&section=…Create a login profile. Numbered sections. A Next pill on the divider below the open section opens the next one. Open any section directly. No Review step.
Validate—Clear the name and open another section: Details turns red. Create opens the first section with a problem.
View?id=…&section=…Click a row. Every section reads as a summary. One section is open at a time.
Edit…&edit=1Edit in the header turns every section into its form. One save bar at the bottom.
Instant section—Secrets act at once, in view and edit. New secret slides in over the list and slides back when done.
Create-only field—Sticky IP is set at create. Afterwards its section shows it read-only with the reason.
Destroy—Delete lives in the header menu, hidden while editing. It confirms with an alert dialog over the drawer.

Same sections, every mode

#SectionSaves
1Details / OverviewDraft
2SecretsInstant
3Email inboxDraft
4Sticky IPCreate-only; read-only after

Surfaces in practice

The rules applied to real entities. Each frame is a working slice of the platform built only from the primitives above. Sheets, dialogs and alert dialogs open over the whole page, as they do in the product.

R4 Docked panelR3 Inline editR5 Drawer from elsewhere

Select a node; the panel follows. Rename it in the header and watch the canvas. The folder button swaps the panel body to files. New environment opens the environment drawer in create mode and selects the result.

Invoice reconciler · builder
Browser agent

What the agent does on this step.

Dialogs

How to build a Dialog once the rules chose one (R1 confirms, R2 show once, R6 tasks). One shell, four sizes set by content: sm 0–1 input, md 2–8 fields, lg 9–16 fields in 2–4 sections or tabs, full files, diff and debugger only. See DESIGN.md §Modals.

Alert dialog · R1

sm · 448

Destructive, irreversible or costly. Title asks the question, body states the blast radius, the action names the verb. Cancel keeps the thing.

Task · R6

md · 672

A one-screen task that is not the page's job. 2–8 fields, one section, no scroll. FormField runs the body; Cancel + verb in the footer.

Tabbed task · R6

lg · 896

An R6 task with 2–4 sections: header, persistent identity strip, chrome tabs, body, footer. Tasks only. Entities open in their drawer (R5), never in a tabbed dialog.

List-pick

md · 672

Search + scroll + multi-select inside a DialogBody. The toolbar pins to the top, the list scrolls, the count summary lives in the footer.

Loading + error

md · 672

Same shell, two states. Loading uses skeleton inside the body. Blocking errors are an in-body banner — never a toast.

Right-action + split footer

lg · 896

The Run workflow shape: Pre-fill Select sits in the header right-action slot; Schedule and Advanced cluster left in the footer; Cancel + Run cluster right.

Size by content

  • sm · 448px. 0–1 input. Show-once values, one choice with a sentence.
  • md · 672px. 2–8 fields, one section, no tabs, no scroll.
  • lg · 896px. 9–16 fields in 2–4 sections or tabs, or a list-pick with preview.
  • full. Files, diff, debugger. Nothing else.

Anything else (sm:max-w-[600px], sm:max-w-5xl, custom max-h-[Nvh]) is a smell.

Stat cards

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

Executions
2,840
2,738 completed · 68 failed · 34 canceled
label
sentence case · text-[11px] font-medium text-muted-foreground
icon
size-3.5 · neutral by default; one status accent allowed
value
text-xl font-semibold tabular-nums · always tabular so values align across the strip
helper
breakdown / context · text-xs text-muted-foreground · color words, not the whole tile
shell
gap-1 border py-3 shadow-none · flat, no offset shadow — tiles are inert

Strip — 4 tiles · /overview

Executions
2,840
2,738 completed · 68 failed
In progress
7
3 held for review
Outcomes
booked 412held 38no-stock 14
Workflows
12
3 updated today · 5 saves

Strip — 3 tiles · /executions

Executions
412
412 completed
In progress
0
nothing live
Outcomes
no outcomes yet

Loading — full strip skeleton

Future use — billing strip (same shell, different content)

Spend
$1,284.40
this period · 62%of cap
Plan
Scale
renews 14 May
Top workflow
Insurance Scraper
$612.18 · 48% of spend

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).

Entity cards

How a domain entity (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

  • Icon tile — neutral muted square. The only place a brand color or product logo is allowed in the header.
  • Overline (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.
  • Title —text-base font-semibold, truncating. The dominant element of the header now that identity info is split across overline + title only.
  • Meta (optional) — supplemental identity line below the title. Only used when the entity has a non-audit fact more identifying than its name (e.g. Schedule's cron description). Most entities skip this entirely.
  • Actions — right-aligned dropdown trigger.
  • Body — a vertical stack (space-y-4) of named section primitives. This card uses PropertyList + FilterRow.
  • Footer — thin top border + small muted line at the bottom. All audit lives here: 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"

URL
https://webhook.site/dddddddd
Version
v2
Headers
0

PropertyList density="inline"

version
v2
headers
0
url
webhook.site/ddd…

PillRow

Features
Captcha SolverPersistent IPCacheAd BlockerPopup BlockerPopups as TabsMedia Blocker

DividedList

Secrets3
REPLY••••••••
Login
MFA Token••••••••
2FA
API_KEY••••••••
Secret

DividedList (editable, with actionSlot)

Secrets3
REPLY••••••••
Login
MFA Token••••••••
2FA
API_KEY••••••••
Secret

FilterRow (with overflow)

Monitoring:Execution CompletedExecution FailedExecution Started

EmptyRow

Secrets0
No secrets attached

EntityCardFooter (default)

Edited >60s after create — meta already shows Created.

Edited

EntityCardFooter (showCreated)

Custom-meta entity (e.g. Schedule) — footer carries Created too.

EditedCreated

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.

Linked to:

People and organisations

Every mention of a user or an org uses one chip. A user is a circle and an org is a rounded square, so the shape says which one it is. The colour comes from a hash of the email for a user (the ID when there is no email) and of the ID for an org, so the same person or org has the same colour on every page. A profile picture replaces the initials. API keys, services and unnamed people use a neutral tile. The lead cell of a row uses the same avatar with the name and one muted line.

Avatar sizes
Users
Glenn SimOriane Duvalpriya@trifetch.comTom Okafor
Orgs
MoshDartford and Gravesham NHS TrustTrifetchMosh Test
System
AsteroidNightly syncAstroFormer member
Plain, in tables
Glenn SimMosh
In a sentence

Triggered by Glenn Sim in Mosh two minutes ago.

Hover card
MemberWorkspaceInvited by
Glenn Sim
glenn@getmosh.com
MoshOriane Duval
Oriane Duval
oriane@getmosh.com
Dartford and Gravesham NHS Trustpriya@trifetch.com
priya@trifetch.com
No name set
TrifetchTom Okafor
Tom Okafor
tom@asteroid.ai
Mosh TestGlenn Sim

Page shell

One layout: the shell (apps/platform/src/components/shell). The rail sits on the bg-sidebar frame. An inset rounded canvas holds the docked Astro thread and the page. Every page but Home and Astro's page opens with the canvas header: a context row (crumbs + actions) and a nav row (line tabs + controls). Routes render inside it through Page, PageTabs and PageDetailHeader. The rail names the page, so the title slot is usually empty.

Anatomy · AppShell with the thread docked

Search
HomeCatalog
Insurance Scraper
Builder
Deploy
Executions
LoginsEnvironmentsConnections

Recents

Fix the login stepWhy did 92890146 fail?
Astro

Why did 92890146 fail?

The login step timed out. The portal now shows a cookie banner over the sign-in button. I can add a step that dismisses it.

Ask Astro…
Builder graph · flat, full-bleed
frame
bg-sidebar · the page behind the rail and the canvas. Mobile swaps the rail for ShellTopBar + a sheet.
rail
ShellRail · rows text-xs, idle text-muted-foreground hover:bg-accent, active bg-primary/5 text-primary font-medium. No headings: dividers split the sections. The workflow scope pill heads Build / Run / Monitor and tints their frame bg-primary/[0.04]. Recents lists chats. Folds to icons.
canvas
AppShell inset lg:m-2 lg:rounded-xl lg:border. A ResizableHandle splits it between the thread and the page canvas bg-light-muted. Cards bg-card lift off it.
header
ShellCanvasHeader on DetailHeader (bg-light-muted border-b). Context row h-9: crumbs + actions. Nav row h-9, border-t: line tabs left, controls right. Global pages collapse to one row. Home has none.
thread
ShellThread on bg-background, under ShellAstroHeader. Docks left by default, can swap sides, go full screen or hide. Hidden, the header shows an Astro opener.
tabs
Page-level: line tabs in the nav row (PageTabs / NavTabs). Framed chrome Tabs only nest inside modals and panels.

The canvas page shapes

A · Plain

Connections, Environments, API keys

Search
HomeCatalog
WorkflowsAll
Builder
Deploy
Executions
LoginsEnvironmentsConnections

Recents

Fix the login stepWhy did 92890146 fail?
Astro
SalesforceConnected 2d ago
SlackConnected 7d ago
GmailConnected 21d ago

One header row. The rail names the page, so the row holds the Astro opener and the primary action at its end. The page body is cards on the canvas. Page drops its title when the header names the route.

B · Tabbed

Logins, Insights, Admin → Models

Search
HomeCatalog
WorkflowsAll
Builder
Deploy
Executions
LoginsEnvironmentsConnections

Recents

Fix the login stepWhy did 92890146 fail?
Astro

PageTabs → the header row. PageTabsList portals line tabs into the header's tab slot. Tab-scoped actions sit at the row's end. Content stays on the canvas. No chrome strip, no inverted corners.

C · Home

/ (ShellHome)

Search
HomeCatalog
WorkflowsAll
Builder
Deploy
Executions
LoginsEnvironmentsConnections

Recents

Fix the login stepWhy did 92890146 fail?

Good morning, Ed.

8 workflows ran 2,840 executions overnight.

Recent workflows

Insurance ScraperRan 4m ago
Claims IntakeRan 1h ago

No canvas header. Home is its own header: an editorial greeting, then Recent workflows, Work from your tools, Learn. Astro's page shares its frame (SHELL_PAGE_FRAME).

D · Detail

Organisation, Batch, workflow pages

Search
HomeCatalog
WorkflowsAll
Builder
Deploy
Executions
LoginsEnvironmentsConnections

Recents

Fix the login stepWhy did 92890146 fail?

PageDetailHeader → both rows. Crumbs start at the list you came from; badges and actions end the context row. NavTabs for the entity's sections, a SegmentedControl for the active tab's sub-views. Workflow pages build the same two rows from the URL (see Anatomy).

Title rule

Page has…Title slotSubtitle slotExamples
PageTabsDrop. The rail names the page. Tabs sit in the header row.Drop. UseInfoCard inside tab content if needed.Logins, Insights, Admin → Models
Editorial greetingDrop. No canvas header. The greeting is the title.Drop. Supporting prose carries it.Home, Astro's page
Start compositionDrop. Split editorial prompt replaces it.Drop. Primary composer and rail copy carry the context.Create / get started
PageDetailHeaderLast crumb · the resource nameDrop. Badges sit after the crumb.Workflow, Execution, Batch, Organisation
Plain route, no tabsAutomatic. Page hides it when the header names the route.Optional. Treat as a smell.Connections, Environments, API keys

Tabs: pick the API by where the tabs sit

PageTabs · NavTabs

Page navigation

Line tabs in the canvas header's nav row. PageTabs (Tabs appearance="line") switches in-page content. NavTabs is stateless: each trigger renders a router Link.

  • Logins (Profiles | Profile groups | Secrets | Requests)
  • Builder (Graph | Files), Organisation detail

SegmentedControl

Sub-views

Which view of the active tab. Sits at the right end of the nav row, via DetailHeaderTabs views.

  • Table | Chart, Graph | Browser

Tabs (chrome, default)

Nested tabs

Framed tongue-card tabs. Only inside a modal or a panel, never as a page's own navigation. Never inside an entity drawer.

  • Task dialogs with sections
  • Settings sections inside a card

Create / Start Surface

The Start page shape: a canvas-first decision surface for creating a workflow, onboarding a user, or choosing a first template. 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.

Build a new agent...

Describe a repeatable workflow, then let Astro help turn it into a reliable browser or computer-use agent.

Astro

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

2/4
01Build first agent
02Run a test execution
03Add credentials

Pro tip

Start broad

Describe the job in plain English first. Astro can help split it into faster scripted steps.

Empty states

Say what is missing and what to do. Page scale on an empty page or tab;size="sm" inside tables, cards and panels. Filtered-to-nothing offers to clear filters.

No executions yet
Run a workflow to see its executions here.
No schedules yet
Schedules run this workflow on a timer.
No executions match these filters
Status: Failed · Last 24 hours

Skeletons

Loading chrome that mirrors the final layout. Bars use bg-muted and a 1.5s opacity pulse. Never a single tall blob. Never primary-tinted placeholders. See DESIGN.md §Skeletons.

Primitive. bg-muted, 1.5s opacity pulse

Option card. Same shell as OptionCard

Data table. Card, muted header, compact rows, pagination footer

Loading

Collection page. Action row, launcher doors, table

Loading
Loading

Kitchen Sink — Workflow Dashboard

A realistic dashboard combining all components

Workflow executions

Monitor and manage your workflow runs

Monday morning · 14 Apr 2026

A quiet weekend — everything held.

Eight workflows ran through2,840 executionswhile you were away. Three flagged themselves for review, one finished early.

Total Runs

2,840
12%vs last week

Success Rate

96.4%
1.2%vs last week

Avg Duration

3m 42s
8%faster

Active workflows

8
of 12 deployed
ExecutionWorkflowStatusDurationStartedActions
exec_a1b2c3
Insurance Scraper
Completed2m 14s
exec_d4e5f6
Form Filler v3
Running1m 08s
exec_g7h8i9
Data Extractor
Failed4m 52s
exec_j0k1l2
QA Automation
Completed5m 33s
Showing 1–4 of 2,840 executions
1

Filter · Failed · last 24h

status=failed 24h
ExecutionWorkflowStatusDurationStartedActions

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.

Kitchen Sink — Execution Activity

A realistic execution progress feed with collapsible turns, actions, user messages, and chat input

Execution Activity
Running
exec_f8a2c1d9

Kitchen Sink — Astro Chat

The AI builder assistant conversation with messages, function calls, reasoning blocks, and rich input

Astro
Improve login flow reliability
12.4k chars

Time & Duration

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 & Durationfor the convention table and rationale.

Relative time · past

now
5 seconds ago
5 minutes ago
3 hours ago
2 days ago
3 weeks ago
4 months ago
2 years ago

Relative time · future

in 5 minutes
in 2 days
in 3 months

Absolute time

short
long

Tooltip contract — local-with-offset + UTC, mirrored on every time component:

4 hours agoMon, Nov 02, 2026, 07:16:30 UTC2026-11-02 07:16:30 UTC

Duration

42 s42s
3760 s · 1h 02m 40s1h 02m 40s
7245 s · 2h 00m 45s2h 00m 45s
precision="minute"1h 02m
live ticker · started 7 s ago7s
ms scale · 120 ms120ms

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.

CreatedEditedStartedNext runJoined

Do · Don't

Do

  • Mono, tabular-nums, single-letter compact:
  • Months usemo(neverm):
  • Use theprefixprop so prefix + value share one font:Edited
  • Hover any timestamp to confirm tooltip shows local-with-offset + UTC.

Don't

  • Render time in proportional sans:5 min ago
  • Mix fonts on the prefix:Edited 12m ago(use theprefixprop)
  • Drop theagosuffix:5m(no tense, reads as a duration)
  • Usemfor 4 months ago:4m ago— that's literally 4 minutes ago
  • Skip the tooltip — every visible time must expose the absolute moment on hover.

Truncated text

Single-line labels that can grow arbitrarily long — workflow 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

10 charsReturns QA
37 charsRefund window verification — backfill
94 charsReplatform launch QA · checkout · returns · loyalty · billing · all locales · regression sweep

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

ExecutionAgentNotes
exec_1234Returns QAWidth is set by the column, not by the label.
exec_1235Refund window verification — backfillWidth is set by the column, not by the label.

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.