Primitive boundaries

Tables stay single-surface: wb-table-wrap owns the border and radius, any toolbar inside it stays a control row, and the table header remains a band instead of a second card. Text casing is content-defined throughout the system; hierarchy comes from weight, spacing, and color rather than automatic uppercase transforms so metadata stays quiet but clear.

Need Reach for Why
Primary actions and status wb-btn, wb-badge, wb-status-pill, wb-action-group Use these when the page needs clear actions, compact signals, and row-level controls.
Grouped information and data display wb-card, wb-stat, wb-list, wb-callout, wb-table Cards, stats, lists, and callouts are surfaces. Tables remain primitives. Choose by UI role, not by folder location.
Orientation and section switching wb-tabs, wb-pagination, pattern wb-breadcrumb Use these when the page needs context or section changes, but keep breadcrumb secondary to the page title.
Temporary or layered interaction wb-modal, wb-dropdown, wb-drawer, wb-accordion, wb-toast Use these when the page needs temporary surfaces and explicit behavior hooks instead of hidden helper logic. Use wb-modal for structured dialogs, content-first viewer work, and cookie-consent preference centers.

Actions and signals

Success

Use compact success signals when the interface needs calm confirmation without turning the row into a new surface.

Warning

Keep warnings concise so they act like state signals rather than full explanatory callouts.

Danger

Use danger states for destructive or high-risk conditions that need immediate recognition.

Active

Active marks the current state without introducing extra helper markup.

Pending

Pending stays compact so data rows can surface progress without becoming mini dashboards.

Forms

Fields use a three-slot contract: label, control, and wb-field-meta. Keep hints and errors inside the shared meta area so multi-column wb-form-row layouts stay aligned even when sibling fields carry different assistive content. Do not place wb-field-hint or wb-field-error directly under wb-field. In stacked forms, empty meta rows are usually unnecessary.

Field Contract example
Project name Hint: Use a short internal name.
Environment Options: Production, Staging, Local.
Owner email Hint: Enter a valid email address.
Region Options: Frankfurt, London, Virginia. Hint: Used for billing and data residency. Error: This region is not available on the current plan.
Description Placeholder: https://
Admin password Hint: Use the shipped input-group toggle instead of adding page-local password JS.
Enable analytics Options: Private, Public, Maintenance mode.

Surfaces and display contracts

Stats

MRR
$12.4k
+8.2%
Trials
214
-2.1%

Alerts

Recommended markup

<div class="wb-alert wb-alert-info">
  <h3 class="wb-alert-title">Title</h3>
  <p>Content...</p>
</div>

<div class="wb-alert wb-alert-info">
  <p>Content only...</p>
</div>

Legacy wrapper markup still renders, but direct semantic headings and paragraphs are now the recommended pattern.

Info
Use alerts for contextual system feedback.
Success
The settings were saved correctly.
Warning
Review destructive actions before confirming.
Content-only alerts
Content-only alerts work without an extra wrapper or title element.

Users

Name Role Status Actions
Alice Brown Owner Active Open
Marco Chen Editor Pending Open

Open in Playground when you want to combine these primitives into a real snippet before wiring them into a full page. Use td.wb-text-end directly when an action cell only needs end alignment.

Editorial body copy

wb-rich-text is the CSS-only primitive for sanitized editorial body copy after safe Markdown-like content has already been rendered to HTML. Keep headings, buttons, media, tables, layout composition, and raw HTML handling outside this primitive.

Readable body copy

Use this primitive for longer editorial copy where paragraphs, strong emphasis, tone shifts, inline code, and links should stay readable without leaking typography rules into the global page.

The readable modifier keeps the body-copy contract explicit, but width now comes from the parent card, shell, or content column instead of the primitive itself.

  • Supports safe unordered lists.
  • Keeps spacing predictable inside one scoped primitive.
  1. Render sanitized content first.
  2. Apply wb-rich-text to the output wrapper.

Rich text is a body-copy contract, not a page builder. Compose headings, media blocks, tables, and actions with their own shipped primitives and patterns.

Rhythm variants

Variant Guidance
wb-rich-text wb-rich-text-compact Compact rhythm fits denser panels, side notes, and shorter editorial summaries. Paragraph spacing tightens while list and quote behavior stays scoped to the same primitive.
wb-rich-text wb-rich-text-loose Loose rhythm gives longer reading surfaces more air. Use it when the page wants calmer editorial pacing, not when you need a new layout wrapper or width rule.

Pagination and context navigation

Use wb-pagination for page-based result sets. Keep it semantic: a nav landmark, an ordered list, a passive current-page marker with aria-current="page", and a passive disabled previous/next state when navigation is unavailable. Do not reuse it for breadcrumb hierarchy or content footer previous-next links.

Default rhythm

  1. Previous
  2. 1
  3. 2
  4. 3
  5. ...
  6. 12
  7. Next

Page 1 of 12

Current and disabled states are passive spans, not fake disabled links.

  1. Previous
  2. 9
  3. 10
  4. 11
  5. Next

Use the compact variant only when the standard rhythm is too large for a dense table or narrow control row.

Interaction shells

Keep lightweight reveal-and-expand patterns close to the page, and reserve wb-modal for true top-layer dialog, viewer, or cookie-preference work.

Cookie consent boundary
Cookie Consent is not a primitive. It is a pattern plus interactive hooks that composes wb-card, switches, buttons, and one shared wb-modal preference center.
Project
Actions

Open. Duplicate. Archive.

Info

Popover note

Use popover for small contextual panels that should not become full dropdown menus or modals.

Tabs switch between matched panels by data-wb-tab and panel id.

<button class="wb-tabs-btn" data-wb-tab="panel-id">Tab</button>
<div class="wb-tabs-panel" id="panel-id">...</div>

Accordion and drawer flows

Primitive questions

What counts as a primitive?
A primitive is an atomic control or local UI contract such as a button, input, table, modal, dropdown, or tabs. Cards, stats, callouts, lists, toolbars, filter bars, and empty states are surfaces.
What does not count?
Theme tokens belong to foundation. Screen compositions belong to patterns. Small nudges belong to utilities.

Use wb-modal for two jobs: explicit decisions and content-first viewing.

Dialog modal

Use it when the user needs to confirm, cancel, continue, or make a focused decision above the page.

Media viewer modal

Use the same modal primitive for images and content previews. Both thumbnails below open one shared viewer modal.

  • Workspace dashboard with charts and analytics cards.
  • Product planning session around a shared table with laptops and notes.
Canonical rule
wb-modal is the single canonical top-layer primitive. Gallery viewer behavior stays inside that modal contract instead of becoming a separate public overlay or lightbox primitive.

Primitive modal

Use wb-modal when the interaction is a real dialog with explicit structure, decisions, or footer actions.

1 / 2

Overview dashboard

Use wb-modal in a content-first mode for gallery viewing instead of inventing a separate public overlay or lightbox primitive.

Primitive checklist

Checklist Guidance
Use shipped classes Do not invent presentation-only names.
Use tokens indirectly Stay inside package primitives and patterns when possible.
Use data hooks for behavior Let shipped JavaScript handle overlays and interaction state.