aobalabs design system

Shared visual vocabulary for sites in the aobalabs family. Pick a theme from the switcher to preview each component in the tenant palette it will ship in.

How to use this page. Each demo shows a live snippet and its copy-paste source. Click through the themes to verify a primitive reads correctly across every tenant before adopting it.

Foundations

Color tokens

All colours go through CSS custom properties so each tenant theme swaps the palette at :root[data-theme="<tenant>"]. Use semantic tokens (--text-muted, --danger) over raw hex anywhere outside the package's themes.css.

Surface
  • --bg-primary page background
  • --bg-elevated cards, nav drawer, inputs
  • --bg-tag chips, subtle fills
Text
  • --text-primary body, headings
  • --text-muted meta, hints, labels
  • --text-faint de-emphasised meta, dropped
Border
  • --border-subtle default field, card edge
  • --border-focus focused input, active field
Semantic
  • --accent primary action, active state
  • --success done, paid, healthy
  • --warning snoozed, due soon
  • --danger errors, destructive actions

Primitives

Buttons

Four variants, each backed by a Go const on ButtonVariant. The variant value IS the CSS class suffix so adding a new style is one Go const + one CSS rule.

Primary

the dominant action on a screen — Capture, Save, Create

@Button(ButtonPrimary, "button", false) { Capture }
Secondary

supporting action — Back, Cancel, alternate path

@Button(ButtonSecondary, "button", false) { Cancel }
Ghost

low-emphasis action, used in dense rows

@Button(ButtonGhost, "button", false) { Edit }
Destructive

irreversible action — Drop, Archive, Delete

@Button(ButtonDestructive, "button", false) { Drop }

Links

Default, muted (for row meta), arrow (for section-to-section navigation). The arrow variant lifts on hover to telegraph the destination.

Default link

inline action or navigation target

<a class="link" href="#">view all captures</a>
Muted link

metadata link, used in row meta

<a class="link link--muted" href="#">2 days ago</a>
Arrow link

section-to-section navigation

<a class="link link--arrow" href="#">go to triage</a>

Chips

Compact state and kind labels. Soft variants (success / warning / danger) pair a background tint with the matching text colour so the tone reads at a glance in dense rows.

Status chip

current entity state in dense lists

active
@Chip(ChipStatus, "active")
Kind chip

discriminator for task / capture variants

payment
@Chip(ChipKind, "payment")
Location chip

shorthand for hierarchical location refs

Sendai
@Chip(ChipLocation, "Sendai")
Soft chip — success

outcome state — paid, done, healthy

paid
@Chip(ChipSuccess, "paid")
Soft chip — warning

transitional state — snoozed, due soon

snoozed
@Chip(ChipWarning, "snoozed")
Soft chip — danger

blocked / overdue / failed

overdue
@Chip(ChipDanger, "overdue")

Keyboard chord

Single visual unit for any keypress hint. Compose multi-key chords as one string ("⌘K") rather than chaining multiple Kbd calls — the design treats each chip as one unit.

Single key

single keypress with no modifier

Esc
@Kbd("Esc")
Chord

modifier + key combo — render as one chip

⌘K
@Kbd("⌘K")
Inline in prose

embedded in body copy — sized down a touch

Press ⌘B to toggle the nav.
Press @Kbd("⌘B") to toggle the nav.

Status dot

Tiny coloured circle that pairs with adjacent label text. Four tone variants. Site-specific score-coloured variants (e.g. a 0–10 motivation scale) live in the consuming site, not here.

Active

currently in scope — default colour, no chip needed

in progress
@StatusDot(StatusDotActive, "active") in progress
Snoozed

deliberately deferred — warm tone

deferred to next week
@StatusDot(StatusDotSnoozed, "snoozed") deferred to next week
Done

completed — success tone

shipped 2 days ago
@StatusDot(StatusDotDone, "done") shipped 2 days ago
Dropped

intentionally abandoned — muted tone

dropped — not pursuing
@StatusDot(StatusDotDropped, "dropped") dropped — not pursuing

Tabs

Segmented control for in-page view switching. The active tab carries the underline; one Tab per --view=… filter.

View filter tabs

segmented control for in-page view switching — list view filter, future activity-feed filters

<nav class="tabs" aria-label="Filter">
  <a class="tab tab--active" href="#">Active</a>
  <a class="tab" href="#">Snoozed</a>
  <a class="tab" href="#">Dormant</a>
  <a class="tab" href="#">Done</a>
  <a class="tab" href="#">Dropped</a>
  <a class="tab" href="#">All</a>
</nav>

Patterns

Entity card

Standard chrome for detail pages: title + optional kind chip on the header, key-value fields in a two-column grid below.

Entity card

the chrome of a detail page — title + kind chip + key-value fields

Yuki Tanaka

person
Email
yuki@example.jp
Lives in
Sendai, Miyagi
Last contact
<article class="card">
  <header class="card-header">
    <h3 class="card-title">Yuki Tanaka</h3>
    <span class="chip chip--kind">person</span>
  </header>
  <dl class="card-fields">
    <div class="card-field">
      <dt class="card-field-label">Email</dt>
      <dd class="card-field-value">yuki@example.jp</dd>
    </div>
    <div class="card-field">
      <dt class="card-field-label">Lives in</dt>
      <dd class="card-field-value">Sendai, Miyagi</dd>
    </div>
    <div class="card-field">
      <dt class="card-field-label">Last contact</dt>
      <dd class="card-field-value"><time class="time" datetime="2026-05-12">May 12 · 18:20</time></dd>
    </div>
  </dl>
</article>

List row

Whole-row clickable surface — wrap row contents in <a class="list-row"> for a single hit area. Hovers lift to --bg-tag.

Clickable row

whole-row link — label on the left, meta on the right

<ul class="list list--rows" role="list">
  <li>
    <a class="list-row" href="#">
      <span class="list-row-label">Renew passport</span>
      <span class="list-row-meta">due Jun 1</span>
    </a>
  </li>
  <li>
    <a class="list-row" href="#">
      <span class="list-row-label">Pay NHK bill</span>
      <span class="list-row-meta">Jan 12</span>
    </a>
  </li>
</ul>

Breadcrumb

Single-page nav chrome with a back arrow. Optional right-aligned action button (Edit / Archive); more than one action wants a dedicated toolbar instead.

Back link

single-step nav up — leading arrow + muted link

<nav class="breadcrumb" aria-label="Breadcrumb">
  <a class="link link--muted breadcrumb-back" href="#"><span aria-hidden="true">←</span> All tasks</a>
</nav>
Breadcrumb with action

back link + right-aligned action — the standard show-page header

<nav class="breadcrumb" aria-label="Breadcrumb">
  <a class="link link--muted breadcrumb-back" href="#"><span aria-hidden="true">←</span> All projects</a>
  <a class="button button--secondary breadcrumb-action" href="#">Edit</a>
</nav>

Page section

Standard subsection chrome. PageSection uses an <h3>; PageSectionTop uses an <h2> for the single page-level heading on new / edit pages.

Section (h3)

in-page subsection beneath the breadcrumb

Members

Section body content goes here.

<section class="page-section">
  <h3 class="section-heading">Members</h3>
  <p>Section body content goes here.</p>
</section>
Top section (h2)

the single page-level heading on a new/edit page

Edit profile

Top-level form content.

<section class="page-section">
  <h2 class="section-heading">Edit profile</h2>
  <p>Top-level form content.</p>
</section>

Button row

Flex container for action button groupings. Pairs of primary / destructive on detail pages, or grouped secondary actions in a card footer.

Action row

groups action buttons — typically Complete + Drop on detail pages

<div class="button-row">
  <button class="button button--primary" type="button">Complete</button>
  <button class="button button--destructive" type="button">Drop</button>
</div>