Postrboard CSS 2.0

A CSS framework for product interfaces. It gives you components, design tokens, and accessible states out of the box, and lets you change the color, shape, density, and typography without editing any CSS.

Install
npm install postrboard-css
<link rel="stylesheet" href="https://burkeholland.github.io/postrboard-design/postrboard.min.css">

The GitHub Pages URL tracks the latest published framework build.

Theme options

These controls change this whole page. The same attributes go on <html> in your product, or on any wrapper when only one region should change.

Tokens

CSS variables for color, spacing, shape, and motion. Use the named roles like --surface and --text instead of raw color values, so light and dark mode both work.

--bgPage background
--surfacePanel background
--surface-subtleGrouped rows
--textBody text
--accentVivid accent, for fills without text
--accent-surfaceAccent behind white text
--accent-inkInk for a glyph on --accent
--focus-colorFocus ring

Three fonts, three jobs.

IBM Plex Sans is for interface text. Plex Serif is for long-form reading. Plex Mono is for code, IDs, and numbers. Setting data-type changes which one headings use.

Spacing
--space-1 through --space-8
Corners
--radius-sharp --radius-compact --radius-soft
Motion
--duration-*, all with an ease-out curve
Focus
A solid outline plus a soft halo behind it

Components

Every component the framework ships, with the markup to copy. Paste it as written; the class names are the API. The same list is in components.md and registry/index.json for agents.

Layout

app-shell

A full-height sidebar and main region, the standard frame for an application.

Ready
12
Building
4
Failed
1
<div class="grid-sidebar is-app-shell">
  <aside class="app-sidebar">
    <a class="brand" href="/"><span class="brand-mark">R/</span> relay</a>
    <nav class="sidebar-nav" aria-label="Sections">
      <a href="/" aria-current="page">Overview</a>
      <a href="/deploys">Deploys</a>
      <a href="/logs">Logs</a>
    </nav>
  </aside>
  <main class="app-main" id="main">
    <header class="page-header">
      <div class="page-header-main">
        <h1 class="page-title">Overview</h1>
        <p class="page-summary">Live services in us-east-1.</p>
      </div>
    </header>
    <dl class="stat-strip">
      <div class="stat-item"><dt class="stat-label">Ready</dt><dd class="stat-value">12</dd></div>
      <div class="stat-item"><dt class="stat-label">Building</dt><dd class="stat-value">4</dd></div>
      <div class="stat-item"><dt class="stat-label">Failed</dt><dd class="stat-value">1</dd></div>
    </dl>
  </main>
</div>

container

Page width and gutters, in three measures.

container-sm

720px prose measure

container

1200px default page width

container-lg

1440px dense tools

<div class="stack">
  <div class="container-sm">
    <div class="panel">
      <div class="panel-content stack stack-sm">
        <span class="text-meta">container-sm</span>
        <p class="text-body">720px prose measure</p>
      </div>
    </div>
  </div>
  <div class="container">
    <div class="panel">
      <div class="panel-content stack stack-sm">
        <span class="text-meta">container</span>
        <p class="text-body">1200px default page width</p>
      </div>
    </div>
  </div>
  <div class="container-lg">
    <div class="panel">
      <div class="panel-content stack stack-sm">
        <span class="text-meta">container-lg</span>
        <p class="text-body">1440px dense tools</p>
      </div>
    </div>
  </div>
</div>

grid-asymmetric

Two columns weighted 2:1. Use grid-asymmetric-reverse to lead with the narrow column.

Deploy notes

This revision only changes the health probe path. No schema migration runs.

region

us-east-1

owner

Platform

<div class="grid-asymmetric">
  <div class="panel">
    <div class="panel-content stack stack-sm">
      <p class="text-card">Deploy notes</p>
      <p class="text-body text-muted">This revision only changes the health probe path. No schema migration runs.</p>
    </div>
  </div>
  <div class="panel">
    <div class="panel-content stack stack-sm">
      <div>
        <p class="text-meta">region</p>
        <p class="text-body">us-east-1</p>
      </div>
      <div>
        <p class="text-meta">owner</p>
        <p class="text-body">Platform</p>
      </div>
    </div>
  </div>
</div>

grid-auto

As many equal columns as fit, wrapping automatically.

us-east-1

3 replicas

us-west-2

2 replicas

eu-west-1

2 replicas

ap-south-1

1 replica

<div class="grid-auto">
  <div class="panel">
    <div class="panel-content stack stack-sm">
      <p class="text-card">us-east-1</p>
      <p class="text-meta">3 replicas</p>
    </div>
  </div>
  <div class="panel">
    <div class="panel-content stack stack-sm">
      <p class="text-card">us-west-2</p>
      <p class="text-meta">2 replicas</p>
    </div>
  </div>
  <div class="panel">
    <div class="panel-content stack stack-sm">
      <p class="text-card">eu-west-1</p>
      <p class="text-meta">2 replicas</p>
    </div>
  </div>
  <div class="panel">
    <div class="panel-content stack stack-sm">
      <p class="text-card">ap-south-1</p>
      <p class="text-meta">1 replica</p>
    </div>
  </div>
</div>

grid-centered

A single measured column, centred in the page.

Confirm the rollback target

Production traffic moves to v2.8.0. Active sessions stay open until they expire.

<div class="grid-centered">
  <div class="panel">
    <div class="panel-content stack stack-sm">
      <p class="text-card">Confirm the rollback target</p>
      <p class="text-body text-muted">Production traffic moves to v2.8.0. Active sessions stay open until they expire.</p>
    </div>
  </div>
</div>

grid-holy-grail

A full-width header and footer around three columns.

header · api-gateway
navOverviewRevisions
mainWork surface for the selected revision.
asideChecksOwners
footer · last synced 12s ago
<div class="grid-holy-grail">
  <div class="panel"><div class="panel-content"><span class="text-meta">header</span> · api-gateway</div></div>
  <div class="panel"><div class="panel-content stack stack-sm"><span class="text-meta">nav</span><span class="text-body">Overview</span><span class="text-body">Revisions</span></div></div>
  <div class="panel"><div class="panel-content stack stack-sm"><span class="text-meta">main</span><span class="text-body">Work surface for the selected revision.</span></div></div>
  <div class="panel"><div class="panel-content stack stack-sm"><span class="text-meta">aside</span><span class="text-body">Checks</span><span class="text-body">Owners</span></div></div>
  <div class="panel"><div class="panel-content"><span class="text-meta">footer</span> · last synced 12s ago</div></div>
</div>

grid-masonry

Three columns that flow by height rather than by row.

Probe path

/healthz

Rollback notes

Keeps the previous revision warm for one hour so a second rollback is instant. Sessions are not drained.

Use only after checks are green on the target revision.

Owner

Platform

Canary

5% of production traffic for fifteen minutes, then promote or abort.

Region

us-east-1

<div class="grid-masonry">
  <div class="panel">
    <div class="panel-content stack stack-sm">
      <p class="text-card">Probe path</p>
      <p class="text-body text-muted">/healthz</p>
    </div>
  </div>
  <div class="panel">
    <div class="panel-content stack stack-sm">
      <p class="text-card">Rollback notes</p>
      <p class="text-body text-muted">Keeps the previous revision warm for one hour so a second rollback is instant. Sessions are not drained.</p>
      <p class="text-body text-muted">Use only after checks are green on the target revision.</p>
    </div>
  </div>
  <div class="panel">
    <div class="panel-content stack stack-sm">
      <p class="text-card">Owner</p>
      <p class="text-body text-muted">Platform</p>
    </div>
  </div>
  <div class="panel">
    <div class="panel-content stack stack-sm">
      <p class="text-card">Canary</p>
      <p class="text-body text-muted">5% of production traffic for fifteen minutes, then promote or abort.</p>
    </div>
  </div>
  <div class="panel">
    <div class="panel-content stack stack-sm">
      <p class="text-card">Region</p>
      <p class="text-body text-muted">us-east-1</p>
    </div>
  </div>
</div>

grid-sidebar

A fixed-width rail beside a fluid main column.

sections

Overview

Retention

Access

Retention policy

Keep failed job logs for 30 days, then drop them.

<div class="grid-sidebar">
  <div class="panel">
    <div class="panel-content stack stack-sm">
      <p class="text-meta">sections</p>
      <p class="text-body">Overview</p>
      <p class="text-body">Retention</p>
      <p class="text-body">Access</p>
    </div>
  </div>
  <div class="panel">
    <div class="panel-content stack stack-sm">
      <p class="text-card">Retention policy</p>
      <p class="text-body text-muted">Keep failed job logs for 30 days, then drop them.</p>
    </div>
  </div>
</div>

grid-split

Two equal columns.

Rollback is one action

Revert production traffic to any green revision without a runbook.

artifact

kubectl rollout undo deploy/api-gateway

<div class="grid-split">
  <div class="panel">
    <div class="panel-content stack stack-sm">
      <p class="text-card">Rollback is one action</p>
      <p class="text-body text-muted">Revert production traffic to any green revision without a runbook.</p>
    </div>
  </div>
  <div class="panel">
    <div class="panel-content stack stack-sm">
      <p class="text-meta">artifact</p>
      <p class="text-mono">kubectl rollout undo deploy/api-gateway</p>
    </div>
  </div>
</div>

grid-stack-rail

A fluid main column with a narrower trailing rail.

Revision 6f82ae1

All required checks passed. Ready to promote to production.

activity

Build finished

Checks green

<div class="grid-stack-rail">
  <div class="panel">
    <div class="panel-content stack stack-sm">
      <p class="text-card">Revision 6f82ae1</p>
      <p class="text-body text-muted">All required checks passed. Ready to promote to production.</p>
    </div>
  </div>
  <div class="panel">
    <div class="panel-content stack stack-sm">
      <p class="text-meta">activity</p>
      <p class="text-body">Build finished</p>
      <p class="text-body">Checks green</p>
    </div>
  </div>
</div>

grid-thirds

Three equal columns.

environment

Staging

v2.8.1-rc.3

environment

Canary

v2.8.1 · 5%

environment

Production

v2.8.0

<div class="grid-thirds">
  <div class="panel">
    <div class="panel-content stack stack-sm">
      <p class="text-meta">environment</p>
      <p class="text-card">Staging</p>
      <p class="text-body text-muted">v2.8.1-rc.3</p>
    </div>
  </div>
  <div class="panel">
    <div class="panel-content stack stack-sm">
      <p class="text-meta">environment</p>
      <p class="text-card">Canary</p>
      <p class="text-body text-muted">v2.8.1 · 5%</p>
    </div>
  </div>
  <div class="panel">
    <div class="panel-content stack stack-sm">
      <p class="text-meta">environment</p>
      <p class="text-card">Production</p>
      <p class="text-body text-muted">v2.8.0</p>
    </div>
  </div>
</div>

scroll-area

A bounded region that scrolls on its own, with a thin scrollbar.

  • api-gateway2h ago
  • billing-worker5h ago
  • webhook-relayyesterday
  • search-indexer2d ago
  • image-resizer4d ago
  • audit-log5d ago
  • event-router6d ago
  • metrics-sinklast week
  • cache-warmerlast week
  • schema-migrator2 weeks ago
<div class="scroll-area">
  <ul class="list-group">
    <li class="list-item"><span class="list-title">api-gateway</span><span class="list-meta">2h ago</span></li>
    <li class="list-item"><span class="list-title">billing-worker</span><span class="list-meta">5h ago</span></li>
    <li class="list-item"><span class="list-title">webhook-relay</span><span class="list-meta">yesterday</span></li>
    <li class="list-item"><span class="list-title">search-indexer</span><span class="list-meta">2d ago</span></li>
    <li class="list-item"><span class="list-title">image-resizer</span><span class="list-meta">4d ago</span></li>
    <li class="list-item"><span class="list-title">audit-log</span><span class="list-meta">5d ago</span></li>
    <li class="list-item"><span class="list-title">event-router</span><span class="list-meta">6d ago</span></li>
    <li class="list-item"><span class="list-title">metrics-sink</span><span class="list-meta">last week</span></li>
    <li class="list-item"><span class="list-title">cache-warmer</span><span class="list-meta">last week</span></li>
    <li class="list-item"><span class="list-title">schema-migrator</span><span class="list-meta">2 weeks ago</span></li>
  </ul>
</div>

stack

Vertical rhythm (stack) and horizontal grouping that wraps (cluster).

Stack spaces its children vertically; cluster groups them horizontally and wraps.

<div class="stack">
  <div class="cluster">
    <button class="btn btn-primary" type="button">Promote</button>
    <button class="btn btn-secondary" type="button">Compare</button>
  </div>
  <p class="text-body text-muted">Stack spaces its children vertically; cluster groups them horizontally and wraps.</p>
</div>

Actions

button-group

Buttons joined into a single control for mutually related actions.

<div class="button-group">
  <button class="btn btn-secondary btn-sm" type="button">Edit</button>
  <button class="btn btn-secondary btn-sm" type="button">Duplicate</button>
  <button class="btn btn-secondary btn-sm" type="button">Archive</button>
</div>

button

The standard action control, in four emphasis levels plus busy and disabled states.

<div class="cluster">
  <button class="btn btn-primary" type="button">Save changes</button>
  <button class="btn btn-secondary" type="button">Preview</button>
  <button class="btn btn-ghost" type="button">Cancel</button>
  <button class="btn btn-danger" type="button">Delete</button>
  <button class="btn btn-primary" type="button" aria-busy="true">Saving</button>
  <button class="btn btn-secondary" type="button" disabled>Unavailable</button>
</div>

fab

A single action pinned to the corner of the viewport.

<button class="fab" type="button" aria-label="Create deployment">
  <svg class="icon" aria-hidden="true" viewBox="0 0 24 24"><path d="M12 5v14M5 12h14"/></svg>
</button>

icon-button

A square control carrying only an icon. Always needs an accessible label.

<div class="cluster">
  <button class="icon-button" type="button" aria-label="Refresh">
    <svg class="icon" aria-hidden="true" viewBox="0 0 24 24"><path d="M21 12a9 9 0 1 1-3-6.7M21 4v5h-5"/></svg>
  </button>
  <button class="close-button" type="button" aria-label="Close">
    <svg class="icon" aria-hidden="true" viewBox="0 0 24 24"><path d="M18 6 6 18M6 6l12 12"/></svg>
  </button>
</div>

Forms

auth-card

A narrow card holding a sign-in form and its secondary links.

Sign in

Use the account your organisation issued.

<div class="auth-card">
  <div class="stack">
    <div class="stack stack-sm">
      <h1 class="text-card">Sign in</h1>
      <p class="text-body text-muted">Use the account your organisation issued.</p>
    </div>
    <form class="form-stack">
      <div class="form-field">
        <label class="form-label" for="auth-mail">Email</label>
        <input class="input" id="auth-mail" type="email" autocomplete="username">
      </div>
      <div class="form-field">
        <label class="form-label" for="auth-pass">Password</label>
        <input class="input" id="auth-pass" type="password" autocomplete="current-password">
      </div>
      <button class="btn btn-primary" type="submit">Sign in</button>
    </form>
    <div class="auth-links">
      <a href="/reset">Forgot password</a>
      <a href="/sso">Use single sign-on</a>
    </div>
  </div>
</div>

checkbox

A boolean control for options that apply independently.

<div class="stack stack-sm">
  <label class="checkbox"><input type="checkbox" checked> Include failed jobs</label>
  <label class="checkbox"><input type="checkbox"> Notify the on-call channel</label>
</div>

date-picker

A native date input in a framed field with a leading icon.

<label class="picker-field" for="cutover-date">
  <span class="sr-only">Cutover date</span>
  <svg class="icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true"><path d="M8 2v4"/><path d="M16 2v4"/><rect x="3" y="4" width="18" height="18" rx="2"/><path d="M3 10h18"/></svg>
  <input class="picker-input" id="cutover-date" type="date" value="2026-06-10">
</label>

dropzone

A drop target for uploading a file.

<label class="dropzone">
  <input type="file">
  <svg class="icon" aria-hidden="true" viewBox="0 0 24 24"><path d="M12 16V4M6 10l6-6 6 6M4 20h16"/></svg>
  <span><strong>Drop a file</strong> or browse</span>
</label>

floating-label

A label that sits inside the field and rises when the field has content.

<div class="floating-field">
  <input class="floating-input" id="cluster-name" placeholder=" ">
  <label class="floating-label" for="cluster-name">Cluster name</label>
</div>

form

A vertical stack of labelled fields with helper text and error messages.

Used in URLs and deployment labels.

This key expired. Create a new key and try again.

<form class="form-stack">
  <div class="form-field">
    <label class="form-label" for="project-name">Project name</label>
    <input class="input w-full" id="project-name" value="release-monitor">
    <p class="form-helper">Used in URLs and deployment labels.</p>
  </div>
  <div class="form-field">
    <label class="form-label" for="region">Region</label>
    <select class="select" id="region"><option>us-east-1</option><option>eu-west-1</option></select>
  </div>
  <div class="form-field">
    <label class="form-label" for="invalid-key">API key</label>
    <input class="input w-full" id="invalid-key" aria-invalid="true" aria-describedby="key-error" value="expired-key">
    <p class="form-message error" id="key-error">This key expired. Create a new key and try again.</p>
  </div>
  <label class="switch"><input type="checkbox" checked><span class="switch-track"></span>Pause on failed checks</label>
</form>

input-group

An input joined to a fixed prefix or suffix.

https://
<div class="input-group">
  <span class="input-affix">https://</span>
  <input class="input" type="text" value="status.example.com" aria-label="Status page domain">
</div>

input-validation

Error and success styling for an input, paired with aria-invalid.

<div class="cluster">
  <input class="input input-error" type="text" value="expired-key" aria-invalid="true" aria-label="API key, invalid">
  <input class="input input-success" type="text" value="a1b2-c3d4-e5f6" aria-label="API key, verified">
</div>

input

A labelled text field with optional helper text. The atom every form is built from.

Lowercase, hyphen separated.

<div class="form-field">
  <label class="form-label" for="service-name">Service name</label>
  <input class="input w-full" id="service-name" value="api-gateway">
  <p class="form-helper">Lowercase, hyphen separated.</p>
</div>

otp-input

Single-character boxes for a verification code.

Six characters, sent to the address on the account.

The boxes are presentation. Advancing focus on input, and handling a pasted code, needs JavaScript. Set aria-invalid="true" on every box when a code is rejected.

<div class="stack stack-sm">
  <div class="otp-row" role="group" aria-label="Verification code">
    <input class="otp-input" inputmode="numeric" maxlength="1" value="4" aria-label="Digit 1">
    <input class="otp-input" inputmode="numeric" maxlength="1" value="2" aria-label="Digit 2">
    <input class="otp-input" inputmode="numeric" maxlength="1" value="8" aria-label="Digit 3">
    <input class="otp-input" inputmode="numeric" maxlength="1" aria-label="Digit 4">
    <input class="otp-input" inputmode="numeric" maxlength="1" aria-label="Digit 5">
    <input class="otp-input" inputmode="numeric" maxlength="1" aria-label="Digit 6">
  </div>
  <p class="form-helper">Six characters, sent to the address on the account.</p>
</div>

radio

A set of mutually exclusive options, all visible at once.

<div class="radio-group">
  <label class="radio"><input type="radio" name="region" checked> us-east-1</label>
  <label class="radio"><input type="radio" name="region"> eu-west-1</label>
</div>

range

A slider for a value where the approximate position matters more than the exact number.

70%
<div class="range-field">
  <div class="range-top"><label class="form-label" for="memory">Memory limit</label><span>70%</span></div>
  <input class="range-input" id="memory" type="range" min="0" max="100" value="70">
</div>

segmented-control

A compact joined control for switching between a few views.

<div class="segmented-control">
  <input type="radio" name="window" id="seg-day" checked><label for="seg-day">Day</label>
  <input type="radio" name="window" id="seg-week"><label for="seg-week">Week</label>
  <input type="radio" name="window" id="seg-month"><label for="seg-month">Month</label>
</div>

select

A labelled dropdown for one value from a known, short list.

<div class="form-field">
  <label class="form-label" for="deploy-region">Region</label>
  <select class="select" id="deploy-region">
    <option>us-east-1</option>
    <option>eu-west-1</option>
    <option>ap-southeast-2</option>
  </select>
</div>

switch

A toggle for a setting that takes effect immediately.

<label class="switch"><input type="checkbox" checked><span class="switch-track"></span>Pause on failed checks</label>

textarea

A multi-line text input that grows vertically.

<div class="form-field">
  <label class="form-label" for="release-notes">Release notes</label>
  <textarea class="textarea" id="release-notes" placeholder="What changed in this revision?"></textarea>
</div>

time-picker

A native time input in the same framed field as the date picker.

<label class="picker-field" for="cutover-time">
  <span class="sr-only">Cutover time</span>
  <svg class="icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true"><circle cx="12" cy="12" r="10"/><path d="M12 6v6l4 2"/></svg>
  <input class="picker-input" id="cutover-time" type="time" value="14:30">
</label>

Surfaces

accordion

Native details and summary styled as a disclosure row.

What do I still have to build in JavaScript?
Focus trapping, closing on Escape, arrow-key navigation, tracking which item is selected, and screen-reader announcements. The CSS only styles these components.
<details class="accordion">
  <summary>What do I still have to build in JavaScript?</summary>
  <div class="accordion-body">Focus trapping, closing on Escape, arrow-key navigation, tracking which item is selected, and screen-reader announcements. The CSS only styles these components.</div>
</details>

card

A compatibility alias for panel, kept for 1.x markup.

api-gateway

Healthy

Three replicas healthy in us-east-1. Last check 40 seconds ago.

<article class="card">
  <div class="card-header"><h3 class="card-title">api-gateway</h3><span class="badge badge-status" data-state="success">Healthy</span></div>
  <div class="card-content"><p class="card-body">Three replicas healthy in us-east-1. Last check 40 seconds ago.</p></div>
  <div class="card-footer"><button class="btn btn-secondary btn-sm" type="button">Open</button></div>
</article>

divider

A rule, optionally with a centred label.


or
<hr class="divider">
<div class="divider-label">or</div>

page-header

The title block at the top of a work surface, with room for actions.

<header class="page-header">
  <div class="page-header-main">
    <h1 class="page-title">Deployments</h1>
    <p class="page-summary">Every revision pushed to production in the last 30 days.</p>
  </div>
  <div class="page-actions">
    <button class="btn btn-secondary" type="button">Export</button>
    <button class="btn btn-primary" type="button">New deployment</button>
  </div>
</header>

panel

The canonical framed surface, with an optional header, body, and footer.

Retention policy

Keep deployment logs for the selected period.

Draft
<section class="panel">
  <header class="panel-header">
    <div><h3 class="panel-title">Retention policy</h3><p class="panel-body">Keep deployment logs for the selected period.</p></div>
    <span class="badge badge-status" data-state="info">Draft</span>
  </header>
  <div class="panel-content">
    <label class="checkbox"><input type="checkbox" checked> Include failed jobs</label>
  </div>
  <footer class="panel-footer"><button class="btn btn-primary btn-sm" type="button">Apply policy</button></footer>
</section>

section-header

A smaller heading block for a section inside a page.

Recent revisions

<div class="section-header">
  <div class="section-header-main">
    <h2 class="section-title">Recent revisions</h2>
    <p class="section-meta">Updated 4 minutes ago</p>
  </div>
  <div class="section-actions"><button class="btn btn-ghost btn-sm" type="button">View all</button></div>
</div>

Data

avatar-group

Overlapping avatars for the people on a thing.

AR MK JT
and 4 more
<div class="cluster">
  <div class="avatar-group">
    <span class="avatar avatar-md">AR</span>
    <span class="avatar avatar-md">MK</span>
    <span class="avatar avatar-md">JT</span>
  </div>
  <span class="text-meta">and 4 more</span>
</div>

avatar

A person or service marker in three sizes.

JK JK JK
<div class="cluster">
  <span class="avatar avatar-sm">JK</span>
  <span class="avatar avatar-md">JK</span>
  <span class="avatar avatar-lg">JK</span>
</div>

badge

A small label for a status or a category.

Ready Running Review Failed Queued
<div class="cluster">
  <span class="badge badge-status" data-state="success">Ready</span>
  <span class="badge badge-status" data-state="info">Running</span>
  <span class="badge badge-status" data-state="warning">Review</span>
  <span class="badge badge-status" data-state="danger">Failed</span>
  <span class="badge badge-status badge-neutral">Queued</span>
</div>

calendar

A month grid with today, selection, and adjacent-month days.

June 2026

The month is a labelled group of day buttons. Month changes and selection need JavaScript. Prefer this presentation shell over a fake ARIA grid unless you implement full grid keyboard behaviour.

<div class="calendar">
  <div class="calendar-head">
    <button class="icon-button" type="button" aria-label="Previous month"><svg class="icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true"><path d="m15 18-6-6 6-6"/></svg></button>
    <span class="calendar-title" id="calendar-label">June 2026</span>
    <button class="icon-button" type="button" aria-label="Next month"><svg class="icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true"><path d="m9 18 6-6-6-6"/></svg></button>
  </div>
  <div class="calendar-grid" role="group" aria-labelledby="calendar-label">
    <span class="calendar-dow" aria-hidden="true">Mo</span><span class="calendar-dow" aria-hidden="true">Tu</span><span class="calendar-dow" aria-hidden="true">We</span><span class="calendar-dow" aria-hidden="true">Th</span><span class="calendar-dow" aria-hidden="true">Fr</span><span class="calendar-dow" aria-hidden="true">Sa</span><span class="calendar-dow" aria-hidden="true">Su</span>
    <button class="calendar-day is-outside" type="button" aria-label="31 May 2026">31</button>
    <button class="calendar-day" type="button" aria-label="1 June 2026">1</button>
    <button class="calendar-day" type="button" aria-label="2 June 2026">2</button>
    <button class="calendar-day" type="button" aria-label="3 June 2026">3</button>
    <button class="calendar-day" type="button" aria-label="4 June 2026">4</button>
    <button class="calendar-day" type="button" aria-label="5 June 2026">5</button>
    <button class="calendar-day" type="button" aria-label="6 June 2026">6</button>
    <button class="calendar-day" type="button" aria-label="7 June 2026">7</button>
    <button class="calendar-day" type="button" aria-label="8 June 2026">8</button>
    <button class="calendar-day is-today" type="button" aria-label="9 June 2026, today" aria-current="date">9</button>
    <button class="calendar-day is-selected" type="button" aria-label="10 June 2026, selected" aria-pressed="true">10</button>
    <button class="calendar-day" type="button" aria-label="11 June 2026">11</button>
    <button class="calendar-day" type="button" aria-label="12 June 2026">12</button>
    <button class="calendar-day" type="button" aria-label="13 June 2026">13</button>
  </div>
</div>

chart

Bars sized by a --value custom property, from 0 to 100.

Build minutes by day. Connect a data source to populate this chart.

Mon Tue Wed Thu Fri

Set --value per bar as a percentage of the tallest value. For real analysis, use a charting library and keep these tokens.

<div class="panel">
  <div class="panel-content stack stack-sm">
    <p class="text-meta">Build minutes by day. Connect a data source to populate this chart.</p>
    <div class="chart">
      <span class="chart-bar" style="--value: 42">Mon</span>
      <span class="chart-bar" style="--value: 61">Tue</span>
      <span class="chart-bar" style="--value: 38">Wed</span>
      <span class="chart-bar is-active" style="--value: 88">Thu</span>
      <span class="chart-bar" style="--value: 54">Fri</span>
    </div>
  </div>
</div>

data-table

A scrollable table with numeric alignment and an optional tight density.

Deployment revisions
ServiceRevisionStatusDuration
api-gateway6f82ae1Ready1m 18s
worker-jobsd1c904bBuilding0m 47s
<div class="table-wrap">
  <table class="data-table is-tight">
    <caption class="sr-only">Deployment revisions</caption>
    <thead><tr><th>Service</th><th>Revision</th><th>Status</th><th class="is-numeric">Duration</th></tr></thead>
    <tbody>
      <tr><td>api-gateway</td><td><code>6f82ae1</code></td><td><span class="badge badge-status" data-state="success">Ready</span></td><td class="is-numeric">1m 18s</td></tr>
      <tr><td>worker-jobs</td><td><code>d1c904b</code></td><td><span class="badge badge-status" data-state="info">Building</span></td><td class="is-numeric">0m 47s</td></tr>
    </tbody>
  </table>
</div>

description-list

Label and value pairs that reflow when the column is narrow.

Region
us-east-1
Image
api-gateway:2.8.1
<dl class="description-list">
  <dt>Region</dt><dd>us-east-1</dd>
  <dt>Image</dt><dd><code>api-gateway:2.8.1</code></dd>
</dl>

list-group

Rows sharing one frame, each with a main label and a trailing value.

api-gatewayv2.8.1 · 3 replicas
Healthy
worker-jobsv1.14.0 · 6 replicas
Deploying
<div class="list-group">
  <div class="list-item"><div class="list-item-main"><span class="list-title">api-gateway</span><span class="list-meta">v2.8.1 &middot; 3 replicas</span></div><span class="list-item-value"><span class="badge badge-status" data-state="success">Healthy</span></span></div>
  <div class="list-item"><div class="list-item-main"><span class="list-title">worker-jobs</span><span class="list-meta">v1.14.0 &middot; 6 replicas</span></div><span class="list-item-value"><span class="badge badge-status" data-state="info">Deploying</span></span></div>
</div>

sortable-table

A data table whose headers are buttons that re-sort the rows.

Status
image-resizer4m 12sFailed
api-gateway1m 48sPassed
webhook-relay0m 39sPassed

aria-sort goes on the sorted th, and takes "ascending" or "descending". Sorting itself needs JavaScript.

<div class="table-wrap">
  <table class="data-table">
    <thead>
      <tr>
        <th scope="col" class="sortable"><button type="button">Service<svg class="sort-indicator" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true"><path d="m6 15 6 6 6-6"/><path d="m6 9 6-6 6 6"/></svg></button></th>
        <th scope="col" class="sortable" aria-sort="descending"><button type="button">Duration<svg class="sort-indicator" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true"><path d="m6 15 6 6 6-6"/><path d="m6 9 6-6 6 6"/></svg></button></th>
        <th scope="col">Status</th>
      </tr>
    </thead>
    <tbody>
      <tr><td>image-resizer</td><td class="is-numeric">4m 12s</td><td><span class="badge badge-danger">Failed</span></td></tr>
      <tr><td>api-gateway</td><td class="is-numeric">1m 48s</td><td><span class="badge badge-success">Passed</span></td></tr>
      <tr><td>webhook-relay</td><td class="is-numeric">0m 39s</td><td><span class="badge badge-success">Passed</span></td></tr>
    </tbody>
  </table>
</div>

stat-card

A single figure in its own frame, with an optional trend.

Median deploy time
1m 18s
12s faster than last week
Failed checks
3
2 more than last week
<div class="stat-grid">
  <div class="stat-card">
    <div class="stat-label">Median deploy time</div>
    <div class="stat-value">1m 18s</div>
    <div class="stat-trend down">12s faster than last week</div>
  </div>
  <div class="stat-card">
    <div class="stat-label">Failed checks</div>
    <div class="stat-value">3</div>
    <div class="stat-trend up">2 more than last week</div>
  </div>
</div>

stat-strip

A row of related counts sharing one frame.

Ready
12
Waiting to promote
Building
4
In progress now
Needs review
1
Checks failed
<dl class="stat-strip">
  <div class="stat-item"><dt class="stat-label">Ready</dt><dd class="stat-value">12</dd><dd class="stat-detail">Waiting to promote</dd></div>
  <div class="stat-item"><dt class="stat-label">Building</dt><dd class="stat-value">4</dd><dd class="stat-detail">In progress now</dd></div>
  <div class="stat-item"><dt class="stat-label">Needs review</dt><dd class="stat-value">1</dd><dd class="stat-detail">Checks failed</dd></div>
</dl>

styled-list

A spaced list for short related points.

  • Checks must pass before a revision can be promoted.
  • Rollback keeps the previous revision warm for one hour.
<ul class="styled-list">
  <li>Checks must pass before a revision can be promoted.</li>
  <li>Rollback keeps the previous revision warm for one hour.</li>
</ul>

tag

Neutral tags and accent-coloured badges for categories rather than conditions.

Platform Data Growth us-east-1

Accent badges name a category. A condition belongs in badge-status.

<div class="cluster">
  <span class="badge badge-coral">Platform</span>
  <span class="badge badge-azure">Data</span>
  <span class="badge badge-sage">Growth</span>
  <span class="tag">us-east-1</span>
</div>

timeline

Events in order, each with a title, timestamp, and body.

Deploy started
14:02:11 UTC
Rolling restart began for api-gateway.
Health checks passed
14:03:47 UTC
All three replicas reported healthy.
<div class="timeline">
  <div class="timeline-item">
    <div class="timeline-dot"></div>
    <div class="timeline-title">Deploy started</div>
    <div class="timeline-meta">14:02:11 UTC</div>
    <div class="timeline-body">Rolling restart began for api-gateway.</div>
  </div>
  <div class="timeline-item">
    <div class="timeline-dot"></div>
    <div class="timeline-title">Health checks passed</div>
    <div class="timeline-meta">14:03:47 UTC</div>
    <div class="timeline-body">All three replicas reported healthy.</div>
  </div>
</div>

Navigation

breadcrumb

The path back up a hierarchy.

<nav class="breadcrumb" aria-label="Breadcrumb">
  <a href="/services">Services</a><span class="breadcrumb-sep">/</span><a href="/services/api-gateway">api-gateway</a><span class="breadcrumb-sep">/</span><span>Revisions</span>
</nav>

nav-rail

A narrow vertical strip of icon-and-label destinations.

Mark the current destination with aria-current="page", or with the is-active class.

<nav class="nav-rail" aria-label="Sections">
  <a class="nav-rail-item" href="/overview" aria-current="page">
    <svg class="icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true"><rect x="3" y="3" width="7" height="9" rx="1"/><rect x="14" y="3" width="7" height="5" rx="1"/><rect x="14" y="12" width="7" height="9" rx="1"/><rect x="3" y="16" width="7" height="5" rx="1"/></svg>
    Overview
  </a>
  <a class="nav-rail-item" href="/deploys">
    <svg class="icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true"><path d="M12 19V5"/><path d="m5 12 7-7 7 7"/></svg>
    Deploys
  </a>
  <a class="nav-rail-item" href="/logs">
    <svg class="icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true"><path d="M4 6h16"/><path d="M4 12h16"/><path d="M4 18h10"/></svg>
    Logs
  </a>
</nav>

navbar

The top bar, in four surfaces: bordered, glass, solid, and minimal.

Pick one surface and keep it. The four below are alternatives, not a set.

<div class="stack">
  <nav class="navbar-bordered" aria-label="Bordered">
    <div class="nav-inner">
      <a class="brand" href="/"><span class="brand-mark">R/</span> relay</a>
      <div class="nav-links">
        <a class="nav-link" aria-current="page" href="/">Overview</a>
        <a class="nav-link" href="/deploys">Deploys</a>
        <a class="nav-link" href="/api">API</a>
      </div>
    </div>
  </nav>
  <nav class="navbar-solid" aria-label="Solid">
    <div class="nav-inner">
      <a class="brand" href="/"><span class="brand-mark">R/</span> relay</a>
      <div class="nav-links">
        <a class="nav-link" aria-current="page" href="/">Overview</a>
        <a class="nav-link" href="/deploys">Deploys</a>
        <a class="nav-link" href="/api">API</a>
      </div>
    </div>
  </nav>
  <nav class="navbar-glass" aria-label="Glass">
    <div class="nav-inner">
      <a class="brand" href="/"><span class="brand-mark">R/</span> relay</a>
      <div class="nav-links">
        <a class="nav-link" aria-current="page" href="/">Overview</a>
        <a class="nav-link" href="/deploys">Deploys</a>
        <a class="nav-link" href="/api">API</a>
      </div>
    </div>
  </nav>
  <nav class="navbar-minimal" aria-label="Minimal">
    <div class="nav-inner">
      <a class="brand" href="/"><span class="brand-mark">R/</span> relay</a>
      <div class="nav-links">
        <a class="nav-link" aria-current="page" href="/">Overview</a>
        <a class="nav-link" href="/deploys">Deploys</a>
        <a class="nav-link" href="/api">API</a>
      </div>
    </div>
  </nav>
</div>

pagination

Page links with the current page marked by aria-current.

<nav class="pagination" aria-label="Pagination">
  <a class="page-link" href="/revisions?page=0" aria-label="Previous page">&lsaquo;</a>
  <a class="page-link" href="/revisions?page=1" aria-current="page">1</a>
  <a class="page-link" href="/revisions?page=2">2</a>
  <a class="page-link" href="/revisions?page=3">3</a>
  <a class="page-link" href="/revisions?page=2" aria-label="Next page">&rsaquo;</a>
</nav>

sidebar-nav

A vertical navigation list, optionally collapsed to an icon rail.

<nav class="sidebar-nav" aria-label="Sections">
  <a href="/" aria-current="page">
    <svg class="icon" aria-hidden="true" viewBox="0 0 24 24"><path d="M3 12h18M3 6h18M3 18h18"/></svg>
    Overview
  </a>
  <a href="/deploys">
    <svg class="icon" aria-hidden="true" viewBox="0 0 24 24"><path d="M12 19V5M5 12l7-7 7 7"/></svg>
    Deploys
  </a>
</nav>

stepper

Progress through a fixed sequence of stages.

Build
2Review
3Release
<div class="stepper">
  <div class="step done"><span class="step-circle">&check;</span>Build</div>
  <div class="step-line"></div>
  <div class="step active"><span class="step-circle">2</span>Review</div>
  <div class="step-line"></div>
  <div class="step"><span class="step-circle">3</span>Release</div>
</div>

tabs

Switching between views of the same object.

Three deploys finished in the last hour.

Show and hide panels, and move focus with the arrow keys, in application code. This markup is the visual shell plus one sample panel.

<div class="stack stack-sm">
  <div class="tabs" role="tablist" aria-label="Views">
    <button class="tab" type="button" role="tab" aria-selected="true" id="tab-activity" aria-controls="panel-activity">Activity</button>
    <button class="tab" type="button" role="tab" aria-selected="false" id="tab-checks" aria-controls="panel-checks" tabindex="-1">Checks</button>
    <button class="tab" type="button" role="tab" aria-selected="false" id="tab-settings" aria-controls="panel-settings" tabindex="-1">Settings</button>
  </div>
  <div class="panel" id="panel-activity" role="tabpanel" aria-labelledby="tab-activity">
    <div class="panel-content text-body">Three deploys finished in the last hour.</div>
  </div>
  <div class="panel" id="panel-checks" role="tabpanel" aria-labelledby="tab-checks" hidden>
    <div class="panel-content text-body">All required checks are green.</div>
  </div>
  <div class="panel" id="panel-settings" role="tabpanel" aria-labelledby="tab-settings" hidden>
    <div class="panel-content text-body">Notification preferences for this service.</div>
  </div>
</div>

Feedback

alert

An inline message in four severities, each with a title and body.

Revision queued

It starts when the current deploy finishes.

All checks passed

This revision is ready to promote.

Two checks need review

Open the failed checks before you promote this revision.

Deploy failed

api-gateway rolled back to v2.8.0 automatically.

<div class="alert alert-info">
  <svg class="icon" aria-hidden="true" viewBox="0 0 24 24"><circle cx="12" cy="12" r="9"/><path d="M12 16v-4M12 8h.01"/></svg>
  <div><strong class="alert-title">Revision queued</strong><p class="alert-body">It starts when the current deploy finishes.</p></div>
</div>
<div class="alert alert-success">
  <svg class="icon" aria-hidden="true" viewBox="0 0 24 24"><path d="M20 6 9 17l-5-5"/></svg>
  <div><strong class="alert-title">All checks passed</strong><p class="alert-body">This revision is ready to promote.</p></div>
</div>
<div class="alert alert-warning">
  <svg class="icon" aria-hidden="true" viewBox="0 0 24 24"><path d="M12 3 2 20h20L12 3Z"/><path d="M12 9v4M12 17h.01"/></svg>
  <div><strong class="alert-title">Two checks need review</strong><p class="alert-body">Open the failed checks before you promote this revision.</p></div>
</div>
<div class="alert alert-danger">
  <svg class="icon" aria-hidden="true" viewBox="0 0 24 24"><circle cx="12" cy="12" r="9"/><path d="M15 9l-6 6M9 9l6 6"/></svg>
  <div><strong class="alert-title">Deploy failed</strong><p class="alert-body">api-gateway rolled back to v2.8.0 automatically.</p></div>
</div>

banner

A dismissible page-level notice.

<div class="banner">
  <div><strong class="banner-title">Scheduled maintenance</strong><p class="banner-body">api-gateway restarts at 02:00 UTC on Thursday.</p></div>
  <button class="btn btn-ghost btn-sm" type="button">Dismiss</button>
</div>

empty-state

What a surface shows before it has data.

No failed checks

Everything in this pipeline passes right now.

Review the last run
<div class="empty-state">
  <h3 class="empty-title">No failed checks</h3>
  <p class="empty-body">Everything in this pipeline passes right now.</p>
  <a class="btn btn-secondary btn-sm" href="/runs/latest">Review the last run</a>
</div>

progress

A determinate bar for work with a known percentage.

<div class="progress" role="progressbar" aria-valuenow="64" aria-valuemin="0" aria-valuemax="100" aria-label="Build progress">
  <div class="progress-fill" style="--value: 64%"></div>
</div>

skeleton

Grey blocks in the shape of the content that is loading.

skeleton-text shortens each following line so it reads as a paragraph. Mark the region aria-busy so it is announced.

<div class="stack stack-sm" aria-busy="true" aria-label="Loading">
  <div class="skeleton-text">
    <div class="skeleton"></div>
    <div class="skeleton"></div>
    <div class="skeleton"></div>
    <div class="skeleton"></div>
  </div>
</div>

spinner

Indeterminate loading marks: a spinner for actions, a skeleton for content.

<div class="cluster">
  <span class="spinner" role="status" aria-label="Loading"></span>
  <span class="skeleton" style="width: 160px"></span>
  <span class="skeleton" style="width: 90px"></span>
</div>

toast-region

The fixed corner container that stacks toasts.

Build queued

Revision 4f2c1ab is waiting for a runner.

aria-live="polite" announces new toasts without cutting off the current sentence.

<div class="toast-region" role="status" aria-live="polite">
  <div class="toast">
    <svg class="icon" aria-hidden="true" viewBox="0 0 24 24"><path d="M12 6v6l4 2"/><circle cx="12" cy="12" r="10"/></svg>
    <div><strong class="toast-title">Build queued</strong><p class="toast-body">Revision 4f2c1ab is waiting for a runner.</p></div>
  </div>
</div>

toast

A transient confirmation of something that just happened.

Deploy complete

worker-jobs v1.14.0 is live.

<div class="toast">
  <svg class="icon" aria-hidden="true" viewBox="0 0 24 24"><path d="M20 6 9 17l-5-5"/></svg>
  <div><strong class="toast-title">Deploy complete</strong><p class="toast-body">worker-jobs v1.14.0 is live.</p></div>
</div>

Overlays

alert-dialog

A modal that asks a person to confirm something they cannot undo.

Revoke key

An alert dialog takes role="alertdialog". Escape should cancel, never confirm. Focus trapping is yours to build.

<a href="#revoke-key" class="btn btn-danger">Revoke key</a>
<div class="modal modal-alert" id="revoke-key" role="alertdialog" aria-modal="true" aria-labelledby="revoke-title" aria-describedby="revoke-body">
  <div class="modal-card">
    <div class="modal-head">
      <span class="modal-icon"><svg class="icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true"><path d="M12 9v4"/><path d="M12 17h.01"/><path d="M10.3 3.9 1.8 18a2 2 0 0 0 1.7 3h17a2 2 0 0 0 1.7-3L13.7 3.9a2 2 0 0 0-3.4 0Z"/></svg></span>
      <h3 class="modal-title" id="revoke-title">Revoke this key?</h3>
    </div>
    <div class="modal-body" id="revoke-body">Any service using <code class="inline-code">deploy-bot-prod</code> stops authenticating straight away. This cannot be undone.</div>
    <div class="modal-actions">
      <a class="btn btn-ghost" href="#components">Keep the key</a>
      <button class="btn btn-danger" type="button">Revoke the key</button>
    </div>
  </div>
</div>

combobox

A text input with a filtered list of options beneath it.

  • api-gateway
  • worker-jobs
  • event-router

CSS can show the list on focus. Filtering, arrow keys, and committing a choice need JavaScript. Add is-open while the list must stay visible for a click.

<div class="combobox">
  <label class="sr-only" for="service-search">Search services</label>
  <input class="input w-full" id="service-search" type="text" role="combobox" aria-expanded="false" aria-controls="service-list" aria-autocomplete="list" placeholder="Search services">
  <ul class="combobox-list" id="service-list" role="listbox">
    <li role="option">api-gateway</li>
    <li role="option">worker-jobs</li>
    <li role="option">event-router</li>
  </ul>
</div>

command-palette

A keyboard-first search over commands and objects.

Filtering and keyboard selection need JavaScript.

<div class="command-palette" role="search">
  <label class="sr-only" for="command-query">Type a command or search</label>
  <input class="input" id="command-query" type="search" placeholder="Type a command or search" autocomplete="off">
  <div class="command-group">
    <div class="command-group-label" id="command-services-label">Services</div>
    <button class="command-item" type="button" aria-describedby="command-services-label">api-gateway <kbd>G A</kbd></button>
    <button class="command-item" type="button" aria-describedby="command-services-label">worker-jobs <kbd>G W</kbd></button>
  </div>
</div>

context-menu

A small menu of actions for one object.

Position it and handle Escape, arrow keys, and outside clicks in application code.

<div class="context-menu" role="menu" aria-label="Deploy actions">
  <button class="context-menu-item" type="button" role="menuitem">
    <svg class="icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true"><path d="M12 19V5"/><path d="m5 12 7-7 7 7"/></svg>
    Promote to production
  </button>
  <button class="context-menu-item" type="button" role="menuitem">
    <svg class="icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true"><path d="M3 12a9 9 0 1 0 3-6.7L3 8"/><path d="M3 3v5h5"/></svg>
    Roll back
    <kbd>R</kbd>
  </button>
  <hr class="context-menu-separator">
  <button class="context-menu-item is-danger" type="button" role="menuitem">
    <svg class="icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true"><path d="M3 6h18"/><path d="M8 6V4h8v2"/><path d="M19 6v14H5V6"/></svg>
    Delete build
  </button>
</div>

drawer

A panel that slides in from the edge, driven by a checkbox toggle.

The checkbox is visually hidden but stays in the tab order so keyboard users can open and close it. Focus trapping and Escape still need application code. Add is-active on the backdrop and panel when you drive open state from script.

<label class="btn btn-secondary" for="filter-drawer">Filters</label>
<input class="drawer-toggle" type="checkbox" id="filter-drawer" aria-controls="filter-drawer-panel">
<div class="drawer-backdrop" aria-hidden="true"></div>
<div class="drawer-panel" id="filter-drawer-panel" role="dialog" aria-modal="true" aria-labelledby="filter-drawer-title">
  <div class="drawer-head"><h3 class="modal-title" id="filter-drawer-title">Filter revisions</h3><label class="close-button" for="filter-drawer" aria-label="Close"><svg class="icon" aria-hidden="true" viewBox="0 0 24 24"><path d="M18 6 6 18M6 6l12 12"/></svg></label></div>
  <div class="stack stack-sm">
    <p class="text-body text-muted">Show only failed checks from the last 24 hours.</p>
    <label class="checkbox"><input type="checkbox" checked> Failed only</label>
    <label class="checkbox"><input type="checkbox"> Needs review</label>
  </div>
</div>

dropdown

A menu of actions attached to a trigger.

CSS handles the open state on focus. Keyboard navigation and Escape are yours to build.

<div class="dropdown">
  <button class="btn btn-secondary btn-sm" type="button">Actions</button>
  <div class="dropdown-menu">
    <button type="button">Restart service</button>
    <button type="button">View logs</button>
    <button type="button">Roll back</button>
  </div>
</div>

hover-card

A panel of detail that opens on hover or focus.

api-gateway api-gateway Owned by Platform. Last deployed 2 hours ago. Healthy

Opens on hover and on focus-within, so keyboard users get it too.

<span class="hover-card">
  <a class="text-accent" href="/services/api-gateway">api-gateway</a>
  <span class="hover-card-panel">
    <span class="stack stack-sm">
      <span class="text-card">api-gateway</span>
      <span class="text-meta">Owned by Platform. Last deployed 2 hours ago.</span>
      <span class="badge badge-status" data-state="success">Healthy</span>
    </span>
  </span>
</span>

modal

A centred dialog that blocks the page until it is resolved.

Roll back

Open it with :target, a checkbox, or application code. Focus trapping, Escape, and restoring focus are yours to build.

<a href="#confirm-rollback" class="btn btn-danger">Roll back</a>
<div class="modal" id="confirm-rollback" role="dialog" aria-modal="true" aria-labelledby="confirm-rollback-title">
  <div class="modal-card">
    <div class="modal-head">
      <h3 class="modal-title" id="confirm-rollback-title">Confirm rollback</h3>
      <a class="close-button" href="#components" aria-label="Close"><svg class="icon" aria-hidden="true" viewBox="0 0 24 24"><path d="M18 6 6 18M6 6l12 12"/></svg></a>
    </div>
    <div class="modal-body">This reverts api-gateway to v2.8.0. Active sessions are not affected.</div>
    <div class="modal-actions">
      <a class="btn btn-ghost" href="#components">Cancel</a>
      <button class="btn btn-danger" type="button">Roll back</button>
    </div>
  </div>
</div>

popover

A small panel of detail attached to a trigger.

us-east-1

Primary region. Three availability zones.

<div class="popover">
  <button class="btn btn-secondary btn-sm" type="button">Region details</button>
  <div class="popover-panel">
    <div class="popover-title">us-east-1</div>
    <p class="popover-body">Primary region. Three availability zones.</p>
  </div>
</div>

tooltip

A short hint attached to a control, shown on hover and focus.

<span class="tooltip" data-tip="Last deployed 12 minutes ago">
  <button class="icon-button" type="button" aria-label="Deployment info">
    <svg class="icon" aria-hidden="true" viewBox="0 0 24 24"><circle cx="12" cy="12" r="9"/><path d="M12 16v-4M12 8h.01"/></svg>
  </button>
</span>

Content

aspect-ratio

A box that holds its shape while the media inside fills it.

Deploy frequency over the last 30 days

The default shape is 16 by 9.

<div class="aspect aspect-4-3">
  <img src="/media/deploy-graph.png" alt="Deploy frequency over the last 30 days">
</div>

cmd-pill

A single copyable command shown as a compact pill.

$ npm install postrboard-css
<span class="cmd-pill"><span class="dollar">$</span> npm install postrboard-css</span>

figure

Media with a caption underneath.

A pipeline with build, test, and deploy stages
Each stage runs on the merge commit, not the branch tip.
<figure class="figure">
  <div class="frame">
    <img src="/media/pipeline.png" alt="A pipeline with build, test, and deploy stages">
  </div>
  <figcaption class="figure-caption">Each stage runs on the merge commit, not the branch tip.</figcaption>
</figure>

frame

A bordered, rounded, clipping container for an image or a screenshot.

The deploy console showing three queued builds
<div class="frame">
  <img src="/media/console.png" alt="The deploy console showing three queued builds">
</div>

icon

The four icon size tokens.

<div class="cluster">
  <svg class="icon icon-sm" aria-hidden="true" viewBox="0 0 24 24"><circle cx="12" cy="12" r="9"/></svg>
  <svg class="icon icon-md" aria-hidden="true" viewBox="0 0 24 24"><circle cx="12" cy="12" r="9"/></svg>
  <svg class="icon icon-lg" aria-hidden="true" viewBox="0 0 24 24"><circle cx="12" cy="12" r="9"/></svg>
  <svg class="icon icon-xl" aria-hidden="true" viewBox="0 0 24 24"><circle cx="12" cy="12" r="9"/></svg>
</div>

inline-code

Identifiers inside prose, and keyboard keys.

Run npm run build, then press Ctrl C to stop the watcher.

<p class="text-body">Run <code class="inline-code">npm run build</code>, then press <kbd class="kbd">Ctrl</kbd> <kbd class="kbd">C</kbd> to stop the watcher.</p>

media-embed

A fixed-shape frame with a play control over it.

<div class="aspect media-embed">
  <img src="/media/walkthrough-poster.png" alt="">
  <button class="media-play" type="button" aria-label="Play the walkthrough">
    <svg class="icon" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M8 5v14l11-7z"/></svg>
  </button>
</div>

quote-block

A pull quote with attribution.

Rollback has to be one action, not a runbook. Quote and attribution pending
<blockquote class="quote-block">
  Rollback has to be one action, not a runbook.
  <cite>Quote and attribution pending</cite>
</blockquote>

terminal

A command and its output, with a labelled title bar.

ci-runner · us-east-1
$ kubectl rollout status deploy/api-gateway
deployment "api-gateway" successfully rolled out
<div class="terminal">
  <div class="terminal-bar">
    <span class="terminal-title">ci-runner &middot; us-east-1</span>
  </div>
  <div class="terminal-body">
    <div><span class="prompt">$</span> <span class="cmd">kubectl rollout status deploy/api-gateway</span></div>
    <div class="dim">deployment "api-gateway" successfully rolled out</div>
  </div>
</div>

type

The full type scale, from the display size down to metadata.

Deploy history

Roll back in one action

Section heading

Card heading, for titles inside a panel.

Body copy. Capped at 72 characters so long paragraphs stay readable without a wrapper.

Muted body, for a second line that supports the first.

METADATA · TIMESTAMPS · LABELS

<div class="stack">
  <p class="text-display">Deploy history</p>
  <h2 class="text-h1">Roll back in one action</h2>
  <h3 class="text-h2">Section heading</h3>
  <p class="text-card">Card heading, for titles inside a panel.</p>
  <p class="text-body">Body copy. Capped at 72 characters so long paragraphs stay readable without a wrapper.</p>
  <p class="text-body text-muted">Muted body, for a second line that supports the first.</p>
  <p class="text-meta">METADATA &middot; TIMESTAMPS &middot; LABELS</p>
</div>

Marketing

cta-band

A closing block with one action.

Read the deploy guide

Set up a pipeline in about ten minutes.

Open the guide
<div class="cta-band">
  <h2 class="cta-title">Read the deploy guide</h2>
  <p class="cta-body">Set up a pipeline in about ten minutes.</p>
  <a class="btn" href="/docs/deploy">Open the guide</a>
</div>

faq

A stack of accordions for real, repeated questions.

Does a rollback re-run the build?
No. A rollback repoints traffic at an artefact that already passed. It takes about four seconds.
Can two people deploy at once?
The queue is per environment. The second deploy waits, and the person who started it sees who is ahead of them.
What happens to in-flight requests?
The old revision keeps serving until it drains, up to 30 seconds. You can raise the limit per service.
<div class="faq">
  <details class="accordion">
    <summary>Does a rollback re-run the build?</summary>
    <div class="accordion-body">No. A rollback repoints traffic at an artefact that already passed. It takes about four seconds.</div>
  </details>
  <details class="accordion">
    <summary>Can two people deploy at once?</summary>
    <div class="accordion-body">The queue is per environment. The second deploy waits, and the person who started it sees who is ahead of them.</div>
  </details>
  <details class="accordion">
    <summary>What happens to in-flight requests?</summary>
    <div class="accordion-body">The old revision keeps serving until it drains, up to 30 seconds. You can raise the limit per service.</div>
  </details>
</div>

feature-card

A rule-separated block for one capability.

Promote by revision

Pick any revision that passed checks and move traffic to it.

<div class="feature-card">
  <svg class="icon feature-icon" aria-hidden="true" viewBox="0 0 24 24"><path d="M12 19V5M5 12l7-7 7 7"/></svg>
  <h3 class="feature-title">Promote by revision</h3>
  <p class="feature-body">Pick any revision that passed checks and move traffic to it.</p>
</div>

logo-cloud

A labelled row of integration or customer slots.

Connects to

Integration name Integration name Integration name Integration name
<div class="logo-cloud">
  <p class="logo-cloud-label">Connects to</p>
  <div class="logo-cloud-row">
    <span class="logo-cloud-item">Integration name</span>
    <span class="logo-cloud-item">Integration name</span>
    <span class="logo-cloud-item">Integration name</span>
    <span class="logo-cloud-item">Integration name</span>
  </div>
</div>

newsletter

One field and one button, with a line about what arrives.

<div class="newsletter">
  <p class="text-body">Release notes, once a fortnight. No other mail.</p>
  <form class="newsletter-form">
    <label class="sr-only" for="release-mail">Email address</label>
    <input class="input" id="release-mail" type="email" placeholder="you@company.com" autocomplete="email">
    <button class="btn btn-primary" type="submit">Subscribe</button>
  </form>
</div>

pricing-card

A plan with a price and a feature list. Add popular to mark one.

<div class="pricing-card popular">
  <h3 class="text-card">Team</h3>
  <p class="text-h2">Price pending</p>
  <ul class="styled-list"><li>Unlimited revisions</li><li>Audit log export</li></ul>
  <button class="btn btn-primary" type="button">Start a trial</button>
</div>

testimonial-card

A quote with attribution, framed by rules.

Customer quote pending

Name and company pending

<div class="testimonial-card">
  <p class="testimonial-quote">Customer quote pending</p>
  <p class="text-meta">Name and company pending</p>
</div>

Utilities

utility-layout

Display mode, flex direction, alignment, and positioning.

api-gateway Healthy
<div class="flex items-center justify-between">
  <span class="text-meta">api-gateway</span>
  <span class="badge badge-success">Healthy</span>
</div>

utility-responsive

Show or hide an element at a breakpoint.

hide-sm and show-sm act at the phone breakpoint; hide-md and show-md act at the tablet breakpoint.

<div class="cluster">
  <button class="btn btn-primary" type="button"><span class="hide-sm">Promote revision</span><span class="show-sm">Promote</span></button>
</div>

utility-sizing

Width caps, full height, flexible growth, and overflow.

<div class="flex gap-3 items-center">
  <input class="input flex-1" value="api-gateway" aria-label="Service">
  <button class="btn btn-secondary" type="button">Find</button>
</div>

utility-spacing

Margin, padding, and gap on the shared space scale, from 1 to 8.

Promoting a revision moves production traffic.

<div class="panel">
  <div class="panel-content">
    <p class="text-body">Promoting a revision moves production traffic.</p>
    <button class="btn btn-primary mt-4" type="button">Promote</button>
  </div>
</div>

utility-surface

Background, border, and corner rounding that match the framework tokens.

GET /v1/deploys?status=ready

<div class="bg-code border rounded p-4">
  <p class="text-mono">GET /v1/deploys?status=ready</p>
</div>

utility-text

Alignment, semantic colour, and truncation for text.

Build passed on main.

sha256:9f2c1ab4de7810b3c5e6f4a2d8b90c1e3f5a7b9d2c4e6f8a0b1c3d5e7f9a1b3c

<div class="stack stack-sm">
  <p class="text-body">Build <span class="text-success">passed</span> on <span class="text-accent">main</span>.</p>
  <p class="text-meta truncate">sha256:9f2c1ab4de7810b3c5e6f4a2d8b90c1e3f5a7b9d2c4e6f8a0b1c3d5e7f9a1b3c</p>
</div>

Choosing a layout

What a page should lead with depends on what the product does. That is a separate decision from the theme options above.

If you are buildingLead withClasses to use
A task people repeat dailyWhere things stand, and the next thing to do.page-header .panel .form-stack
MonitoringWhat is broken, and what changed recently.stat-strip .data-table .timeline
Settings and audit trailsThe controls, and a record of past changes.description-list .list-group .alert
DocumentationThe smallest working example.grid-sidebar .inline-code .accordion
Long-form writingThe point, then what supports itdata-type="editorial" .grid-asymmetric