Asteroid Design System

Component kitchen sink and token reference

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

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

92890146 v3 gpt-5 claude-opus-4-7-thinking */5 * * * * env:prod NODE_TRANSITIONED 1,248 tok joe@example.com

Not mono

Acme Corp Completed $1,204.50 (stat-card rule wins) Created a moment ago Save changes

Platform type scale — Inter

H1 Page Title
H2 Section Header
H3 Card Title
H4 Subsection
Body Default UI text used in sidebar and nav elements
Body-lg Main content body text for longer paragraphs
Caption SIDEBAR LABEL
Code exec_a1b2c3d4e5f6

Editorial tier — Freight Big Pro (use sparingly)

Hero Welcome to Asteroid.
Title No 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 agent 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 \\ size sm default lg
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 Agents

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 §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-312px
Not canonical — only survives as drift; almost always a mistake
size-3.514px
Inside a Button · Badge · table cell · card meta · TabsTrigger default · ChromeTab · entity section header · stat card header
size-416px
Inside a DropdownMenuItem · CommandItem · SidebarMenuButton · TabsTrigger lg · entity card full tile
size-520px
Page-header action · section anchor · large standalone control
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 size="default" and ChromeTab use size-3.5. size="lg" steps up to size-4 because the tab IS the page title.

size default
size lg
ChromeTabs

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

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

Default Secondary Error Outline

Status — filled, distinct from brand colors

Starting Running Completed
Awaiting
Failed Canceled

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

Sidebar items

AdminUsersEval RunnerModelsUsage

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
PlatformOverviewAgentsExecutionsAdminUsersModelsUsage

Agents

Cross-org agents 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

Chrome-style tabs — active tab uses bg-background against a muted bar surface, with inverted SVG corners

One Chrome-tab visual, two component APIs for different semantic models. Both render identically — pick based on what the tabs represent, not how they should look.
  • 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

Modals

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 · 448

One question, one answer. Destructive footer: outline-Cancel + destructive primary with the verb spelled out.

Form

md · 672

Single-purpose form. Header description carries the intent; FieldGroup runs the body; Cancel + Save in the footer.

Tabbed

lg · 896

The canonical shape: header → persistent identity strip → chrome tabs → body → footer. Mirrors §Page shell tabbed pages.

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.

Sheet (right)

sheet · right

Long-lived, sequential, or sticky-while-page-context-matters. Reach for Sheet over Dialog when the flow has multiple steps.

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

  • sm · sm:max-w-md · 448px — confirms, single-input.
  • md · sm:max-w-2xl · 672px — single-purpose forms, list-picks.
  • lg · sm:max-w-4xl · 896px — tabbed config, multi-section forms. The default for "complex".
  • full · 95vw / 90dvh — workspace views (file manager, diff, debugger). Last resort.

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

Form Elements

Inputs, labels, and form patterns

Text Inputs

Input + Button combinations

Cards

Content containers with header, body, footer

Agent Performance
Last 7 days execution metrics
1,284

Total executions

Success Rate
Completed vs failed
94.2% +2.1%
Active Agents
Currently deployed
12 of 20 slots

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 412 held 38 no-stock 14
Agents
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 agent
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 (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

  • 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.
  • Titletext-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

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

DividedList (editable, with actionSlot)

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

FilterRow (with overflow)

Monitoring:Execution CompletedExecution FailedExecution Started

EmptyRow

Stored Secrets0
No secrets stored

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:

Page shells

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

Overview
API Keys
Billing
New key

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

Overview
Profiles
Integrations
Profiles
Pools
Docs
+ Create

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.

chrome
bg-sidebar · the page frame · used by sidebar AND chrome-mode page strips
canvas
bg-light-muted · the canvas-mode page surface · cards lift off it via border + ~1.5% lightness delta
content
bg-background / bg-card · the white surface · used by chrome-mode panels and by every card on canvas
tabs
ride on the chrome strip · TabsList override !bg-transparent !px-0 so the strip itself is the bar (no nested-bar effect)
actions
on canvas pages: in the slim page bar · on chrome pages: inside each TabsContent (they're tab-scoped)
title
drop when tabs or hero present · sidebar carries the label
subtitle
deprecated · use InfoCard at top of content (canvas) or top of TabsContent (chrome)

The core shell shapes

A · Plain

Top-level routes with one purpose · sidebar carries the label

Overview
Agents
API Keys
Billing
New key
Name Created
prod-api-key
staging-api-key
ci-runner

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

Overview
Agents
Profiles
Integrations
Profiles
Pools
Docs
+ Create

What are Agent Profiles?

Login credentials, 2FA handling, captchas. Saved across runs.

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

Overview
Agents
Executions
Billing

Mon morning · 14 Apr

A quiet weekend — everything held.

8 agents ran 2,840 executions while you were away.

Runs

2,840

Success

96.4%

Avg dur

3m 42s

Active

8

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

Overview
Agents
Executions
Profiles
A
Insurance Scraper
Builder
Executions
92890146
Run
Builder canvas (flat — no card padding)

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.

  • Agent Profiles (Profiles | Pools)
  • Admin → Models / Usage / Users
  • Settings sections

ChromeTabs

Documents / routes

Browser-like. Tabs are openable, draggable, closable. Outer state owns the list.

  • Agent header (Builder + Executions + open execution tabs)
  • Anywhere the user is expected to manage the tab list itself

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.

Create / Start Surface

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.

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 State

Guides users when there's no data

No executions yet
Run your first agent to see execution results here.

Kitchen Sink — Agent Dashboard

A realistic dashboard combining all components

Agent Executions

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

2,840
12% vs last week

Success Rate

96.4%
1.2% vs last week

Avg Duration

3m 42s
8% faster

Active Agents

8
of 12 deployed
Execution Agent Status Duration Started Actions
exec_a1b2c3
Insurance Scraper
Completed 2m 14s
exec_d4e5f6
Form Filler v3
Running 1m 08s
exec_g7h8i9
Data Extractor
Failed 4m 52s
exec_j0k1l2
QA Automation
Completed 5m 33s
Showing 1–4 of 2,840 executions
1

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.

Kitchen Sink — Execution Activity

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

Execution Activity
Running
exec_f8a2c1d9
Navigating to Login Page
Navigating to the login page and entering credentials from the provided profile.
Navigating to Extract Claims
Extracting claim records from the table. Found 24 entries matching the filter criteria.
Applied date filter "Last 30 days"
560ms
Scrolling through table pages to collect all records
Can you also grab the policy numbers from the detail pages?
Sure! I'll navigate into each claim detail page to extract the associated policy number. This will take a moment since there are 24 claims.
Clicked into claim "CLM-2024-0847" detail page
Failed to load detail page for "CLM-2024-0812" — page returned 403
3s

Kitchen Sink — Astro Chat

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

Astro
Improve login flow reliability
Astro
Can you look at my login agent and tell me why it's failing on the 2FA step?

{ "nodes": 5, "edges": 7, "has_2fa_node": true, "2fa_node_timeout": "30s" }

I found the issue. Your agent is failing at the 2FA step because the TOTP input field uses a shadow DOM component that loads asynchronously. The selector #totp-input isn't available until the component hydrates.

Here are two fixes:

  1. Add a wait for selector action before the type action, with a 10s timeout
  2. Switch the selector to pierce the shadow DOM: >>> #totp-input
yaml
# Updated 2FA node configuration
- action: wait_for_selector
  selector: ">>> #totp-input"
  timeout: 10000
- action: type
  selector: ">>> #totp-input"
  value: "{{totp_code}}"
login-page-v2.png
Great, can you apply that fix to my workflow? I've also attached our updated login page screenshot.

Draft saved. 2 nodes updated: "Wait for 2FA Input" (new), "Enter 2FA Code" (modified selector).

Done! I've updated your workflow with both fixes. The new draft adds a Wait for 2FA Input node before the type action and updates the selector to pierce shadow DOM. You can review the changes in the builder and run a test execution when ready.
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 & Duration for 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 agoWed, May 20, 2026, 19:51:50 UTC2026-05-20 19:51:50 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 — 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

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.

Archive

Typography — evolution

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

Sans
Futura PT
Serif
Mono
IBM Plex Mono

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

Sans
Inter (Google Fonts)
Serif
Freight Big Pro → Iowan fallback
Mono
IBM Plex Mono

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

Sans
InterVariable (rsms.me)
Serif
Freight Big Pro
Mono
IBM Plex Mono

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

I l 1 ss02 — disambiguation, data-UI win Il1 · Illinois · fill · 1,284 Il1 · Illinois · fill · 1,284
I cv08 — uppercase I with serifs ID · IAM · Info · IST · Idle ID · IAM · Info · IST · Idle
l cv05 — lowercase l with tail (humanist) rollback · fall · bulk · bills rollback · fall · bulk · bills
0 zero — slashed zero, distinct from O 0x3AF · Org-0 · 0/100 · 20°C 0x3AF · Org-0 · 0/100 · 20°C
a cv11 — single-story a, already on, kept agent · rollback · anagram agent · rollback · anagram
“ ” , ss03 — round quotes & commas, already on, kept “it works”, then keeps working “it works”, then keeps working

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

Four voices side-by-side

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