HaTchi-MaXchi

An htmx4-native design system. Server-rendered markup, one accent, dark as a material, and lifecycle-driven motion — the maturity of the modern component aesthetic without a client framework. Vendored Geist + Lucide 1.52.0.

“htmx4-native” here means server-rendered htmx partials, no hydration, lifecycle-aware CSS/JS affordances, and endpoint contracts colocated with components — it is not an official htmx distribution.

Setup — what a copied snippet needs

1. The bundle. One CSS + one JS file, hosted on the jsDelivr CDN pinned to a release tag. Every release's notes carry the ready-to-paste <link>/<script> snippet with that version's SRI hashes; or vendor dist/hatchi-maxchi.css + dist/hatchi-maxchi.js.

2. Icons. Snippets reference icons as <svg><use href="#name"></svg> against a symbol sheet included once per page — copy sprite_sheet.svg inline into your layout (this page embeds it at the top of <body>).

3. The server half. Interactive Hyperparts are htmx4 partials: your endpoints must return the fragments documented in each component's Endpoint contract. Tables/lists that swap with hx-swap="innerMorph" need the idiomorph extension. This gallery runs a static mock instead of a server, so everything here works offline.

Full details (theming, prefixing, releases): the README.

Button

Primary, outline, ghost, destructive — chromatic accent CTAs.

html
<div class="hm-demo-row">
  <button class="button" data-variant="primary">Save changes</button>
  <button class="button" data-variant="outline">Cancel</button>
  <button class="button" data-variant="ghost">Learn more</button>
  <button class="button" data-variant="destructive">Delete</button>
</div>

Full reference: contracts, guidance, anatomy →

Badge identity

Colour + icon + text — status never relies on colour alone (WCAG 1.4.1).

ApprovedPendingRejectedDraft
html
<!-- icons: include the icon sheet once per page (see the Setup section, #setup) -->
<div class="hm-demo-row">
  <span class="badge" data-tone="success"><span class="badge-icon"><svg class="icon" aria-hidden="true"><use href="#i-circle-check"/></svg></span>Approved</span>
  <span class="badge" data-tone="warning"><span class="badge-icon"><svg class="icon" aria-hidden="true"><use href="#i-triangle-alert"/></svg></span>Pending</span>
  <span class="badge" data-tone="destructive"><span class="badge-icon"><svg class="icon" aria-hidden="true"><use href="#i-circle-x"/></svg></span>Rejected</span>
  <span class="badge" data-tone="neutral">Draft</span>
</div>

Full reference: contracts, guidance, anatomy →

Alert identity

Tone-wash surfaces — an identity layer shadcn has no vocabulary for.

html
<!-- icons: include the icon sheet once per page (see the Setup section, #setup) -->
<div class="alert hm-measure-lg" data-tone="warning" role="alert">
  <span class="alert__icon"><svg class="icon" aria-hidden="true"><use href="#i-triangle-alert"/></svg></span>
  <div class="alert__body">
    <div class="alert__title">Payment method expiring</div>
    <div class="alert__description">Your card ending 4242 expires next month.</div>
  </div>
</div>

Full reference: contracts, guidance, anatomy →

Toast feedback

Stack host + auto-dismiss notifications — title, body, optional actions; hover/focus pauses the timer (htmx OOB or client bridge).

html
<div id="toast" class="toast-stack" aria-live="polite" data-toast-cap="8">
  <div class="toast toast-enter" data-toast-level="success" data-remove-after="8s" role="status">
    <span class="toast__icon" aria-hidden="true"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><path d="m9 12 2 2 4-4"/></svg></span>
    <div class="toast__body">
      <div class="toast__title">Saved</div>
      <div class="toast__message">Your changes are live.</div>
      <div class="toast__actions"><a class="toast__action" href="#toast">View record</a><button type="button" class="toast__action" data-toast-dismiss>Dismiss</button></div>
    </div>
    <button type="button" class="toast__close" data-toast-dismiss aria-label="Dismiss"></button>
  </div>
</div>

Full reference: contracts, guidance, anatomy →

Card layout

Bordered surface with a resting stacked shadow. Content classes (label / value / delta) build KPI tiles; compose several in auto-grid so each card keeps a natural width instead of stretching full-bleed across the preview.

Total Revenue
£1,250.00
+12.5% this month
Open invoices
18
−3 vs last week
Avg. days to pay
24
+2 days
html
<!-- icons: include the icon sheet once per page (see the Setup section, #setup) -->
<div class="auto-grid" style="--grid-min: 11rem">
  <div class="card card-body">
    <div class="card-label">Total Revenue</div>
    <div class="card-value">£1,250.00</div>
    <div class="card-delta"><svg class="icon" aria-hidden="true"><use href="#i-trending-up"/></svg> +12.5% this month</div>
  </div>
  <div class="card card-body">
    <div class="card-label">Open invoices</div>
    <div class="card-value">18</div>
    <div class="card-delta"><svg class="icon" aria-hidden="true"><use href="#i-trending-down"/></svg> −3 vs last week</div>
  </div>
  <div class="card card-body">
    <div class="card-label">Avg. days to pay</div>
    <div class="card-value">24</div>
    <div class="card-delta"><svg class="icon" aria-hidden="true"><use href="#i-trending-up"/></svg> +2 days</div>
  </div>
</div>

Full reference: contracts, guidance, anatomy →

Pagination htmx

The footer beneath a data table — a summary and page buttons. Each button hx-gets a page into the list body (an Exchange, not a widget).

INV-001 · Acme
INV-002 · Globex
INV-003 · Initech
html
<div class="hm-stack hm-measure-lg">
  <div id="hm-pag-body" class="hm-pag-list">
    <div class="hm-pag-row">INV-001 · Acme</div>
    <div class="hm-pag-row">INV-002 · Globex</div>
    <div class="hm-pag-row">INV-003 · Initech</div>
  </div>
  <div class="pagination" data-pagination data-grid-pagination data-grid-total="42" aria-label="Pagination">
    <span class="pagination-summary"><span class="bulk-summary-selected"><span data-bulk-count-target>0</span> of 42 selected</span><span class="bulk-summary-rows">42 rows</span></span>
    <div class="pagination-pages"><button class="pagination-page" disabled aria-label="Previous page">‹</button><button class="pagination-page is-current" aria-current="page">1</button><button class="pagination-page" hx-get="/mock/pagination/2" hx-target="#hm-pag-body" hx-swap="innerHTML">2</button><button class="pagination-page" hx-get="/mock/pagination/3" hx-target="#hm-pag-body" hx-swap="innerHTML">3</button><span class="pagination-ellipsis" aria-hidden="true">…</span><button class="pagination-page" hx-get="/mock/pagination/9" hx-target="#hm-pag-body" hx-swap="innerHTML">9</button><button class="pagination-page" hx-get="/mock/pagination/2" hx-target="#hm-pag-body" hx-swap="innerHTML" aria-label="Next page">›</button></div>
  </div>
</div>

Full reference: contracts, guidance, anatomy →

Data table data

A server-rendered data table on a real <table>, all HTML over the wire: search, sortable headers, filters, row selection (one page or every matching row), bulk actions, pagination, and deep-linkable URL-synced state. Optional extensions add column visibility, column resize, and inline cell editing. See How to use it and the DOM contract on this page for wiring.

Columns
0 selected

No customers found

Adjust the filters to widen your search.

html
<!-- icons: include the icon sheet once per page (see the Setup section, #setup) -->
<div class="hm-stack">
  <div class="table" data-grid data-grid-url data-bulk-count="0" data-grid-page="1" data-grid-edit-url="/mock/grid">
    <div class="filter-bar">
      <div class="filter-cell">
        <label class="filter-label" for="hm-grid-search">Search</label>
        <input class="filter-input" id="hm-grid-search" type="search" data-grid-search name="q" placeholder="Name or plan…">
      </div>
      <div class="filter-cell">
        <label class="filter-label" for="hm-grid-filter-plan">Plan</label>
        <select class="filter-select" id="hm-grid-filter-plan" data-grid-filter="plan">
          <option value="">Any plan</option>
          <option value="Free">Free</option>
          <option value="Pro">Pro</option>
          <option value="Team">Team</option>
          <option value="Enterprise">Enterprise</option>
        </select>
      </div>
      <!-- status is a filter-only field (no column): filters can narrow on any server field, not just displayed columns -->
      <div class="filter-cell">
        <label class="filter-label" for="hm-grid-filter-status">Status</label>
        <select class="filter-select" id="hm-grid-filter-status" data-grid-filter="status">
          <option value="">Any status</option>
          <option value="Active">Active</option>
          <option value="Trialing">Trialing</option>
          <option value="Churned">Churned</option>
        </select>
      </div>
      <div class="filter-cell">
        <label class="filter-label" for="hm-grid-page-size">Per page</label>
        <select class="filter-select" id="hm-grid-page-size" data-grid-page-size>
          <option value="2">2</option>
          <option value="4" selected>4</option>
          <option value="8">8</option>
        </select>
      </div>
      <details class="table-col-menu">
        <summary class="table-col-menu-trigger" aria-label="Toggle column visibility">Columns</summary>
        <div class="table-col-menu-panel">
          <label class="table-col-menu-item"><input type="checkbox" checked class="table-col-menu-checkbox" data-grid-col-toggle="first" aria-label="Show First name column"><span>First name</span></label>
          <label class="table-col-menu-item"><input type="checkbox" checked class="table-col-menu-checkbox" data-grid-col-toggle="last" aria-label="Show Last name column"><span>Last name</span></label>
          <label class="table-col-menu-item"><input type="checkbox" checked class="table-col-menu-checkbox" data-grid-col-toggle="plan" aria-label="Show Plan column"><span>Plan</span></label>
          <label class="table-col-menu-item"><input type="checkbox" checked class="table-col-menu-checkbox" data-grid-col-toggle="signed" aria-label="Show Signed up column"><span>Signed up</span></label>
          <button type="button" class="table-col-menu-reset" data-grid-cols-reset>Show all columns</button>
        </div>
      </details>
    </div>
    <div class="bulk-actions"><span aria-live="polite" aria-atomic="true"><span data-bulk-count-target>0</span> selected</span><button type="button" class="bulk-matching" data-grid-select-all-matching title="Select every row that matches the current search and filters (including other pages) — not only the rows on this page" aria-label="Select all results matching current search and filters">Select all <span data-grid-matching-total>…</span> results</button><button type="button" class="bulk-delete" data-grid-bulk-action="delete" data-grid-bulk-refresh hx-swap="none" hx-post="/mock/grid/bulk" hx-confirm="Delete the selected customers? This cannot be undone.">Delete</button><button type="button" class="bulk-clear" data-grid-clear>Clear</button></div>
    <div class="table-scroll">
      <div class="table-loading" aria-hidden="true"><span class="table-loading-spinner"><svg class="icon" aria-hidden="true"><use href="#i-loader-circle"/></svg></span></div>
      <div class="table-scroll-x">
        <table class="table-grid">
          <colgroup>
            <col class="table-col-select">
            <col data-col="first">
            <col data-col="last">
            <col data-col="plan">
            <col data-col="signed">
          </colgroup>
          <thead>
            <tr>
              <th class="table-th-select"><input type="checkbox" data-grid-select-all aria-label="Select all rows"></th>
              <th class="table-th" data-col="first" aria-sort="none"><button type="button" class="table-sort-button" data-grid-sort="first">First name<span class="table-sort-icon" aria-hidden="true"><svg class="icon" aria-hidden="true"><use href="#i-chevron-up"/></svg></span></button><span class="table-resize-handle" data-grid-resize="first" aria-hidden="true"></span></th>
              <th class="table-th" data-col="last" aria-sort="none"><button type="button" class="table-sort-button" data-grid-sort="last">Last name<span class="table-sort-icon" aria-hidden="true"><svg class="icon" aria-hidden="true"><use href="#i-chevron-up"/></svg></span></button><span class="table-resize-handle" data-grid-resize="last" aria-hidden="true"></span></th>
              <th class="table-th" data-col="plan" aria-sort="none"><button type="button" class="table-sort-button" data-grid-sort="plan">Plan<span class="table-sort-icon" aria-hidden="true"><svg class="icon" aria-hidden="true"><use href="#i-chevron-up"/></svg></span></button><span class="table-resize-handle" data-grid-resize="plan" aria-hidden="true"></span></th>
              <th class="table-th" data-col="signed" aria-sort="none"><button type="button" class="table-sort-button" data-grid-sort="signed">Signed up<span class="table-sort-icon" aria-hidden="true"><svg class="icon" aria-hidden="true"><use href="#i-chevron-up"/></svg></span></button><span class="table-resize-handle" data-grid-resize="signed" aria-hidden="true"></span></th>
            </tr>
          </thead>
          <tbody class="table-body" data-grid-body data-grid-src="/mock/grid/rows" hx-get="/mock/grid/rows" hx-trigger="load, grid:refresh" hx-swap="innerMorph">
            <tr class="tr-row" aria-hidden="true">
              <td class="tr-checkbox-cell"><span class="skeleton" data-shape="text"></span></td>
              <td class="tr-cell"><span class="skeleton" data-shape="text"></span></td>
              <td class="tr-cell"><span class="skeleton" data-shape="text"></span></td>
              <td class="tr-cell"><span class="skeleton" data-shape="text"></span></td>
              <td class="tr-cell"><span class="skeleton" data-shape="text"></span></td>
            </tr>
            <tr class="tr-row" aria-hidden="true">
              <td class="tr-checkbox-cell"><span class="skeleton" data-shape="text"></span></td>
              <td class="tr-cell"><span class="skeleton" data-shape="text"></span></td>
              <td class="tr-cell"><span class="skeleton" data-shape="text"></span></td>
              <td class="tr-cell"><span class="skeleton" data-shape="text"></span></td>
              <td class="tr-cell"><span class="skeleton" data-shape="text"></span></td>
            </tr>
            <tr class="tr-row" aria-hidden="true">
              <td class="tr-checkbox-cell"><span class="skeleton" data-shape="text"></span></td>
              <td class="tr-cell"><span class="skeleton" data-shape="text"></span></td>
              <td class="tr-cell"><span class="skeleton" data-shape="text"></span></td>
              <td class="tr-cell"><span class="skeleton" data-shape="text"></span></td>
              <td class="tr-cell"><span class="skeleton" data-shape="text"></span></td>
            </tr>
          </tbody>
        </table>
        <div class="table-empty">
          <span class="table-empty-icon"><svg class="icon" aria-hidden="true"><use href="#i-inbox"/></svg></span>
          <p class="table-empty-title">No customers found</p>
          <p class="table-empty-hint">Adjust the filters to widen your search.</p>
        </div>
      </div>
    </div>
    <span class="grid-announce" data-grid-announce aria-live="polite" aria-atomic="true"></span>
    <nav class="pagination" data-grid-pagination aria-label="Pagination"></nav>
  </div>
</div>

Full reference: contracts, guidance, anatomy →

Command palette interactive

The hx-get palette — the htmx4 flagship. Press ⌘K.

html
<!-- icons: include the icon sheet once per page (see the Setup section, #setup) -->
<button class="button" data-variant="outline" data-hm-open-command>Open palette <kbd class="kbd">⌘K</kbd></button>
<dialog class="command" data-command aria-label="Command palette" closedby="any">
  <div class="command__bar"><input class="command__input" type="search" name="q" placeholder="Search workspaces and records…" autocomplete="off" aria-controls="command-results" aria-autocomplete="list" hx-get="/mock/command" hx-trigger="input changed delay:150ms, focus once" hx-target="next .command__results"><button type="button" class="command__close" data-hm-close-command aria-label="Close command palette"><svg class="icon" aria-hidden="true"><use href="#i-x"/></svg></button></div>
  <div class="command__results" id="command-results" role="listbox" aria-label="Results"></div>
</dialog>

Full reference: contracts, guidance, anatomy →

Confirm dialog interactive

Designed replacement for window.confirm — every hx-confirm upgrades automatically.

html
<button class="button" data-variant="destructive" hx-delete="/mock/noop" hx-confirm="Delete this invoice? This cannot be undone.">Delete invoice</button>

Full reference: contracts, guidance, anatomy →

Popover interactive

Disclosure popover (`<details>`) — free-content panel; body can lazy-load via htmx. Not a focus-trapped/positioned popover.

Details
Dimensions

Filters, previews, quick forms.

html
<details class="popover">
  <summary class="button" data-variant="outline">Details</summary>
  <div class="popover__panel">
    <div class="hm-demo-title">Dimensions</div>
    <p class="hm-demo-muted">Filters, previews, quick forms.</p>
  </div>
</details>

Full reference: contracts, guidance, anatomy →

Tooltip

CSS-only visual hint (`data-tooltip`) — zero JS. A hint, not an accessible tooltip: keep it non-critical (no touch/SR/keyboard path). Optional `data-tooltip-open` forces the hint visible (capture/docs).

html
<button class="button" data-variant="outline" data-tooltip="Saved 2 minutes ago">Hover me</button>

Full reference: contracts, guidance, anatomy →

Dialog interactive

Modal on the native <dialog> — one line of JS to open, close for free (Esc / backdrop / method=dialog submit). Focus-trapped by the platform.

Delete workspace?

This permanently deletes the workspace and every record in it. This action cannot be undone.

html
<!-- icons: include the icon sheet once per page (see the Setup section, #setup) -->
<button class="button" data-variant="primary" data-dialog-open="hm-dialog-demo">Delete workspace…</button>
<dialog class="dialog" id="hm-dialog-demo" aria-labelledby="hm-dialog-demo-title" closedby="any">
  <form method="dialog">
    <div class="dialog__header">
      <h2 class="dialog__title" id="hm-dialog-demo-title">Delete workspace?</h2>
      <button type="submit" class="dialog__close" aria-label="Close dialog"><svg class="icon" aria-hidden="true"><use href="#i-x"/></svg></button>
    </div>
    <div class="dialog__body">
      <p>This permanently deletes the workspace and every record in it. This action cannot be undone.</p>
    </div>
    <div class="dialog__footer"><button type="submit" class="button" data-variant="outline">Cancel</button><button type="submit" class="button" data-variant="destructive" value="confirm">Delete</button></div>
  </form>
</dialog>

Full reference: contracts, guidance, anatomy →

Drawer interactive

Edge-anchored panel on the native <dialog> — a drawer with a modal's guarantees (focus trap, inert background, Esc, backdrop). Built on the dialog: shares its opener, adds a side + slide. No drawer-specific JS. Body is a composition host — nest field, badge, card, controls, …

Filters

Compose field, toggle-group, switch, and controls inside the scrollable body — guests keep their own DOM contracts.

Matches title and secondary fields on the list exchange.

Density
Status
3 filters

Record detail

Open record to load a composed peek fragment…

html
<!-- icons: include the icon sheet once per page (see the Setup section, #setup) -->
<div class="hm-demo-row" style="gap:var(--space-sm);flex-wrap:wrap">
  <button type="button" class="button" data-variant="outline" data-dialog-open="hm-drawer-demo">Open filters</button>
  <button type="button" class="button" data-variant="outline" hx-get="/mock/drawer/detail" hx-target="#hm-drawer-lazy-body" hx-swap="innerHTML" data-dialog-open="hm-drawer-lazy">Open record</button>
</div>
<dialog class="drawer" id="hm-drawer-demo" data-side="right" data-width="md" aria-labelledby="hm-drawer-demo-title" closedby="any">
  <form method="dialog">
    <div class="drawer__header">
      <h2 class="drawer__title" id="hm-drawer-demo-title">Filters</h2>
      <button type="submit" class="drawer__close" aria-label="Close drawer"><svg class="icon" aria-hidden="true"><use href="#i-x"/></svg></button>
    </div>
    <div class="drawer__body" tabindex="0" aria-label="Filter controls">
      <div class="stack" data-gap="md">
        <p class="hm-demo-muted" style="margin:0">Compose field, toggle-group, switch, and controls inside the scrollable body — guests keep their own DOM contracts.</p>
        <div class="form-field">
          <label class="form-label" for="hm-drawer-q">Search</label>
          <input class="form-input" id="hm-drawer-q" type="search" name="q" placeholder="Name, id, or region…" aria-describedby="hm-drawer-q-hint">
          <p class="form-hint" id="hm-drawer-q-hint">Matches title and secondary fields on the list exchange.</p>
        </div>
        <div class="stack" data-gap="xs">
          <div class="form-label" id="hm-drawer-density-label">Density</div>
          <fieldset class="toggle-group" role="radiogroup" aria-labelledby="hm-drawer-density-label">
            <label><input type="radio" name="hm-drawer-density" value="comfortable" checked><span>Comfortable</span></label>
            <label><input type="radio" name="hm-drawer-density" value="compact"><span>Compact</span></label>
          </fieldset>
        </div>
        <fieldset class="stack" data-gap="xs" style="border:0;padding:0;margin:0">
          <legend class="form-label">Status</legend>
          <label class="hm-inline"><input type="checkbox" class="checkbox" name="status" value="active" checked> Active</label>
          <label class="hm-inline"><input type="checkbox" class="checkbox" name="status" value="trial"> Trialing</label>
          <label class="hm-inline"><input type="checkbox" class="checkbox" name="status" value="churned"> Churned</label>
        </fieldset>
        <div class="hm-demo-row" style="justify-content:space-between;align-items:center;gap:var(--space-sm)">
          <label class="switch"><input type="checkbox" name="mine" value="1" data-switch><span class="switch__track" aria-hidden="true"></span><span>Only my records</span></label>
          <span class="badge" data-tone="neutral"><span class="badge-icon"><svg class="icon" aria-hidden="true"><use href="#i-filter"/></svg></span>3 filters</span>
        </div>
        <div class="alert" data-tone="info" role="alert">
          <span class="alert__icon"><svg class="icon" aria-hidden="true"><use href="#i-info"/></svg></span>
          <div class="alert__body">
            <div class="alert__title">Server owns the query</div>
            <div class="alert__description">Apply posts filter params on the list exchange — this form is method=dialog only so the gallery can close without a backend.</div>
          </div>
        </div>
      </div>
    </div>
    <div class="drawer__footer"><button type="submit" class="button" data-variant="ghost" value="reset">Reset</button><button type="submit" class="button" data-variant="primary" value="apply">Apply</button></div>
  </form>
</dialog>
<dialog class="drawer" id="hm-drawer-lazy" data-width="md" data-side="right" closedby="any" aria-labelledby="hm-drawer-lazy-title">
  <div class="drawer__header">
    <h2 class="drawer__title" id="hm-drawer-lazy-title">Record detail</h2>
    <div class="hm-demo-row" style="gap:var(--space-xs);align-items:center">
      <button type="button" class="button" data-variant="ghost" data-drawer-expand aria-pressed="false" aria-label="Expand drawer panel">Expand</button>
      <form method="dialog"><button type="submit" class="drawer__close" aria-label="Close"><svg class="icon" aria-hidden="true"><use href="#i-x"/></svg></button></form>
    </div>
  </div>
  <div id="hm-drawer-lazy-body" class="drawer__body" tabindex="0" aria-label="Record detail body" aria-live="polite">
    <p class="hm-demo-muted">Open record to load a composed peek fragment…</p>
  </div>
  <div class="drawer__footer">
    <form method="dialog"><button type="submit" class="button" data-variant="ghost">Close</button></form>
    <a class="button" data-variant="primary" href="blueprints/record-page.html">Open full page</a>
  </div>
</dialog>

Full reference: contracts, guidance, anatomy →

Selection controls

Designed checkbox / radio / switch on native inputs — semantics free.

html
<div class="hm-demo-row">
  <label class="hm-inline"><input type="checkbox" class="checkbox" checked> Checkbox</label>
  <label class="hm-inline"><input type="radio" class="radio" checked> Radio</label>
  <label class="hm-inline"><input type="checkbox" class="switch" checked> Switch</label>
</div>

Full reference: contracts, guidance, anatomy →

Field forms

The label + control + help + error triad as one accessible unit. Error state derives from aria-invalid; help/error bind via aria-describedby.

Receipts and renewal notices go here.

Use lowercase letters, numbers and hyphens only.

html
<div class="hm-stack hm-measure">
  <div class="form-field">
    <label class="form-label" for="hm-field-email">Billing email<span class="form-required">*</span></label>
    <input class="form-input" id="hm-field-email" type="email" required placeholder="you@company.com" aria-describedby="hm-field-email-hint">
    <p class="form-hint" id="hm-field-email-hint">Receipts and renewal notices go here.</p>
  </div>
  <div class="form-field">
    <label class="form-label" for="hm-field-slug">Workspace slug</label>
    <input class="form-input" id="hm-field-slug" value="Acme Corp" aria-invalid="true" aria-describedby="hm-field-slug-error">
    <p class="form-error" id="hm-field-slug-error">Use lowercase letters, numbers and hyphens only.</p>
  </div>
  <div class="form-field">
    <label class="form-label" for="hm-field-color">Brand colour</label>
    <div class="form-color-group" data-color-group><input class="form-color-input" id="hm-field-color" type="color" value="#3b82f6"><input class="form-color-hex" type="text" spellcheck="false" autocomplete="off" aria-label="Hex colour" value="#3b82f6"></div>
  </div>
  <div class="form-field">
    <label class="form-label" for="hm-field-due">Due time</label>
    <div class="form-time-group" data-time-group><input class="form-input" id="hm-field-due" type="time" value="14:30"><input data-time-iso class="form-time-iso" type="text" spellcheck="false" autocomplete="off" aria-label="ISO time" value="14:30"></div>
  </div>
  <div class="form-field">
    <label class="form-label" for="hm-field-on">On date</label>
    <div class="form-date-group" data-date-group><input class="form-input" id="hm-field-on" type="date" value="2026-06-01"><input data-date-iso class="form-date-iso" type="text" spellcheck="false" autocomplete="off" aria-label="ISO date" value="2026-06-01"></div>
  </div>
</div>

Full reference: contracts, guidance, anatomy →

Slider forms

Native <input type=range> — styled track + thumb, both themes, with an editable value companion via a tiny delegated controller.

html
<div class="hm-stack hm-measure">
  <label class="form-label" for="hm-slider-vol">Volume</label>
  <div class="form-slider-group"><input id="hm-slider-vol" type="range" data-slider class="form-slider" min="0" max="100" step="1" value="70"><input data-range-value class="form-slider-value" type="text" inputmode="decimal" spellcheck="false" autocomplete="off" aria-label="Slider value" value="70"></div>
</div>

Full reference: contracts, guidance, anatomy →

Confirm panel forms

The irreversible-action consent gate: a checklist of obligations that must be ticked before the primary action arms, plus live and revoked summary states.

  • Save draftGo live

This action is recorded in the audit log with your identity and timestamp.

Currently live
Enabled 12 May by j.reyes.
html
<div class="hm-measure">
  <div class="confirm-panel" data-state-value="off">
    <ul data-confirm-gate class="confirm-checklist" data-required-count="2">
      <li class="confirm-row" data-required="true">
        <input type="checkbox" class="confirm-checkbox" data-required="true" id="confirm-1">
        <label for="confirm-1" class="confirm-row-label"><span class="confirm-title">I have exported a backup of live data</span><span class="confirm-caption">Rollback needs a snapshot taken today.</span></label>
      </li>
      <li class="confirm-row" data-required="true">
        <input type="checkbox" class="confirm-checkbox" data-required="true" id="confirm-2">
        <label for="confirm-2" class="confirm-row-label"><span class="confirm-title">The billing owner has approved this change</span></label>
      </li>
      <li class="confirm-row" data-required="false">
        <input type="checkbox" class="confirm-checkbox" id="confirm-3">
        <label for="confirm-3" class="confirm-row-label"><span class="confirm-title">Notify the team afterwards (optional)</span></label>
      </li>
      <li class="confirm-actions"><a href="#" class="confirm-secondary">Save draft</a><a data-confirm-href="#go-live" aria-disabled="true" class="confirm-primary">Go live</a></li>
    </ul>
    <p class="confirm-audit">This action is recorded in the audit log with your identity and timestamp.</p>
  </div>
  <div class="confirm-panel" data-state-value="live">
    <div class="confirm-summary" data-confirm-tone="success">
      <div class="confirm-summary-title">Currently live</div>
      <div class="confirm-summary-body">Enabled 12 May by j.reyes.</div>
    </div>
    <div class="confirm-actions"><a href="#" class="confirm-revoke">Revoke</a></div>
  </div>
</div>

Full reference: contracts, guidance, anatomy →

Form chrome forms

The structural form pieces: titled sections, the validation-error summary, and the multi-section progress stepper.

  1. 1Details
  2. 2Schedule
  3. 3Review

Contact details

Shown on invoices and receipts.

html
<div class="hm-stack hm-measure">
  <div class="form-errors" role="alert">
    <svg class="form-errors-icon" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2" aria-hidden="true"><path stroke-linecap="round" stroke-linejoin="round" d="M12 9v3.75m-9.303 3.376c-.866 1.5.217 3.374 1.948 3.374h14.71c1.73 0 2.813-1.874 1.948-3.374L13.949 3.378c-.866-1.5-3.032-1.5-3.898 0L2.697 16.126zM12 15.75h.007v.008H12v-.008z"/></svg>
    <div class="form-errors-body">
      <h3 class="form-errors-title">Validation Error</h3>
      <ul class="form-errors-list" role="list">
        <li>Name is required</li>
        <li>Start date must be before end date</li>
      </ul>
    </div>
  </div>
  <ol class="form-stepper" role="list" aria-label="Form progress">
    <li class="form-stepper-item is-not-last" aria-current="step"><span class="form-stepper-circle is-active"><span>1</span></span><span class="form-stepper-label is-active">Details</span><span class="form-stepper-connector" aria-hidden="true"></span></li>
    <li class="form-stepper-item is-not-last"><span class="form-stepper-circle"><span>2</span></span><span class="form-stepper-label">Schedule</span><span class="form-stepper-connector" aria-hidden="true"></span></li>
    <li class="form-stepper-item"><span class="form-stepper-circle"><span>3</span></span><span class="form-stepper-label">Review</span></li>
  </ol>
  <section class="form-section">
    <h3 class="form-section-title">Contact details</h3>
    <p class="form-section-note">Shown on invoices and receipts.</p>
    <div class="form-field">
      <label class="form-label" for="hm-fc-name">Full name<span class="form-required" aria-hidden="true">*</span></label>
      <input id="hm-fc-name" class="form-input" type="text" aria-required="true">
    </div>
  </section>
</div>

Full reference: contracts, guidance, anatomy →

Wizard forms

Multi-stage form navigation: the stepper drives stage reveal — back freely, forward one validated step at a time.

html
<div data-wizard data-step="0" class="hm-measure-lg">
  <ol class="form-stepper" role="list" aria-label="Form progress">
    <li class="form-stepper-item is-not-last" data-state="current" aria-current="step"><button type="button" class="form-stepper-button" data-step-to="0"><span class="form-stepper-circle is-active"><span>1</span></span><span class="form-stepper-label is-active">Details</span><span class="visually-hidden" data-step-status>current</span></button><span class="form-stepper-connector" aria-hidden="true"></span></li>
    <li class="form-stepper-item is-not-last" data-state="pending"><button type="button" class="form-stepper-button" data-step-to="1"><span class="form-stepper-circle"><span>2</span></span><span class="form-stepper-label">Schedule</span><span class="visually-hidden" data-step-status>pending</span></button><span class="form-stepper-connector" aria-hidden="true"></span></li>
    <li class="form-stepper-item" data-state="pending"><button type="button" class="form-stepper-button" data-step-to="2"><span class="form-stepper-circle"><span>3</span></span><span class="form-stepper-label">Review</span><span class="visually-hidden" data-step-status>pending</span></button></li>
  </ol>
  <div class="wizard-stage" data-stage="0">
    <div class="form-field">
      <label class="form-label" for="hm-wiz-name">Project name<span class="form-required" aria-hidden="true">*</span></label>
      <input id="hm-wiz-name" class="form-input" type="text" required aria-required="true">
    </div>
  </div>
  <div class="wizard-stage" data-stage="1" hidden>
    <div class="form-field">
      <label class="form-label" for="hm-wiz-date">Start date</label>
      <input id="hm-wiz-date" class="form-input" type="date">
    </div>
  </div>
  <div class="wizard-stage" data-stage="2" hidden>
    <p>Review your answers, then submit.</p>
  </div>
</div>

Full reference: contracts, guidance, anatomy →

Money field forms

Major-unit decimal input over a hidden minor-unit carrier — the form posts integer minor units, never floats.

html
<div class="money hm-measure" data-money data-currency="GBP" data-scale="2">
  <div class="form-money-group"><span class="form-money-prefix" aria-hidden="true">£</span><input type="text" inputmode="decimal" id="hm-money-input" value="15.00" class="form-input form-input-trailing" placeholder="0.00" aria-label="Amount (GBP)"></div>
  <input type="hidden" name="amount_minor" value="1500">
  <input type="hidden" name="amount_currency" value="GBP">
</div>

Full reference: contracts, guidance, anatomy →

PDF viewer data

The hx-pdf viewing shell: server-authorized bytes, lazy PDF.js rendering, toolbar slots for paging/zoom, URL deep-links — progressive enhancement over a download link.

Download
html
<section class="pdf" data-pdf data-pdf-src="sample.pdf" data-pdf-lib="https://cdn.jsdelivr.net/npm/pdfjs-dist@4.10.38/legacy/build/pdf.min.mjs" data-pdf-worker="https://cdn.jsdelivr.net/npm/pdfjs-dist@4.10.38/legacy/build/pdf.worker.min.mjs">
  <header class="pdf-toolbar" data-pdf-toolbar>
    <button type="button" class="button" data-size="sm" data-variant="outline" data-pdf-prev>Previous</button>
    <label>Page <input class="pdf-page-input" data-pdf-page value="1" inputmode="numeric" aria-label="Page number"></label>
    <span class="pdf-page-count" data-pdf-page-count></span>
    <button type="button" class="button" data-size="sm" data-variant="outline" data-pdf-next>Next</button>
    <span class="pdf-toolbar-spacer"></span>
    <button type="button" class="button" data-size="sm" data-variant="outline" data-pdf-zoom-out aria-label="Zoom out">−</button>
    <button type="button" class="button" data-size="sm" data-variant="outline" data-pdf-zoom-in aria-label="Zoom in">+</button>
    <button type="button" class="button" data-size="sm" data-variant="outline" data-pdf-fit-width>Fit width</button>
    <a href="sample.pdf" class="button" data-size="sm" data-variant="ghost" data-pdf-download-link download>Download</a>
  </header>
  <div class="pdf-status" data-pdf-status aria-live="polite"></div>
  <div class="pdf-stage" data-pdf-viewer tabindex="0">
    <noscript><a href="sample.pdf" download>Download PDF</a></noscript>
  </div>
</section>

Full reference: contracts, guidance, anatomy →

Two-factor panel forms

The 2FA enrolment/settings card: QR + manual secret, the big-digit code input, recovery-code grid, and factor status rows.

Set Up 2FA

Aurora Ops

Authenticator App

Scan this QR code with your authenticator app.

Or enter the secret manually: JBSW Y3DP EHPK 3PXP


QK2M-8Y1DHW7C-04RAZX3N-55PTMB9E-71LQ
Authenticator app
Enabled
Email codes
Off
Back to App
html
<div class="auth-card hm-measure">
  <h1 class="auth-card-title">Set Up 2FA</h1>
  <p class="auth-card-subtitle">Aurora Ops</p>
  <h2 class="auth-section-title">Authenticator App</h2>
  <p class="auth-section-body">Scan this QR code with your authenticator app.</p>
  <div class="auth-qr-container"><button class="button" data-variant="outline">Generate QR Code</button></div>
  <p class="auth-section-body">Or enter the secret manually: <code class="auth-secret-inline">JBSW Y3DP EHPK 3PXP</code></p>
  <form class="auth-form">
    <div class="auth-field">
      <label for="hm-2fa-code" class="auth-label">Enter code from app</label>
      <input type="text" id="hm-2fa-code" inputmode="numeric" pattern="[0-9]*" maxlength="6" placeholder="000000" class="auth-input-code">
    </div>
    <button type="submit" class="button auth-submit" data-variant="primary">Verify and Enable</button>
  </form>
  <hr class="auth-hr">
  <div class="auth-recovery-alert" role="alert">
    <h3 class="auth-recovery-alert-title">Save Your Recovery Codes</h3>
    <p class="auth-recovery-alert-body">Store these codes in a safe place. Each code can only be used once.</p>
  </div>
  <div class="auth-recovery-grid"><span class="auth-recovery-pill">QK2M-8Y1D</span><span class="auth-recovery-pill">HW7C-04RA</span><span class="auth-recovery-pill">ZX3N-55PT</span><span class="auth-recovery-pill">MB9E-71LQ</span></div>
  <div class="auth-status-row">
    <div class="auth-status-label">Authenticator app</div>
    <span class="badge" data-tone="success">Enabled</span>
  </div>
  <div class="auth-status-row is-last">
    <div class="auth-status-label">Email codes</div>
    <span class="badge">Off</span>
  </div>
  <a href="#" class="auth-back-link">Back to App</a>
</div>

Full reference: contracts, guidance, anatomy →

Search select forms

The FK typeahead: debounced remote search into a listbox, then a per-row select exchange that fills a hidden id. Domain data maps into a fixed result-row anatomy (name / secondary / optional media) — do not invent a new combobox per entity. Demo: focus the input (or type) to open; media is optional so some rows are text-only.

Type to search — rows share one anatomy; media is optional
html
<div class="search-select hm-measure" data-widget="search_select" data-blur-grace-ms="200" data-confirm-hold-ms="1800">
  <input type="hidden" name="company" id="hm-ss-field" value="">
  <input type="text" id="hm-ss-input" class="search-select-input" name="q" form="hm-detached-q" placeholder="Search companies, people, SKUs…" autocomplete="off" role="combobox" aria-expanded="false" aria-controls="hm-ss-results" aria-autocomplete="list" aria-haspopup="listbox" hx-get="/mock/typeahead" hx-trigger="keyup changed delay:300ms[this.value.trim().length>0]" hx-target="#hm-ss-results" hx-params="q">
  <div id="hm-ss-results" role="listbox" aria-label="Suggestions" class="search-select-results">
    <div class="search-select-prompt" role="option" aria-disabled="true">Type to search — rows share one anatomy; media is optional</div>
  </div>
</div>

Full reference: contracts, guidance, anatomy →

Combobox forms

Searchable single-select over a list of options — a native <select> progressively enhanced into a type-to-filter combobox. Fixed lists by default; growing catalogues (add a missing value) use the same Hyperpart with data-allow-create — not a separate part.

html
<div class="hm-stack hm-measure" data-gap="md">
  <label class="field" for="hm-cb-field">
    <span class="field__label">Priority (fixed list)</span>
    <select id="hm-cb-field" name="priority" data-combobox class="form-input">
      <option value="">Select a priority…</option>
      <option value="low">Low</option>
      <option value="medium" selected>Medium</option>
      <option value="high">High</option>
      <option value="urgent">Urgent</option>
    </select>
  </label>
  <label class="field" for="hm-cb-dept">
    <span class="field__label">Department (growing list — type to add)</span>
    <select id="hm-cb-dept" name="department" data-combobox data-allow-create class="form-input">
      <option value="">Pick or add a department…</option>
      <option value="operations">Operations</option>
      <option value="finance">Finance</option>
      <option value="support">Support</option>
    </select>
  </label>
</div>

Full reference: contracts, guidance, anatomy →

Tags forms

Multi-value chips + free create — a native text input carrying a comma-joined value, progressively enhanced into a chips UI. JS off: a usable comma-separated text field. JS on: type + Enter/comma creates a chip, blur commits a leftover token, × removes; the native input stays as the submitted value.

html
<label class="field hm-measure" for="hm-tags-field"><span class="field__label">Labels</span><input id="hm-tags-field" name="labels" type="text" data-tags class="form-input" value="urgent,backend" placeholder="Add a label…"></label>

Full reference: contracts, guidance, anatomy →

Date range forms

Two native date inputs driving one htmx exchange — the from/to filter bar for time-scoped regions.

html
<div class="date-range-picker date-range-bar" data-date-range>
  <label class="date-range-label" for="hm-dr-from">From</label>
  <span class="date-range-group"><input type="date" id="hm-dr-from" name="date_from" value="2026-06-01" class="date-range-input" hx-get="/mock/search" hx-target="#hm-dr-out" hx-swap="innerHTML" hx-include="closest .date-range-bar"><input data-date-iso class="date-range-iso" type="text" spellcheck="false" autocomplete="off" aria-label="From ISO date" value="2026-06-01"></span>
  <label class="date-range-label" for="hm-dr-to">To</label>
  <span class="date-range-group"><input type="date" id="hm-dr-to" name="date_to" value="2026-06-30" class="date-range-input" hx-get="/mock/search" hx-target="#hm-dr-out" hx-swap="innerHTML" hx-include="closest .date-range-bar"><input data-date-iso class="date-range-iso" type="text" spellcheck="false" autocomplete="off" aria-label="To ISO date" value="2026-06-30"></span>
  <div id="hm-dr-out" hidden></div>
</div>

Full reference: contracts, guidance, anatomy →

Toggle group

Segmented control on native radios.

html
<!-- icons: include the icon sheet once per page (see the Setup section, #setup) -->
<fieldset class="toggle-group" role="radiogroup">
  <label><input type="radio" name="hm-view" checked><span><svg class="icon icon--size-sm" aria-hidden="true"><use href="#i-list"/></svg> List</span></label>
  <label><input type="radio" name="hm-view"><span><svg class="icon icon--size-sm" aria-hidden="true"><use href="#i-kanban"/></svg> Board</span></label>
  <label><input type="radio" name="hm-view"><span><svg class="icon icon--size-sm" aria-hidden="true"><use href="#i-calendar"/></svg> Calendar</span></label>
</fieldset>

Full reference: contracts, guidance, anatomy →

Switch form

On/off control — progressive enhancement over a native checkbox. State is the checkbox's checked attribute (DOM), not a JS store.

html
<div class="hm-demo-row">
  <label class="switch"><input type="checkbox" name="hm-notify" data-switch checked><span class="switch__track" aria-hidden="true"></span><span>Email notifications</span></label>
  <label class="switch"><input type="checkbox" name="hm-digest" data-switch><span class="switch__track" aria-hidden="true"></span><span>Weekly digest</span></label>
</div>

Full reference: contracts, guidance, anatomy →

Toggle form

Single pressable mode control (Bold / Italic toolbar style). State is aria-pressed on the button — pair with toggle-group for exclusive segments.

html
<div class="hm-demo-row" role="group" aria-label="Text style">
  <button type="button" class="toggle" data-toggle aria-pressed="true"><strong>B</strong> Bold</button>
  <button type="button" class="toggle" data-toggle aria-pressed="false"><em>I</em> Italic</button>
  <button type="button" class="toggle" data-toggle aria-pressed="false" data-size="sm"><span style="text-decoration:underline">U</span></button>
</div>

Full reference: contracts, guidance, anatomy →

Keyboard key docs

Shortcut chip for docs and command chrome — <kbd class=kbd>.

⌘KEsc↵⇧
html
<div class="hm-demo-row">
  <kbd class="kbd">⌘K</kbd>
  <kbd class="kbd">Esc</kbd>
  <kbd class="kbd">↵</kbd>
  <kbd class="kbd">⇧</kbd>
</div>

Full reference: contracts, guidance, anatomy →

Aspect ratio layout

Media frame that locks width/height — images and embeds fill with object-fit cover.

1:1
16:9
4:3
html
<div class="hm-demo-row" style="gap: var(--space-md); align-items: flex-start;">
  <div class="aspect-ratio" data-ratio="1/1" style="width: 6rem;" aria-label="1:1 frame"><span style="display:grid;place-items:center;background:var(--colour-brand-soft);color:var(--colour-brand-text);font-size:var(--text-xs);">1:1</span></div>
  <div class="aspect-ratio" data-ratio="16/9" style="width: 10rem;" aria-label="16:9 frame"><span style="display:grid;place-items:center;background:var(--colour-brand-soft);color:var(--colour-brand-text);font-size:var(--text-xs);">16:9</span></div>
  <div class="aspect-ratio" data-ratio="4/3" style="width: 8rem;" aria-label="4:3 frame"><span style="display:grid;place-items:center;background:var(--colour-brand-soft);color:var(--colour-brand-text);font-size:var(--text-xs);">4:3</span></div>
</div>

Full reference: contracts, guidance, anatomy →

Item data

Generic list row — optional media, title, description, trailing actions. Compose into lists, pickers, and search results.

Maya Reyes
Operations · North grid
Jordan Kim
Support · Escalations
html
<div class="item-group hm-measure-lg">
  <div class="item" data-item data-variant="outline">
    <span class="item__media" aria-hidden="true">MR</span>
    <div class="item__content">
      <div class="item__title">Maya Reyes</div>
      <div class="item__description">Operations · North grid</div>
    </div>
    <div class="item__actions"><button type="button" class="button" data-variant="ghost" data-size="sm">View</button></div>
  </div>
  <div class="item" data-item data-variant="outline">
    <span class="item__media" aria-hidden="true">JK</span>
    <div class="item__content">
      <div class="item__title">Jordan Kim</div>
      <div class="item__description">Support · Escalations</div>
    </div>
    <div class="item__actions"><button type="button" class="button" data-variant="ghost" data-size="sm">View</button></div>
  </div>
</div>

Full reference: contracts, guidance, anatomy →

Hover card overlay

Rich preview on hover/focus/tap — progressive enhancement over a trigger link or button.

html
<div class="hover-card" data-hover-card>
  <button type="button" class="hover-card__trigger" aria-expanded="false">@maya</button>
  <div class="hover-card__content" role="tooltip">
    <p class="hover-card__title">Maya Reyes</p>
    <p class="hover-card__description">Operations lead · Online now</p>
  </div>
</div>

Full reference: contracts, guidance, anatomy →

Bubble media

Chat bubble shell — rounded content for inbound/outbound speech. Compose inside message rows.

Can we reschedule the walkthrough to Thursday?

Thursday 14:00 works — I'll send a calendar hold.

html
<div class="hm-demo-row" style="gap:1rem;flex-wrap:wrap;align-items:flex-end">
  <div class="bubble" data-bubble data-from="in">
    <p>Can we reschedule the walkthrough to Thursday?</p>
  </div>
  <div class="bubble" data-bubble data-from="out">
    <p>Thursday 14:00 works — I'll send a calendar hold.</p>
  </div>
</div>

Full reference: contracts, guidance, anatomy →

Message media

Chat message row — media + author/time meta + bubble. Outbound rows reverse with data-from=out.

Maya Reyes

Can we reschedule the walkthrough to Thursday?

You

Thursday 14:00 works — I'll send a calendar hold.

html
<div class="hm-stack" style="gap:0.75rem;max-width:28rem">
  <div class="message" data-message data-from="in">
    <span class="message__media" aria-hidden="true">MR</span>
    <div class="message__body">
      <div class="message__meta"><span class="message__author">Maya Reyes</span><time class="message__time" datetime="2026-07-12T10:02">10:02</time></div>
      <div class="bubble" data-bubble data-from="in">
        <p>Can we reschedule the walkthrough to Thursday?</p>
      </div>
    </div>
  </div>
  <div class="message" data-message data-from="out">
    <span class="message__media" aria-hidden="true">You</span>
    <div class="message__body">
      <div class="message__meta"><span class="message__author">You</span><time class="message__time" datetime="2026-07-12T10:04">10:04</time></div>
      <div class="bubble" data-bubble data-from="out">
        <p>Thursday 14:00 works — I'll send a calendar hold.</p>
      </div>
    </div>
  </div>
</div>

Full reference: contracts, guidance, anatomy →

Message scroller media

Chat transcript viewport — scrollable stack of message rows. Document order is chronological (newest last).

Maya Reyes

Can we reschedule the walkthrough to Thursday?

You

Thursday 14:00 works — I'll send a calendar hold.

Maya Reyes

Perfect — see you then.

html
<div class="message-scroller" data-message-scroller role="log" aria-label="Conversation" aria-live="polite" tabindex="0">
  <div class="message" data-message data-from="in">
    <span class="message__media" aria-hidden="true">MR</span>
    <div class="message__body">
      <div class="message__meta"><span class="message__author">Maya Reyes</span><time class="message__time" datetime="2026-07-12T10:02">10:02</time></div>
      <div class="bubble" data-bubble data-from="in">
        <p>Can we reschedule the walkthrough to Thursday?</p>
      </div>
    </div>
  </div>
  <div class="message" data-message data-from="out">
    <span class="message__media" aria-hidden="true">You</span>
    <div class="message__body">
      <div class="message__meta"><span class="message__author">You</span><time class="message__time" datetime="2026-07-12T10:04">10:04</time></div>
      <div class="bubble" data-bubble data-from="out">
        <p>Thursday 14:00 works — I'll send a calendar hold.</p>
      </div>
    </div>
  </div>
  <div class="message" data-message data-from="in">
    <span class="message__media" aria-hidden="true">MR</span>
    <div class="message__body">
      <div class="message__meta"><span class="message__author">Maya Reyes</span><time class="message__time" datetime="2026-07-12T10:05">10:05</time></div>
      <div class="bubble" data-bubble data-from="in">
        <p>Perfect — see you then.</p>
      </div>
    </div>
  </div>
</div>

Full reference: contracts, guidance, anatomy →

Marker media

Map pin chrome + optional label — host owns map projection / placement.

HQDepotAlert
html
<div class="hm-demo-row" style="gap:2rem;align-items:flex-end;padding:1.5rem;background:var(--colour-surface-muted);border-radius:var(--radius-md)">
  <span class="marker" data-marker><span class="marker__pin" aria-hidden="true"></span><span class="marker__label">HQ</span></span>
  <span class="marker" data-marker data-tone="success" data-size="lg"><span class="marker__pin" aria-hidden="true"></span><span class="marker__label">Depot</span></span>
  <span class="marker" data-marker data-tone="danger"><span class="marker__pin" aria-hidden="true"></span><span class="marker__label">Alert</span></span>
</div>

Full reference: contracts, guidance, anatomy →

Accordion interactive

Native <details> group; single-open via the HTML name= attribute — opening one closes its siblings, zero JS.

What is a Hyperpart?
A server-rendered partial plus its exchange contract — the htmx-native unit of reuse.
Do I need a client framework?
No — state lives on the server and htmx swaps the markup.
Can two panels be open at once?
Not while they share a name=. Drop the attribute for an independent, multi-open group.
html
<div class="accordion">
  <details class="accordion__item" name="hm-acc" open>
    <summary class="accordion__trigger">What is a Hyperpart?</summary>
    <div class="accordion__panel">A server-rendered partial plus its exchange contract — the htmx-native unit of reuse.</div>
  </details>
  <details class="accordion__item" name="hm-acc">
    <summary class="accordion__trigger">Do I need a client framework?</summary>
    <div class="accordion__panel">No — state lives on the server and htmx swaps the markup.</div>
  </details>
  <details class="accordion__item" name="hm-acc">
    <summary class="accordion__trigger">Can two panels be open at once?</summary>
    <div class="accordion__panel">Not while they share a name=. Drop the attribute for an independent, multi-open group.</div>
  </details>
</div>

Full reference: contracts, guidance, anatomy →

Tabs interactive

A lazy tab strip — an honest link-strip (buttons + aria-current, no unkept role=tablist). Each panel hx-gets its content the first time it is shown.

Active on the Pro plan, renewing 1 August.

html
<!-- icons: include the icon sheet once per page (see the Setup section, #setup) -->
<div class="tabs" data-tabs>
  <div class="tabs__list"><button type="button" class="tabs__tab" aria-current="true" data-tab-target="hm-tab-overview">Overview</button><button type="button" class="tabs__tab" data-tab-target="hm-tab-activity">Activity</button><button type="button" class="tabs__tab" data-tab-target="hm-tab-settings">Settings</button></div>
  <div id="hm-tab-overview" class="tabs__panel">
    <p class="hm-demo-muted">Active on the Pro plan, renewing 1 August.</p>
  </div>
  <div id="hm-tab-activity" class="tabs__panel" hidden hx-get="/mock/tabs/activity" hx-trigger="intersect once" hx-swap="innerHTML">
    <div class="tabs__loading"><svg class="icon" aria-hidden="true"><use href="#i-loader-circle"/></svg></div>
  </div>
  <div id="hm-tab-settings" class="tabs__panel" hidden hx-get="/mock/tabs/settings" hx-trigger="intersect once" hx-swap="innerHTML">
    <div class="tabs__loading"><svg class="icon" aria-hidden="true"><use href="#i-loader-circle"/></svg></div>
  </div>
</div>

Full reference: contracts, guidance, anatomy →

Avatar

Initials or image; stacked groups.

JDAK+3HM
html
<div class="hm-demo-row">
  <span class="avatar-group"><span class="avatar">JD</span><span class="avatar">AK</span><span class="avatar">+3</span></span>
  <span class="avatar" data-size="lg">HM</span>
</div>

Full reference: contracts, guidance, anatomy →

Progress

Toned determinate bar.

html
<div class="hm-stack hm-measure">
  <div class="progress" role="progressbar" aria-label="Storage used" aria-valuenow="62" aria-valuemin="0" aria-valuemax="100">
    <div class="progress__bar" style="--progress-value:62%"></div>
  </div>
  <div class="progress" data-tone="success" role="progressbar" aria-label="Upload progress" aria-valuenow="100" aria-valuemin="0" aria-valuemax="100">
    <div class="progress__bar" style="--progress-value:100%"></div>
  </div>
</div>

Full reference: contracts, guidance, anatomy →

Skeleton

Loading placeholder with a lifecycle-driven sheen (TASTE-9) — drop it into a swap target while the request is in flight.

html
<div class="card card-body hm-measure hm-stack" aria-hidden="true" data-skeleton>
  <div class="hm-demo-row">
    <div class="skeleton" data-shape="circle"></div>
    <div class="hm-grow hm-stack">
      <div class="skeleton" data-shape="text"></div>
      <div class="skeleton" data-shape="text"></div>
    </div>
  </div>
  <div class="skeleton" data-shape="block"></div>
</div>

Full reference: contracts, guidance, anatomy →

Empty state

Icon + one sentence + primary action — never a bare 'No X'.

No invoices yet

Create your first invoice to get started.

html
<!-- icons: include the icon sheet once per page (see the Setup section, #setup) -->
<div class="card card-body hm-measure">
  <div class="empty-state" data-empty-state>
    <span class="empty-state__icon"><svg class="icon" aria-hidden="true"><use href="#i-inbox"/></svg></span>
    <h3 class="empty-state__title">No invoices yet</h3>
    <p class="empty-state__description">Create your first invoice to get started.</p>
    <div class="empty-state__action"><a class="button" data-variant="primary" href="#">New Invoice</a></div>
  </div>
</div>

Full reference: contracts, guidance, anatomy →

Code block docs

Fenced code surface with optional language chip and copy control — server-emitted chrome for docs and samples. Syntax colour is build-time token spans (Python), not a browser highlighter.

python
def greet(name: str) -> str:
    """Return a friendly hello."""
    return f"Hello, {name}"
html
<figure class="code" data-code data-language="python">
  <div class="code__meta"><span class="code__lang">python</span><button type="button" class="code__copy" data-code-copy aria-label="Copy code to clipboard"><span class="code__copy-idle">Copy</span><span class="code__copy-done">Copied</span></button></div>
  <pre class="code__pre" tabindex="0" role="region" aria-label="Python example"><code class="code__source">def greet(name: str) -> str:
    """Return a friendly hello."""
    return f"Hello, {name}"
</code></pre>
</figure>

Full reference: contracts, guidance, anatomy →

Toolbar composite

Inline composition — real button, toggle-group and menu markup nested in a role=toolbar bar. No client tree, no props: composition is HTML.

html
<!-- icons: include the icon sheet once per page (see the Setup section, #setup) -->
<div class="toolbar" role="toolbar" aria-label="Editor actions">
  <button class="button" data-variant="primary"><svg class="icon icon--size-sm" aria-hidden="true"><use href="#i-circle-plus"/></svg> New</button>
  <div class="toggle-group" role="radiogroup" aria-label="View">
    <label><input type="radio" name="tb-view" checked><span>List</span></label>
    <label><input type="radio" name="tb-view"><span>Grid</span></label>
  </div>
  <details class="menu">
    <summary class="button" data-variant="outline">More</summary>
    <div class="menu__panel"><button class="menu__item"><svg class="icon icon--size-sm" aria-hidden="true"><use href="#i-copy"/></svg> Duplicate</button><button class="menu__item" data-tone="destructive"><svg class="icon icon--size-sm" aria-hidden="true"><use href="#i-trash-2"/></svg> Delete</button></div>
  </details>
</div>

Full reference: contracts, guidance, anatomy →

Master–detail composite

Exchange composition — a list item hx-gets its detail card into the detail pane. The canonical htmx composite; two can coexist on a page.

INV-001 · Acme
£1,250.00
Paid · 2 days ago
html
<div class="master-detail" data-master-detail>
  <ul class="master-detail__list" aria-label="Invoices" data-master-detail-list-body>
    <li><a class="master-detail__item" href="#" aria-current="true" hx-get="/mock/master-detail/inv-001" hx-target="next .master-detail__detail">INV-001 · Acme</a></li>
    <li><a class="master-detail__item" href="#" hx-get="/mock/master-detail/inv-002" hx-target="next .master-detail__detail">INV-002 · Globex</a></li>
    <li><a class="master-detail__item" href="#" hx-get="/mock/master-detail/inv-003" hx-target="next .master-detail__detail">INV-003 · Initech</a></li>
  </ul>
  <div class="master-detail__detail" data-master-detail-detail-body>
    <div class="card card-body">
      <div class="card-label">INV-001 · Acme</div>
      <div class="card-value">£1,250.00</div>
      <div class="card-delta">Paid · 2 days ago</div>
    </div>
  </div>
</div>

Full reference: contracts, guidance, anatomy →

Stack layout

Vertical rhythm: children flow top-to-bottom with one gap token. The workhorse — most page sections are a stack of stacks.

One
Two
Three
html
<div class="stack" data-gap="md">
  <div class="hm-demo-box">One</div>
  <div class="hm-demo-box">Two</div>
  <div class="hm-demo-box">Three</div>
</div>

Full reference: contracts, guidance, anatomy →

Cluster layout

A wrapping horizontal group — buttons, chips, metadata rows. Items keep their size and wrap when the line runs out.

Draft
html
<div class="cluster" data-gap="sm"><button class="button" data-variant="primary">Save</button><button class="button" data-variant="outline">Cancel</button><span class="badge" data-tone="neutral">Draft</span></div>

Full reference: contracts, guidance, anatomy →

Auto grid layout

A responsive card grid with no breakpoints: columns pack to fit, each at least the minimum width, all equal.

A
B
C
D
E
html
<div class="auto-grid" style="--grid-min: 9rem">
  <div class="hm-demo-box">A</div>
  <div class="hm-demo-box">B</div>
  <div class="hm-demo-box">C</div>
  <div class="hm-demo-box">D</div>
  <div class="hm-demo-box">E</div>
</div>

Full reference: contracts, guidance, anatomy →

Center layout

A measure-capped, centred column — reading width for prose and forms.

A comfortable reading measure tops out around 65 characters; this block centres itself and caps its width so lines stay scannable on any screen.

html
<div class="center" data-measure="prose">
  <p class="hm-demo-muted">A comfortable reading measure tops out around 65 characters; this block centres itself and caps its width so lines stay scannable on any screen.</p>
</div>

Full reference: contracts, guidance, anatomy →

App shell composite

The SaaS/admin application frame: persistent left navigation, an optional sticky top bar, a routed main workspace, and a responsive/collapsible sidebar whose state the server renders.

html
<!-- icons: include the icon sheet once per page (see the Setup section, #setup) -->
<div class="app-shell" data-sidebar="open">
  <aside class="app-sidebar" id="app-sidebar">
    <div class="sidebar">
      <div class="sidebar-brand"><span class="sidebar-brand-text">Acme Ops</span></div>
      <nav class="sidebar-nav" aria-label="Primary">
        <ul class="sidebar-nav-list">
          <li><a class="sidebar-nav-link" aria-current="page" href="#"><span class="sidebar-nav-icon"><svg class="icon" aria-hidden="true"><use href="#i-layout-dashboard"/></svg></span><span class="sidebar-nav-label">Dashboard</span></a></li>
          <li><a class="sidebar-nav-link" href="#"><span class="sidebar-nav-icon"><svg class="icon" aria-hidden="true"><use href="#i-receipt"/></svg></span><span class="sidebar-nav-label">Invoices</span></a></li>
        </ul>
      </nav>
    </div>
  </aside>
  <div class="app-content">
    <header class="app-header">
      <div class="topbar">
        <div class="topbar-leading"><button type="button" class="sidebar-toggle" data-sidebar-toggle aria-expanded="true" aria-controls="app-sidebar" aria-label="Toggle navigation"><span class="sidebar-toggle__icon" aria-hidden="true"></span></button></div>
        <div class="topbar-title"><span class="topbar-title-text">Dashboard</span></div>
        <div class="topbar-trailing"><button type="button" class="icon-button" aria-label="Notifications"><svg class="icon" aria-hidden="true"><use href="#i-triangle-alert"/></svg></button></div>
      </div>
    </header>
    <div class="app-main">
      <p class="hm-demo-muted">The routed workspace. The hamburger collapses the sidebar; the state persists via a cookie so the server renders it correctly on the next request.</p>
    </div>
  </div>
</div>

Full reference: contracts, guidance, anatomy →

Status list data

System / check states as an icon + title + caption list — tone rides data-state per row, never colour alone.

  • Payments API
    Operational · 99.99% this month
    positive
  • Webhooks
    Elevated retries since 09:20
    warning
  • Nightly export
    Scheduled 02:00
html
<!-- icons: include the icon sheet once per page (see the Setup section, #setup) -->
<div class="status-list-region hm-measure-lg">
  <ul class="status-list" data-entry-count="3">
    <li class="status-list-entry" data-status-entry data-state="positive">
      <span class="status-list-icon" aria-hidden="true"><svg class="icon" aria-hidden="true"><use href="#i-circle-check"/></svg></span>
      <div class="status-list-text">
        <div class="status-list-title">Payments API</div>
        <div class="status-list-caption">Operational · 99.99% this month</div>
      </div>
      <span class="status-list-pill">positive</span>
    </li>
    <li class="status-list-entry" data-status-entry data-state="warning">
      <span class="status-list-icon" aria-hidden="true"><svg class="icon" aria-hidden="true"><use href="#i-triangle-alert"/></svg></span>
      <div class="status-list-text">
        <div class="status-list-title">Webhooks</div>
        <div class="status-list-caption">Elevated retries since 09:20</div>
      </div>
      <span class="status-list-pill">warning</span>
    </li>
    <li class="status-list-entry" data-status-entry data-state="neutral">
      <span class="status-list-icon-spacer" aria-hidden="true"></span>
      <div class="status-list-text">
        <div class="status-list-title">Nightly export</div>
        <div class="status-list-caption">Scheduled 02:00</div>
      </div>
    </li>
  </ul>
</div>

Full reference: contracts, guidance, anatomy →

Action grid data

Tone-tinted CTA cards with a count badge — the dashboard's 'what needs doing' surface. Cards with a URL are anchors; the grid packs intrinsically.

html
<!-- icons: include the icon sheet once per page (see the Setup section, #setup) -->
<div class="action-grid-region">
  <div class="action-grid">
    <a class="action-card" data-action-card data-tone="warning" href="#">
      <div class="action-card-row"><span class="action-card-icon"><svg class="icon" aria-hidden="true"><use href="#i-triangle-alert"/></svg></span><span class="action-card-count" data-tone-badge="warning">3</span></div>
      <span class="action-card-label">Overdue invoices</span>
    </a>
    <a class="action-card" data-action-card data-tone="accent" href="#">
      <div class="action-card-row"><span class="action-card-icon"><svg class="icon" aria-hidden="true"><use href="#i-receipt"/></svg></span><span class="action-card-count" data-tone-badge="accent">12</span></div>
      <span class="action-card-label">Awaiting approval</span>
    </a>
    <div class="action-card" data-action-card data-tone="neutral">
      <div class="action-card-row"><span class="action-card-icon-spacer"></span></div>
      <span class="action-card-label">Nothing else today</span>
    </div>
  </div>
</div>

Full reference: contracts, guidance, anatomy →

Queue data

The worklist: a count, roll-up metrics, and attention-flagged rows — the triage surface for SLA-driven work.

7open items
2
breaching today
4h
median age
Refund request — Acme

SLA breaches at 16:00 — assign now.

2h left
KYC review — Globex
due tomorrow
html
<div class="queue-region hm-measure-lg">
  <div class="queue-count-row"><span class="queue-count">7</span><span>open items</span></div>
  <div class="queue-metrics">
    <div class="queue-metric">
      <div class="queue-metric-value">2</div>
      <div class="queue-metric-label">breaching today</div>
    </div>
    <div class="queue-metric">
      <div class="queue-metric-value">4h</div>
      <div class="queue-metric-label">median age</div>
    </div>
  </div>
  <div class="queue-rows">
    <div class="queue-row attn-both attn-tone-critical" data-queue-row data-attn="critical">
      <div class="queue-row-main ">
        <div class="queue-row-headline"><span class="queue-row-title">Refund request — Acme</span></div>
        <p class="queue-row-attn">SLA breaches at 16:00 — assign now.</p>
        <span class="queue-row-date">2h left</span>
      </div>
    </div>
    <div class="queue-row " data-queue-row>
      <div class="queue-row-main ">
        <div class="queue-row-headline"><span class="queue-row-title">KYC review — Globex</span></div>
        <span class="queue-row-date">due tomorrow</span>
      </div>
    </div>
  </div>
</div>

Full reference: contracts, guidance, anatomy →

Kanban data

Status columns of cards — the flow view. Columns show a count; overflowing boards offer a server-rendered Load-all.

Open2

Refund request — Acme

£1,250 · assigned to Ada

SLA breaches at 16:00

KYC review — Globex

due tomorrow

In progress1

Chargeback — Initech

evidence uploaded

Done0

Nothing here yet.

html
<!-- icons: include the icon sheet once per page (see the Setup section, #setup) -->
<div class="kanban-board" role="region" aria-label="Kanban board" tabindex="0" data-kanban-board data-kanban-rearrange="status" data-kanban-status-field="status" data-kanban-api="/mock/kanban" data-kanban-src="/mock/kanban/board" data-kanban-rank-field="rank">
  <div class="kanban-announce" data-kanban-announce aria-live="polite" aria-atomic="true"></div>
  <div class="kanban-column">
    <div class="kanban-column-head"><span class="badge" data-tone="neutral">Open</span><span class="kanban-column-count">2</span></div>
    <div class="kanban-stack" data-kanban-stack data-to-state="open">
      <div class="kanban-card" data-kanban-card id="kanban-card-k1" data-entity-id="k1" data-from-state="open" data-rank="1000" data-allowed-to="in_progress done" draggable="true">
        <div class="kanban-card-body">
          <h4 class="kanban-card-title">Refund request — Acme</h4>
          <p class="kanban-card-field">£1,250 · assigned to Ada</p>
          <p class="kanban-card-attn" data-attn="critical">SLA breaches at 16:00</p>
          <div class="kanban-card-move">
            <label>
              <span class="visually-hidden">Move card</span>
              <select data-kanban-move aria-label="Move Refund request — Acme">
                <option value="">Move to…</option>
                <option value="in_progress">In Progress</option>
                <option value="done">Done</option>
              </select>
            </label>
          </div>
        </div>
      </div>
      <div class="kanban-card" data-kanban-card id="kanban-card-k2" data-entity-id="k2" data-from-state="open" data-rank="2000" data-allowed-to="in_progress" draggable="true">
        <div class="kanban-card-body">
          <h4 class="kanban-card-title">KYC review — Globex</h4>
          <p class="kanban-card-field">due tomorrow</p>
          <div class="kanban-card-move">
            <label>
              <span class="visually-hidden">Move card</span>
              <select data-kanban-move aria-label="Move KYC review — Globex">
                <option value="">Move to…</option>
                <option value="in_progress">In Progress</option>
              </select>
            </label>
          </div>
        </div>
      </div>
    </div>
  </div>
  <div class="kanban-column">
    <div class="kanban-column-head"><span class="badge" data-tone="info">In progress</span><span class="kanban-column-count">1</span></div>
    <div class="kanban-stack" data-kanban-stack data-to-state="in_progress">
      <div class="kanban-card" data-kanban-card id="kanban-card-k3" data-entity-id="k3" data-from-state="in_progress" data-rank="1000" data-allowed-to="done open" draggable="true">
        <div class="kanban-card-body">
          <h4 class="kanban-card-title">Chargeback — Initech</h4>
          <p class="kanban-card-field">evidence uploaded</p>
          <div class="kanban-card-move">
            <label>
              <span class="visually-hidden">Move card</span>
              <select data-kanban-move aria-label="Move Chargeback — Initech">
                <option value="">Move to…</option>
                <option value="done">Done</option>
                <option value="open">Open</option>
              </select>
            </label>
          </div>
        </div>
      </div>
    </div>
  </div>
  <div class="kanban-column">
    <div class="kanban-column-head"><span class="badge" data-tone="success"><span class="badge-icon"><svg class="icon" aria-hidden="true"><use href="#i-circle-check"/></svg></span>Done</span><span class="kanban-column-count">0</span></div>
    <div class="kanban-stack" data-kanban-stack data-to-state="done">
      <p class="kanban-empty">Nothing here yet.</p>
    </div>
  </div>
</div>

Full reference: contracts, guidance, anatomy →

Timeline data

Dated events on a vertical line — bullets carry the attention contract, dates keep a fixed column so titles align.

  • Today

    Payment failed — retry scheduled

    Card declined (insufficient funds)

  • Mon

    Invoice sent

html
<div class="timeline-region hm-measure-lg">
  <ul class="timeline-list">
    <li class="timeline-item" data-timeline-item>
      <span class="timeline-bullet-wrap"><svg class="timeline-bullet attn-bullet attn-tone-critical" fill="currentColor" viewBox="0 0 20 20" aria-hidden="true"><circle cx="10" cy="10" r="6"/></svg></span>
      <div class="timeline-row">
        <div class="timeline-date">Today</div>
        <div class="timeline-content">
          <p class="timeline-title">Payment failed — retry scheduled</p>
          <p class="timeline-field">Card declined (insufficient funds)</p>
        </div>
      </div>
    </li>
    <li class="timeline-item" data-timeline-item>
      <span class="timeline-bullet-wrap"><svg class="timeline-bullet attn-bullet attn-tone-default" fill="currentColor" viewBox="0 0 20 20" aria-hidden="true"><circle cx="10" cy="10" r="6"/></svg></span>
      <div class="timeline-row">
        <div class="timeline-date">Mon</div>
        <div class="timeline-content">
          <p class="timeline-title">Invoice sent</p>
        </div>
      </div>
    </li>
  </ul>
</div>

Full reference: contracts, guidance, anatomy →

Activity feed data

Who-did-what rows on a dotted spine — actor, time, and a message bubble.

  • 09:41
    Ada approved the refund.
  • 09:12
    System flagged the account for review.
html
<div class="hm-measure-lg">
  <ul class="activity-feed">
    <li class="activity-row" data-activity-row>
      <span class="activity-dot"><svg fill="currentColor" viewBox="0 0 20 20" aria-hidden="true"><circle cx="10" cy="10" r="6"/></svg></span>
      <div class="activity-row-inner">
        <div class="activity-time">09:41</div>
        <div class="activity-bubble"><span class="activity-actor">Ada</span> approved the refund.</div>
      </div>
    </li>
    <li class="activity-row" data-activity-row>
      <span class="activity-dot"><svg fill="currentColor" viewBox="0 0 20 20" aria-hidden="true"><circle cx="10" cy="10" r="6"/></svg></span>
      <div class="activity-row-inner">
        <div class="activity-time">09:12</div>
        <div class="activity-bubble"><span class="activity-actor">System</span> flagged the account for review.</div>
      </div>
    </li>
  </ul>
</div>

Full reference: contracts, guidance, anatomy →

Metric tiles data

The KPI strip: label + value tiles in a packing grid, optionally toned. The server stamps the tile count for e2e anchors.

Outstanding
£12,450
Paid this month
£48,900
Overdue
3
html
<div class="metrics-grid" data-tile-count="3">
  <div class="metric-tile" data-metric-key="outstanding">
    <div class="metric-label">Outstanding</div>
    <div class="metric-value">£12,450</div>
  </div>
  <div class="metric-tile" data-metric-key="paid" data-tone="positive">
    <div class="metric-label">Paid this month</div>
    <div class="metric-value">£48,900</div>
  </div>
  <div class="metric-tile" data-metric-key="overdue" data-tone="warning">
    <div class="metric-label">Overdue</div>
    <div class="metric-value">3</div>
  </div>
</div>

Full reference: contracts, guidance, anatomy →

Sparkline data

A headline number with its recent shape — the smallest chart: a current value, its bucket label, and an area glyph.

184msthis hour
html
<div class="sparkline-region" data-sparkline>
  <div class="sparkline-headline"><span class="sparkline-value">184ms</span><span class="sparkline-bucket-label">this hour</span></div>
  <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 180 32" preserveAspectRatio="none" class="sparkline-svg" role="img" aria-label="Sparkline — 12 points, latest 184ms, peak 240ms"><polygon points="0,32 0,20 18,18 36,22 54,14 72,16 90,10 108,12 126,8 144,14 162,6 180,9 180,32" fill="var(--colour-brand)" fill-opacity="0.15" stroke="none"/><polyline points="0,20 18,18 36,22 54,14 72,16 90,10 108,12 126,8 144,14 162,6 180,9" fill="none" stroke="var(--colour-brand)" stroke-width="1.25" vector-effect="non-scaling-stroke" stroke-linejoin="round" stroke-linecap="round"/></svg>
</div>

Full reference: contracts, guidance, anatomy →

Funnel data

Stage-by-stage narrowing — each bar's width is the stage's share, with a total summary line.

Visited (1,204)
Signed up (746)
Subscribed (338)

1,204 total

html
<div class="funnel-chart-region hm-measure-lg" data-funnel>
  <div class="funnel-stages">
    <div class="funnel-stage-row">
      <div class="funnel-stage" data-funnel-step="0" style="width: 100%;"><span class="funnel-stage-label">Visited</span><span class="funnel-stage-count"> (1,204)</span></div>
    </div>
    <div class="funnel-stage-row">
      <div class="funnel-stage" data-funnel-step="1" style="width: 62%;"><span class="funnel-stage-label">Signed up</span><span class="funnel-stage-count"> (746)</span></div>
    </div>
    <div class="funnel-stage-row">
      <div class="funnel-stage" data-funnel-step="2" style="width: 28%;"><span class="funnel-stage-label">Subscribed</span><span class="funnel-stage-count"> (338)</span></div>
    </div>
  </div>
  <p class="funnel-summary">1,204 total</p>
</div>

Full reference: contracts, guidance, anatomy →

Bar chart data

Label / track / value rows — the workhorse categorical chart, server-computed and scope-safe.

API
126
Dashboard
84
Billing
35
html
<div class="bar-chart-region hm-measure-lg" data-bar-chart>
  <div class="bar-chart-bars">
    <div class="bar-chart-row">
      <span class="bar-chart-label">API</span>
      <div class="bar-chart-track">
        <div class="bar-chart-fill" style="width: 84%"></div>
      </div>
      <span class="bar-chart-value">126</span>
    </div>
    <div class="bar-chart-row">
      <span class="bar-chart-label">Dashboard</span>
      <div class="bar-chart-track">
        <div class="bar-chart-fill" style="width: 56%"></div>
      </div>
      <span class="bar-chart-value">84</span>
    </div>
    <div class="bar-chart-row">
      <span class="bar-chart-label">Billing</span>
      <div class="bar-chart-track">
        <div class="bar-chart-fill" style="width: 23%"></div>
      </div>
      <span class="bar-chart-value">35</span>
    </div>
  </div>
</div>

Full reference: contracts, guidance, anatomy →

Chart legend data

The shared tail of every multi-series chart: swatch + series-name chips and a sample/series summary line.

  • Revenue
  • Costs

12 buckets · 2 series · peak £48,900

html
<div class="hm-measure-lg">
  <ul class="chart-legend">
    <li class="chart-legend-item"><span class="chart-legend-swatch" style="background:var(--colour-brand)"></span><span class="chart-legend-name">Revenue</span></li>
    <li class="chart-legend-item"><span class="chart-legend-swatch" style="background:var(--colour-success)"></span><span class="chart-legend-name">Costs</span></li>
  </ul>
  <p class="chart-summary">12 buckets · 2 series · peak £48,900</p>
</div>

Full reference: contracts, guidance, anatomy →

Radar data

Polar multi-axis profile — spokes share a scale; the polygon is server-rendered SVG.

Auth value: 80API value: 65UI value: 90Data value: 70Ops value: 55AuthAPIUIDataOps

5 spokes · 1 series · peak 90

html
<div class="radar-region hm-measure-lg" data-radar>
  <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 320" class="radar-svg chart-svg" role="img" aria-label="Coverage radar — 5 spokes, peak 90">
    <polygon points="160.0,128.0 190.43380852144492,150.11145618000168 178.80912807335915,185.88854381999832 141.19087192664085,185.88854381999832 129.56619147855508,150.11145618000168" fill="none" stroke="var(--colour-border)" stroke-width="0.5" stroke-opacity="0.6"/>
    <polygon points="160.0,96.0 220.86761704288983,140.22291236000336 197.6182561467183,211.77708763999664 122.38174385328173,211.77708763999664 99.13238295711017,140.22291236000336" fill="none" stroke="var(--colour-border)" stroke-width="0.5" stroke-opacity="0.6"/>
    <polygon points="160.0,64.0 251.30142556433475,130.33436854000504 216.4273842200774,237.66563145999496 103.57261577992259,237.66563145999496 68.69857443566525,130.33436854000507" fill="none" stroke="var(--colour-border)" stroke-width="0.5" stroke-opacity="0.6"/>
    <polygon points="160.0,32.0 281.73523408577967,120.44582472000673 235.23651229343656,263.5541752799933 84.76348770656345,263.5541752799933 38.264765914220334,120.44582472000675" fill="none" stroke="var(--colour-border)" stroke-width="0.5" stroke-opacity="0.6"/>
    <line x1="160.0" y1="160.0" x2="160.0" y2="32.0" stroke="var(--colour-border)" stroke-width="0.5" stroke-opacity="0.7"/>
    <line x1="160.0" y1="160.0" x2="281.73523408577967" y2="120.44582472000673" stroke="var(--colour-border)" stroke-width="0.5" stroke-opacity="0.7"/>
    <line x1="160.0" y1="160.0" x2="235.23651229343656" y2="263.5541752799933" stroke="var(--colour-border)" stroke-width="0.5" stroke-opacity="0.7"/>
    <line x1="160.0" y1="160.0" x2="84.76348770656345" y2="263.5541752799933" stroke="var(--colour-border)" stroke-width="0.5" stroke-opacity="0.7"/>
    <line x1="160.0" y1="160.0" x2="38.264765914220334" y2="120.44582472000675" stroke="var(--colour-border)" stroke-width="0.5" stroke-opacity="0.7"/>
    <polygon points="160.0,46.22222222222223 247.91989128417418,131.43309563111598 235.23651229343656,263.5541752799933 101.48271266066047,240.54213632888366 85.60624583646798,135.82800399555967" fill="var(--colour-brand)" fill-opacity="0.15" stroke="var(--colour-brand)" stroke-width="1.5" stroke-linejoin="round"/>
    <circle cx="160.0" cy="46.22222222222223" r="3" fill="var(--colour-brand)" stroke="var(--colour-surface)" stroke-width="1">
      <title>Auth value: 80</title>
    </circle>
    <circle cx="247.91989128417418" cy="131.43309563111598" r="3" fill="var(--colour-brand)" stroke="var(--colour-surface)" stroke-width="1">
      <title>API value: 65</title>
    </circle>
    <circle cx="235.23651229343656" cy="263.5541752799933" r="3" fill="var(--colour-brand)" stroke="var(--colour-surface)" stroke-width="1">
      <title>UI value: 90</title>
    </circle>
    <circle cx="101.48271266066047" cy="240.54213632888366" r="3" fill="var(--colour-brand)" stroke="var(--colour-surface)" stroke-width="1">
      <title>Data value: 70</title>
    </circle>
    <circle cx="85.60624583646798" cy="135.82800399555967" r="3" fill="var(--colour-brand)" stroke="var(--colour-surface)" stroke-width="1">
      <title>Ops value: 55</title>
    </circle>
    <text x="160.0" y="18.0" text-anchor="middle" dominant-baseline="middle" font-size="10" fill="var(--colour-text)" font-family="ui-monospace, 'SF Mono', Menlo, monospace">Auth</text>
    <text x="295.05002531391176" y="116.11958679875747" text-anchor="middle" dominant-baseline="middle" font-size="10" fill="var(--colour-text)" font-family="ui-monospace, 'SF Mono', Menlo, monospace">API</text>
    <text x="243.46550582553118" y="274.8804132012425" text-anchor="middle" dominant-baseline="middle" font-size="10" fill="var(--colour-text)" font-family="ui-monospace, 'SF Mono', Menlo, monospace">UI</text>
    <text x="76.53449417446883" y="274.8804132012425" text-anchor="middle" dominant-baseline="middle" font-size="10" fill="var(--colour-text)" font-family="ui-monospace, 'SF Mono', Menlo, monospace">Data</text>
    <text x="24.949974686088183" y="116.1195867987575" text-anchor="middle" dominant-baseline="middle" font-size="10" fill="var(--colour-text)" font-family="ui-monospace, 'SF Mono', Menlo, monospace">Ops</text>
  </svg>
  <p class="chart-summary">5 spokes · 1 series · peak 90</p>
</div>

Full reference: contracts, guidance, anatomy →

Time series data

Line or area sequential chart — one series of (label, value) points, or multi-series overlays with a shared legend.

3 Mar: 424 Mar: 515 Mar: 486 Mar: 637 Mar: 718 Mar: 389 Mar: 3510 Mar: 5811 Mar: 6612 Mar: 7213 Mar: 8014 Mar: 9115 Mar: 4716 Mar: 443 Mar6 Mar9 Mar12 Mar15 Mar16 Mar

14 buckets · peak 91

html
<div class="line-chart-region hm-measure-lg" data-time-series>
  <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 400 120" class="line-chart-svg chart-svg" role="img" aria-label="Traffic time series — 14 buckets, peak 91">
    <line x1="8" y1="92" x2="392" y2="92" stroke="var(--colour-border)" stroke-width="1"/>
    <polygon points="8,92 8.0,53.23 37.54,44.92 67.08,47.69 96.62,33.85 126.15,26.46 155.69,56.92 185.23,59.69 214.77,38.46 244.31,31.08 273.85,25.54 303.38,18.15 332.92,8.0 362.46,48.62 392.0,51.38 392,92" fill="var(--colour-brand)" fill-opacity="0.12" stroke="none"/>
    <polyline points="8.0,53.23 37.54,44.92 67.08,47.69 96.62,33.85 126.15,26.46 155.69,56.92 185.23,59.69 214.77,38.46 244.31,31.08 273.85,25.54 303.38,18.15 332.92,8.0 362.46,48.62 392.0,51.38" fill="none" stroke="var(--colour-brand)" stroke-width="1.5" stroke-linejoin="round" stroke-linecap="round"/>
    <circle cx="8.0" cy="53.23" r="2.5" fill="var(--colour-brand)" stroke="var(--colour-surface)" stroke-width="1">
      <title>3 Mar: 42</title>
    </circle>
    <circle cx="37.54" cy="44.92" r="2.5" fill="var(--colour-brand)" stroke="var(--colour-surface)" stroke-width="1">
      <title>4 Mar: 51</title>
    </circle>
    <circle cx="67.08" cy="47.69" r="2.5" fill="var(--colour-brand)" stroke="var(--colour-surface)" stroke-width="1">
      <title>5 Mar: 48</title>
    </circle>
    <circle cx="96.62" cy="33.85" r="2.5" fill="var(--colour-brand)" stroke="var(--colour-surface)" stroke-width="1">
      <title>6 Mar: 63</title>
    </circle>
    <circle cx="126.15" cy="26.46" r="2.5" fill="var(--colour-brand)" stroke="var(--colour-surface)" stroke-width="1">
      <title>7 Mar: 71</title>
    </circle>
    <circle cx="155.69" cy="56.92" r="2.5" fill="var(--colour-brand)" stroke="var(--colour-surface)" stroke-width="1">
      <title>8 Mar: 38</title>
    </circle>
    <circle cx="185.23" cy="59.69" r="2.5" fill="var(--colour-brand)" stroke="var(--colour-surface)" stroke-width="1">
      <title>9 Mar: 35</title>
    </circle>
    <circle cx="214.77" cy="38.46" r="2.5" fill="var(--colour-brand)" stroke="var(--colour-surface)" stroke-width="1">
      <title>10 Mar: 58</title>
    </circle>
    <circle cx="244.31" cy="31.08" r="2.5" fill="var(--colour-brand)" stroke="var(--colour-surface)" stroke-width="1">
      <title>11 Mar: 66</title>
    </circle>
    <circle cx="273.85" cy="25.54" r="2.5" fill="var(--colour-brand)" stroke="var(--colour-surface)" stroke-width="1">
      <title>12 Mar: 72</title>
    </circle>
    <circle cx="303.38" cy="18.15" r="2.5" fill="var(--colour-brand)" stroke="var(--colour-surface)" stroke-width="1">
      <title>13 Mar: 80</title>
    </circle>
    <circle cx="332.92" cy="8.0" r="2.5" fill="var(--colour-brand)" stroke="var(--colour-surface)" stroke-width="1">
      <title>14 Mar: 91</title>
    </circle>
    <circle cx="362.46" cy="48.62" r="2.5" fill="var(--colour-brand)" stroke="var(--colour-surface)" stroke-width="1">
      <title>15 Mar: 47</title>
    </circle>
    <circle cx="392.0" cy="51.38" r="2.5" fill="var(--colour-brand)" stroke="var(--colour-surface)" stroke-width="1">
      <title>16 Mar: 44</title>
    </circle>
    <text x="8.0" y="112" text-anchor="middle" font-size="9" fill="var(--colour-text-muted)" font-family="ui-monospace, 'SF Mono', Menlo, monospace">3 Mar</text>
    <text x="96.62" y="112" text-anchor="middle" font-size="9" fill="var(--colour-text-muted)" font-family="ui-monospace, 'SF Mono', Menlo, monospace">6 Mar</text>
    <text x="185.23" y="112" text-anchor="middle" font-size="9" fill="var(--colour-text-muted)" font-family="ui-monospace, 'SF Mono', Menlo, monospace">9 Mar</text>
    <text x="273.85" y="112" text-anchor="middle" font-size="9" fill="var(--colour-text-muted)" font-family="ui-monospace, 'SF Mono', Menlo, monospace">12 Mar</text>
    <text x="362.46" y="112" text-anchor="middle" font-size="9" fill="var(--colour-text-muted)" font-family="ui-monospace, 'SF Mono', Menlo, monospace">15 Mar</text>
    <text x="392.0" y="112" text-anchor="middle" font-size="9" fill="var(--colour-text-muted)" font-family="ui-monospace, 'SF Mono', Menlo, monospace">16 Mar</text>
  </svg>
  <p class="chart-summary">14 buckets · peak 91</p>
</div>

Full reference: contracts, guidance, anatomy →

Heatmap data

A two-dimensional grid of toned cells — rows × buckets, thresholds driving good/warn/bad tones, never colour alone (the value is IN the cell).

MonTueWed
API 99.9 99.7 97.2
Webhooks 96.1 89.4 99.2
html
<div class="heatmap-region hm-measure-lg" data-heatmap>
  <div class="heatmap-scroll">
    <table class="heatmap-grid">
      <thead>
        <tr>
          <th></th>
          <th>Mon</th>
          <th>Tue</th>
          <th>Wed</th>
        </tr>
      </thead>
      <tbody>
        <tr>
          <td class="heatmap-row-label">API</td>
          <td class="heatmap-cell" data-heatmap-tone="good"> 99.9 </td>
          <td class="heatmap-cell" data-heatmap-tone="good"> 99.7 </td>
          <td class="heatmap-cell" data-heatmap-tone="warn"> 97.2 </td>
        </tr>
        <tr>
          <td class="heatmap-row-label">Webhooks</td>
          <td class="heatmap-cell" data-heatmap-tone="warn"> 96.1 </td>
          <td class="heatmap-cell" data-heatmap-tone="bad"> 89.4 </td>
          <td class="heatmap-cell" data-heatmap-tone="good"> 99.2 </td>
        </tr>
      </tbody>
    </table>
  </div>
</div>

Full reference: contracts, guidance, anatomy →

Bullet chart data

Actual vs target on qualitative bands — the KPI-with-context bar. All geometry is server-computed inline percentages.

Revenue
72 / 80

1 rows · scale 0–100

html
<div class="bullet-region hm-measure-lg" data-bullet>
  <div class="bullet-rows">
    <div class="bullet-row">
      <span class="bullet-label">Revenue</span>
      <div class="bullet-track"><span class="bullet-band" style="left: 0%; width: 60%; background: var(--colour-danger);" title="Poor: 0–60"></span><span class="bullet-band" style="left: 60%; width: 25%; background: hsl(40, 90%, 55%);" title="OK: 60–85"></span><span class="bullet-band" style="left: 85%; width: 15%; background: hsl(145, 55%, 45%);" title="Good: 85–100"></span><span class="bullet-actual" style="width: 72%;" title="Revenue actual: 72"></span><span class="bullet-target" style="left: 80%;" title="Revenue target: 80"></span></div>
      <span class="bullet-value">72 / 80</span>
    </div>
  </div>
  <p class="bullet-summary">1 rows · scale 0–100</p>
</div>

Full reference: contracts, guidance, anatomy →

Pivot table data

Two group-bys crossed into a matrix — row labels × column buckets, empty intersections rendered as explicit nulls.

SystemSeverityCount
APICritical3
Dashboard—9

2 rows

html
<!-- icons: include the icon sheet once per page (see the Setup section, #setup) -->
<div class="pivot-region hm-measure-lg" data-pivot>
  <div class="pivot-scroll">
    <table class="pivot-grid">
      <thead>
        <tr>
          <th>System</th>
          <th>Severity</th>
          <th class="is-measure">Count</th>
        </tr>
      </thead>
      <tbody>
        <tr>
          <td>API</td>
          <td><span class="badge badge-sm" data-tone="destructive" role="status" aria-label="Status: Critical"><span class="badge-icon"><svg class="icon" aria-hidden="true"><use href="#i-circle-x"/></svg></span>Critical</span></td>
          <td class="is-measure">3</td>
        </tr>
        <tr>
          <td>Dashboard</td>
          <td><span class="pivot-null">—</span></td>
          <td class="is-measure">9</td>
        </tr>
      </tbody>
    </table>
  </div>
  <p class="pivot-summary">2 rows</p>
</div>

Full reference: contracts, guidance, anatomy →

Bar track data

Value-against-capacity rows with real progressbar semantics — the resource-usage sibling of the bar chart.

Storage
62%
Compute
38%

2 rows0–100

html
<div class="bar-track-region hm-measure-lg" data-bar-track>
  <div class="bar-track-rows">
    <div class="bar-track-row">
      <span class="bar-track-label" title="Storage">Storage</span>
      <div class="bar-track" role="progressbar" aria-valuemin="0" aria-valuemax="100" aria-valuenow="62" aria-label="Storage: 62%"><span class="bar-track-fill" style="width: 62%;" title="Storage: 62%"></span></div>
      <span class="bar-track-value">62%</span>
    </div>
    <div class="bar-track-row">
      <span class="bar-track-label" title="Compute">Compute</span>
      <div class="bar-track" role="progressbar" aria-valuemin="0" aria-valuemax="100" aria-valuenow="38" aria-label="Compute: 38%"><span class="bar-track-fill" style="width: 38%;" title="Compute: 38%"></span></div>
      <span class="bar-track-value">38%</span>
    </div>
  </div>
  <p class="bar-track-summary"><span class="bar-track-summary-gutter" aria-hidden="true"></span><span class="bar-track-summary-meta">2 rows</span><span class="bar-track-summary-scale">0–100</span></p>
</div>

Full reference: contracts, guidance, anatomy →

Histogram data

Value-distribution buckets as a server-rendered SVG plus a mono summary line.

6 buckets · 120 samples

html
<div class="histogram-region hm-measure-lg" data-histogram>
  <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 180 48" role="img" aria-label="Histogram — 6 buckets, 120 samples"><rect x="4" y="30" width="24" height="18" fill="var(--colour-brand)" fill-opacity="0.7"/><rect x="32" y="18" width="24" height="30" fill="var(--colour-brand)" fill-opacity="0.7"/><rect x="60" y="6" width="24" height="42" fill="var(--colour-brand)" fill-opacity="0.7"/><rect x="88" y="14" width="24" height="34" fill="var(--colour-brand)" fill-opacity="0.7"/><rect x="116" y="28" width="24" height="20" fill="var(--colour-brand)" fill-opacity="0.7"/><rect x="144" y="38" width="24" height="10" fill="var(--colour-brand)" fill-opacity="0.7"/></svg>
  <p class="histogram-summary">6 buckets · 120 samples</p>
</div>

Full reference: contracts, guidance, anatomy →

Box plot data

Distribution five-number summaries per bucket — a server-rendered SVG with the counts in the summary line.

5.095.0API: Q1 20.0, median 30.0, Q3 45.0, n=12080API max: 8045API q3: 4530API median: 3020API q1: 2010API min: 10APIWeb: Q1 15.0, median 25.0, Q3 40.0, n=9870Web max: 7040Web q3: 4025Web median: 2515Web q1: 155Web min: 5WebJobs: Q1 22.0, median 35.0, Q3 50.0, n=6495Jobs max: 9550Jobs q3: 5035Jobs median: 3522Jobs q1: 2212Jobs min: 12Jobs

3 groups · 282 samples

html
<div class="box-plot-region hm-measure-lg" data-box-plot>
  <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 232 200" class="box-plot-svg" role="img" aria-label="Latency box plot — 3 groups, range 5.0–95.0">
    <line x1="32" y1="168" x2="224" y2="168" stroke="var(--colour-border)" stroke-width="1"/>
    <line x1="32" y1="8" x2="32" y2="168" stroke="var(--colour-border)" stroke-width="1"/>
    <text x="28" y="172" text-anchor="end" font-size="9" fill="var(--colour-text-muted)" font-family="ui-monospace, 'SF Mono', Menlo, monospace">5.0</text>
    <text x="28" y="12" text-anchor="end" font-size="9" fill="var(--colour-text-muted)" font-family="ui-monospace, 'SF Mono', Menlo, monospace">95.0</text>
    <line class="box-plot-whisker" x1="64.0" y1="159.11" x2="64.0" y2="141.33" stroke="var(--colour-text-muted)" stroke-width="1"/>
    <line class="box-plot-whisker" x1="64.0" y1="96.89" x2="64.0" y2="34.67" stroke="var(--colour-text-muted)" stroke-width="1"/>
    <line class="box-plot-whisker-cap" x1="55.0" y1="159.11" x2="73.0" y2="159.11" stroke="var(--colour-text-muted)" stroke-width="1"/>
    <line class="box-plot-whisker-cap" x1="55.0" y1="34.67" x2="73.0" y2="34.67" stroke="var(--colour-text-muted)" stroke-width="1"/>
    <rect class="box-plot-box" x="46.0" y="96.89" width="36" height="44.44" fill="var(--colour-brand)" fill-opacity="0.18" stroke="var(--colour-brand)" stroke-width="1">
      <title>API: Q1 20.0, median 30.0, Q3 45.0, n=120</title>
    </rect>
    <line class="box-plot-median" x1="46.0" y1="123.56" x2="82.0" y2="123.56" stroke="var(--colour-brand)" stroke-width="1"/>
    <g class="box-plot-mark" data-box-mark="max">
      <circle class="box-plot-mark-hit" cx="64.0" cy="34.67" r="7" fill="transparent" stroke="none"/>
      <circle class="box-plot-mark-dot" cx="64.0" cy="34.67" r="2" fill="var(--colour-brand)" stroke="var(--colour-surface)" stroke-width="1"/>
      <text class="box-plot-mark-label" x="88.0" y="37.67" text-anchor="start" font-size="9" fill="var(--colour-text)" font-family="ui-monospace, 'SF Mono', Menlo, monospace">80</text>
      <title>API max: 80</title>
    </g>
    <g class="box-plot-mark" data-box-mark="q3">
      <circle class="box-plot-mark-hit" cx="64.0" cy="96.89" r="7" fill="transparent" stroke="none"/>
      <circle class="box-plot-mark-dot" cx="64.0" cy="96.89" r="2" fill="var(--colour-brand)" stroke="var(--colour-surface)" stroke-width="1"/>
      <text class="box-plot-mark-label" x="88.0" y="99.89" text-anchor="start" font-size="9" fill="var(--colour-text)" font-family="ui-monospace, 'SF Mono', Menlo, monospace">45</text>
      <title>API q3: 45</title>
    </g>
    <g class="box-plot-mark" data-box-mark="median">
      <circle class="box-plot-mark-hit" cx="64.0" cy="123.56" r="7" fill="transparent" stroke="none"/>
      <circle class="box-plot-mark-dot" cx="64.0" cy="123.56" r="2" fill="var(--colour-brand)" stroke="var(--colour-surface)" stroke-width="1"/>
      <text class="box-plot-mark-label" x="88.0" y="126.56" text-anchor="start" font-size="9" fill="var(--colour-text)" font-family="ui-monospace, 'SF Mono', Menlo, monospace">30</text>
      <title>API median: 30</title>
    </g>
    <g class="box-plot-mark" data-box-mark="q1">
      <circle class="box-plot-mark-hit" cx="64.0" cy="141.33" r="7" fill="transparent" stroke="none"/>
      <circle class="box-plot-mark-dot" cx="64.0" cy="141.33" r="2" fill="var(--colour-brand)" stroke="var(--colour-surface)" stroke-width="1"/>
      <text class="box-plot-mark-label" x="88.0" y="144.33" text-anchor="start" font-size="9" fill="var(--colour-text)" font-family="ui-monospace, 'SF Mono', Menlo, monospace">20</text>
      <title>API q1: 20</title>
    </g>
    <g class="box-plot-mark" data-box-mark="min">
      <circle class="box-plot-mark-hit" cx="64.0" cy="159.11" r="7" fill="transparent" stroke="none"/>
      <circle class="box-plot-mark-dot" cx="64.0" cy="159.11" r="2" fill="var(--colour-brand)" stroke="var(--colour-surface)" stroke-width="1"/>
      <text class="box-plot-mark-label" x="88.0" y="162.11" text-anchor="start" font-size="9" fill="var(--colour-text)" font-family="ui-monospace, 'SF Mono', Menlo, monospace">10</text>
      <title>API min: 10</title>
    </g>
    <text x="64.0" y="192" text-anchor="middle" font-size="10" fill="var(--colour-text)" font-family="ui-monospace, 'SF Mono', Menlo, monospace">API</text>
    <line class="box-plot-whisker" x1="128.0" y1="168.0" x2="128.0" y2="150.22" stroke="var(--colour-text-muted)" stroke-width="1"/>
    <line class="box-plot-whisker" x1="128.0" y1="105.78" x2="128.0" y2="52.44" stroke="var(--colour-text-muted)" stroke-width="1"/>
    <line class="box-plot-whisker-cap" x1="119.0" y1="168.0" x2="137.0" y2="168.0" stroke="var(--colour-text-muted)" stroke-width="1"/>
    <line class="box-plot-whisker-cap" x1="119.0" y1="52.44" x2="137.0" y2="52.44" stroke="var(--colour-text-muted)" stroke-width="1"/>
    <rect class="box-plot-box" x="110.0" y="105.78" width="36" height="44.44" fill="var(--colour-brand)" fill-opacity="0.18" stroke="var(--colour-brand)" stroke-width="1">
      <title>Web: Q1 15.0, median 25.0, Q3 40.0, n=98</title>
    </rect>
    <line class="box-plot-median" x1="110.0" y1="132.44" x2="146.0" y2="132.44" stroke="var(--colour-brand)" stroke-width="1"/>
    <g class="box-plot-mark" data-box-mark="max">
      <circle class="box-plot-mark-hit" cx="128.0" cy="52.44" r="7" fill="transparent" stroke="none"/>
      <circle class="box-plot-mark-dot" cx="128.0" cy="52.44" r="2" fill="var(--colour-brand)" stroke="var(--colour-surface)" stroke-width="1"/>
      <text class="box-plot-mark-label" x="152.0" y="55.44" text-anchor="start" font-size="9" fill="var(--colour-text)" font-family="ui-monospace, 'SF Mono', Menlo, monospace">70</text>
      <title>Web max: 70</title>
    </g>
    <g class="box-plot-mark" data-box-mark="q3">
      <circle class="box-plot-mark-hit" cx="128.0" cy="105.78" r="7" fill="transparent" stroke="none"/>
      <circle class="box-plot-mark-dot" cx="128.0" cy="105.78" r="2" fill="var(--colour-brand)" stroke="var(--colour-surface)" stroke-width="1"/>
      <text class="box-plot-mark-label" x="152.0" y="108.78" text-anchor="start" font-size="9" fill="var(--colour-text)" font-family="ui-monospace, 'SF Mono', Menlo, monospace">40</text>
      <title>Web q3: 40</title>
    </g>
    <g class="box-plot-mark" data-box-mark="median">
      <circle class="box-plot-mark-hit" cx="128.0" cy="132.44" r="7" fill="transparent" stroke="none"/>
      <circle class="box-plot-mark-dot" cx="128.0" cy="132.44" r="2" fill="var(--colour-brand)" stroke="var(--colour-surface)" stroke-width="1"/>
      <text class="box-plot-mark-label" x="152.0" y="135.44" text-anchor="start" font-size="9" fill="var(--colour-text)" font-family="ui-monospace, 'SF Mono', Menlo, monospace">25</text>
      <title>Web median: 25</title>
    </g>
    <g class="box-plot-mark" data-box-mark="q1">
      <circle class="box-plot-mark-hit" cx="128.0" cy="150.22" r="7" fill="transparent" stroke="none"/>
      <circle class="box-plot-mark-dot" cx="128.0" cy="150.22" r="2" fill="var(--colour-brand)" stroke="var(--colour-surface)" stroke-width="1"/>
      <text class="box-plot-mark-label" x="152.0" y="153.22" text-anchor="start" font-size="9" fill="var(--colour-text)" font-family="ui-monospace, 'SF Mono', Menlo, monospace">15</text>
      <title>Web q1: 15</title>
    </g>
    <g class="box-plot-mark" data-box-mark="min">
      <circle class="box-plot-mark-hit" cx="128.0" cy="168.0" r="7" fill="transparent" stroke="none"/>
      <circle class="box-plot-mark-dot" cx="128.0" cy="168.0" r="2" fill="var(--colour-brand)" stroke="var(--colour-surface)" stroke-width="1"/>
      <text class="box-plot-mark-label" x="152.0" y="171.0" text-anchor="start" font-size="9" fill="var(--colour-text)" font-family="ui-monospace, 'SF Mono', Menlo, monospace">5</text>
      <title>Web min: 5</title>
    </g>
    <text x="128.0" y="192" text-anchor="middle" font-size="10" fill="var(--colour-text)" font-family="ui-monospace, 'SF Mono', Menlo, monospace">Web</text>
    <line class="box-plot-whisker" x1="192.0" y1="155.56" x2="192.0" y2="137.78" stroke="var(--colour-text-muted)" stroke-width="1"/>
    <line class="box-plot-whisker" x1="192.0" y1="88.0" x2="192.0" y2="8.0" stroke="var(--colour-text-muted)" stroke-width="1"/>
    <line class="box-plot-whisker-cap" x1="183.0" y1="155.56" x2="201.0" y2="155.56" stroke="var(--colour-text-muted)" stroke-width="1"/>
    <line class="box-plot-whisker-cap" x1="183.0" y1="8.0" x2="201.0" y2="8.0" stroke="var(--colour-text-muted)" stroke-width="1"/>
    <rect class="box-plot-box" x="174.0" y="88.0" width="36" height="49.78" fill="var(--colour-brand)" fill-opacity="0.18" stroke="var(--colour-brand)" stroke-width="1">
      <title>Jobs: Q1 22.0, median 35.0, Q3 50.0, n=64</title>
    </rect>
    <line class="box-plot-median" x1="174.0" y1="114.67" x2="210.0" y2="114.67" stroke="var(--colour-brand)" stroke-width="1"/>
    <g class="box-plot-mark" data-box-mark="max">
      <circle class="box-plot-mark-hit" cx="192.0" cy="8.0" r="7" fill="transparent" stroke="none"/>
      <circle class="box-plot-mark-dot" cx="192.0" cy="8.0" r="2" fill="var(--colour-brand)" stroke="var(--colour-surface)" stroke-width="1"/>
      <text class="box-plot-mark-label" x="216.0" y="11.0" text-anchor="start" font-size="9" fill="var(--colour-text)" font-family="ui-monospace, 'SF Mono', Menlo, monospace">95</text>
      <title>Jobs max: 95</title>
    </g>
    <g class="box-plot-mark" data-box-mark="q3">
      <circle class="box-plot-mark-hit" cx="192.0" cy="88.0" r="7" fill="transparent" stroke="none"/>
      <circle class="box-plot-mark-dot" cx="192.0" cy="88.0" r="2" fill="var(--colour-brand)" stroke="var(--colour-surface)" stroke-width="1"/>
      <text class="box-plot-mark-label" x="216.0" y="91.0" text-anchor="start" font-size="9" fill="var(--colour-text)" font-family="ui-monospace, 'SF Mono', Menlo, monospace">50</text>
      <title>Jobs q3: 50</title>
    </g>
    <g class="box-plot-mark" data-box-mark="median">
      <circle class="box-plot-mark-hit" cx="192.0" cy="114.67" r="7" fill="transparent" stroke="none"/>
      <circle class="box-plot-mark-dot" cx="192.0" cy="114.67" r="2" fill="var(--colour-brand)" stroke="var(--colour-surface)" stroke-width="1"/>
      <text class="box-plot-mark-label" x="216.0" y="117.67" text-anchor="start" font-size="9" fill="var(--colour-text)" font-family="ui-monospace, 'SF Mono', Menlo, monospace">35</text>
      <title>Jobs median: 35</title>
    </g>
    <g class="box-plot-mark" data-box-mark="q1">
      <circle class="box-plot-mark-hit" cx="192.0" cy="137.78" r="7" fill="transparent" stroke="none"/>
      <circle class="box-plot-mark-dot" cx="192.0" cy="137.78" r="2" fill="var(--colour-brand)" stroke="var(--colour-surface)" stroke-width="1"/>
      <text class="box-plot-mark-label" x="216.0" y="140.78" text-anchor="start" font-size="9" fill="var(--colour-text)" font-family="ui-monospace, 'SF Mono', Menlo, monospace">22</text>
      <title>Jobs q1: 22</title>
    </g>
    <g class="box-plot-mark" data-box-mark="min">
      <circle class="box-plot-mark-hit" cx="192.0" cy="155.56" r="7" fill="transparent" stroke="none"/>
      <circle class="box-plot-mark-dot" cx="192.0" cy="155.56" r="2" fill="var(--colour-brand)" stroke="var(--colour-surface)" stroke-width="1"/>
      <text class="box-plot-mark-label" x="216.0" y="158.56" text-anchor="start" font-size="9" fill="var(--colour-text)" font-family="ui-monospace, 'SF Mono', Menlo, monospace">12</text>
      <title>Jobs min: 12</title>
    </g>
    <text x="192.0" y="192" text-anchor="middle" font-size="10" fill="var(--colour-text)" font-family="ui-monospace, 'SF Mono', Menlo, monospace">Jobs</text>
  </svg>
  <p class="box-plot-summary">3 groups · 282 samples</p>
</div>

Full reference: contracts, guidance, anatomy →

Progress stages data

A native progress bar with stage chips — where the work is, stage by stage, with completion tones.

33%
Draft (4)Review (2)Published (0)

1 of 3 complete

html
<div class="progress-region hm-measure-lg" data-progress-region>
  <div class="progress-header">
    <progress data-progress value="33" max="100"></progress>
    <span>33%</span>
  </div>
  <div class="progress-stages"><span class="progress-chip" data-stage-tone="complete">Draft (4)</span><span class="progress-chip" data-stage-tone="active">Review (2)</span><span class="progress-chip" data-stage-tone="empty">Published (0)</span></div>
  <p class="progress-summary">1 of 3 complete</p>
</div>

Full reference: contracts, guidance, anatomy →

Profile card data

The identity panel: avatar or initials beside name and meta, a KPI stats strip (value above label), and optional facts.

Maya Reyes

Operations lead · North grid

Orders
7
Sites
3
On call
—
  • Certified for HV switching
  • Joined March 2024
html
<div class="profile-card-region hm-measure">
  <div class="profile-card" data-profile-card>
    <div class="profile-identity">
      <span class="profile-initials" aria-hidden="true">MR</span>
      <div class="profile-text">
        <h3 class="profile-primary">Maya Reyes</h3>
        <p class="profile-secondary">Operations lead · North grid</p>
      </div>
    </div>
    <dl class="profile-stats">
      <div class="profile-stat">
        <dt class="profile-stat-label">Orders</dt>
        <dd class="profile-stat-value">7</dd>
      </div>
      <div class="profile-stat">
        <dt class="profile-stat-label">Sites</dt>
        <dd class="profile-stat-value">3</dd>
      </div>
      <div class="profile-stat">
        <dt class="profile-stat-label">On call</dt>
        <dd class="profile-stat-value">—</dd>
      </div>
    </dl>
    <ul class="profile-facts">
      <li class="profile-fact"><span class="profile-fact-bullet" aria-hidden="true">·</span><span class="profile-fact-text">Certified for HV switching</span></li>
      <li class="profile-fact"><span class="profile-fact-bullet" aria-hidden="true">·</span><span class="profile-fact-text">Joined March 2024</span></li>
    </ul>
  </div>
</div>

Full reference: contracts, guidance, anatomy →

Cell grid data

A responsive grid of plain record cells — title plus label: value lines, 1 → 2 → 3 columns as the container widens.

Aurora Substation

Region: North

Load: 82%

Beacon Substation

Region: East

Load: 47%

Cinder Substation

Region: West

Load: 91%

html
<div class="grid-region">
  <div class="grid-list">
    <div class="grid-cell ">
      <h4 class="grid-cell-title">Aurora Substation</h4>
      <p class="grid-cell-field"><span class="grid-cell-field-label">Region:</span> North</p>
      <p class="grid-cell-field"><span class="grid-cell-field-label">Load:</span> 82%</p>
    </div>
    <div class="grid-cell ">
      <h4 class="grid-cell-title">Beacon Substation</h4>
      <p class="grid-cell-field"><span class="grid-cell-field-label">Region:</span> East</p>
      <p class="grid-cell-field"><span class="grid-cell-field-label">Load:</span> 47%</p>
    </div>
    <div class="grid-cell ">
      <h4 class="grid-cell-title">Cinder Substation</h4>
      <p class="grid-cell-field"><span class="grid-cell-field-label">Region:</span> West</p>
      <p class="grid-cell-field"><span class="grid-cell-field-label">Load:</span> 91%</p>
    </div>
  </div>
</div>

Full reference: contracts, guidance, anatomy →

List region data

The in-card data table: CSV export, sortable headers, a scrollable table body, and an overflow count.

Name▲OwnerStatus
Capacity reviewJ. DiasActive
Load studyK. NovakDraft
Quarterly auditM. ReyesActive
Site walkdownM. ReyesClosed
Vendor renewalA. OseiDraft

Showing 5 of 5

html
<!-- icons: include the icon sheet once per page (see the Setup section, #setup) -->
<div class="list-region" data-list-region id="hm-list-region-demo">
  <div class="list-actions">
    <div class="list-action-group"><button type="button" class="list-csv-button" title="Export CSV" aria-label="Export CSV" data-csv-endpoint="sample-list-export.csv" data-csv-filename="work-items.csv" onclick="window.dz.downloadCsv(this.dataset.dzCsvEndpoint||this.dataset.csvEndpoint, this.dataset.dzCsvFilename||this.dataset.csvFilename)"><svg class="icon" aria-hidden="true"><use href="#i-download"/></svg></button></div>
  </div>
  <div class="list-scroll">
    <table class="list-table">
      <thead>
        <tr>
          <th><a class="list-sort-link" hx-get="/mock/list-region?sort=name&amp;dir=desc" hx-target="closest [data-list-region]" hx-swap="outerHTML">Name<span>▲</span></a></th>
          <th><a class="list-sort-link" hx-get="/mock/list-region?sort=owner&amp;dir=asc" hx-target="closest [data-list-region]" hx-swap="outerHTML">Owner</a></th>
          <th><a class="list-sort-link" hx-get="/mock/list-region?sort=status&amp;dir=asc" hx-target="closest [data-list-region]" hx-swap="outerHTML">Status</a></th>
        </tr>
      </thead>
      <tbody>
        <tr class="list-row is-clickable">
          <td>Capacity review</td>
          <td>J. Dias</td>
          <td>Active</td>
        </tr>
        <tr class="list-row is-clickable">
          <td>Load study</td>
          <td>K. Novak</td>
          <td>Draft</td>
        </tr>
        <tr class="list-row is-clickable">
          <td>Quarterly audit</td>
          <td>M. Reyes</td>
          <td>Active</td>
        </tr>
        <tr class="list-row ">
          <td>Site walkdown</td>
          <td>M. Reyes</td>
          <td>Closed</td>
        </tr>
        <tr class="list-row ">
          <td>Vendor renewal</td>
          <td>A. Osei</td>
          <td>Draft</td>
        </tr>
      </tbody>
    </table>
  </div>
  <p class="list-overflow">Showing 5 of 5</p>
</div>

Full reference: contracts, guidance, anatomy →

Task inbox data

The personal worklist: filter chips over urgency-flagged items, each a drill link with title and meta.

html
<!-- icons: include the icon sheet once per page (see the Setup section, #setup) -->
<div class="hm-measure-lg">
  <div class="task-inbox-region" data-task-inbox data-region-name="inbox">
    <div class="task-inbox-chips">
      <div class="task-inbox-chip" data-chip-id="all"><span class="task-inbox-chip-count">6</span><span class="task-inbox-chip-label">All</span></div>
      <div class="task-inbox-chip" data-chip-id="overdue"><span class="task-inbox-chip-count">2</span><span class="task-inbox-chip-label">Overdue</span></div>
    </div>
    <ul class="task-inbox-items">
      <li class="task-inbox-item" data-urgency="overdue" data-item-id="t1">
        <a class="task-inbox-item-link" href="#">
          <span class="task-inbox-item-icon" aria-hidden="true"><svg class="icon" aria-hidden="true"><use href="#i-inbox"/></svg></span>
          <div class="task-inbox-item-body">
            <div class="task-inbox-item-title">Approve refund — Acme</div>
            <div class="task-inbox-item-meta">due in 2h · assigned to you</div>
          </div>
        </a>
      </li>
      <li class="task-inbox-item" data-urgency="due" data-item-id="t2">
        <a class="task-inbox-item-link" href="#">
          <span class="task-inbox-item-icon" aria-hidden="true"><svg class="icon" aria-hidden="true"><use href="#i-inbox"/></svg></span>
          <div class="task-inbox-item-body">
            <div class="task-inbox-item-title">Review KYC — Globex</div>
            <div class="task-inbox-item-meta">due tomorrow</div>
          </div>
        </a>
      </li>
    </ul>
  </div>
</div>

Full reference: contracts, guidance, anatomy →

Tree data

Hierarchy on native <details>/<summary> for branches; leaves are plain rows. Chevron is CSS chrome (not summary content). No JS.

Engineering2
Platform1
Build tooling
Design systems1
Tokens
html
<div class="hm-measure">
  <div class="tree" data-tree>
    <details class="tree-node" open>
      <summary class="tree-summary"><span class="tree-label">Engineering</span><span class="tree-count">2</span></summary>
      <div class="tree-children">
        <details class="tree-node">
          <summary class="tree-summary"><span class="tree-label">Platform</span><span class="tree-count">1</span></summary>
          <div class="tree-children">
            <div class="tree-leaf"><span class="tree-label">Build tooling</span></div>
          </div>
        </details>
        <details class="tree-node">
          <summary class="tree-summary"><span class="tree-label">Design systems</span><span class="tree-count">1</span></summary>
          <div class="tree-children">
            <div class="tree-leaf"><span class="tree-label">Tokens</span></div>
          </div>
        </details>
      </div>
    </details>
  </div>
</div>

Full reference: contracts, guidance, anatomy →

Diagram data

A horizontal-scroll wrapper for server-emitted Mermaid source — the library replaces the <pre> with rendered SVG.

erDiagram
  CUSTOMER ||--o{ ORDER : places
  ORDER ||--|{ LINE_ITEM : contains
html
<div class="diagram-scroll" data-diagram>
  <pre class="mermaid diagram-source">erDiagram
  CUSTOMER ||--o{ ORDER : places
  ORDER ||--|{ LINE_ITEM : contains</pre>
</div>

Full reference: contracts, guidance, anatomy →

Separator

A hairline divider on the border token — horizontal (`<hr>`) or vertical (`role=separator`).

Account details


Billing and invoices

DraftPublishedArchived
html
<div class="hm-stack hm-measure">
  <p class="hm-demo-muted">Account details</p>
  <hr class="separator">
  <p class="hm-demo-muted">Billing and invoices</p>
  <div class="hm-demo-row">
    <span class="hm-demo-muted">Draft</span>
    <div class="separator--vertical" role="separator" aria-orientation="vertical"></div>
    <span class="hm-demo-muted">Published</span>
    <div class="separator--vertical" role="separator" aria-orientation="vertical"></div>
    <span class="hm-demo-muted">Archived</span>
  </div>
</div>

Full reference: contracts, guidance, anatomy →

Icon icon

Inline SVG from a vendored Lucide registry — currentColor, decorative by default. Shown here as the sprite <use> form (one sheet per page).

html
<!-- icons: include the icon sheet once per page (see the Setup section, #setup) -->
<div class="icon-demo"><svg class="icon icon--size-xs" aria-hidden="true"><use href="#i-circle-check"/></svg><svg class="icon icon--size-sm" aria-hidden="true"><use href="#i-circle-check"/></svg><svg class="icon icon--size-md" aria-hidden="true"><use href="#i-circle-check"/></svg><svg class="icon icon--size-lg" aria-hidden="true"><use href="#i-circle-check"/></svg><svg class="icon icon--size-xl" aria-hidden="true"><use href="#i-circle-check"/></svg></div>

Full reference: contracts, guidance, anatomy →