Design System
The single source of truth for colors, typography, spacing, and components across every Acme product surface.
Colors
All color tokens from design-tokens.ts. Hover any swatch to see its name. Switch to Dev mode to reveal CSS variable names. Contrast ratios shown against white (#FFFFFF).
/* Reference colors via CSS custom properties */
.element {
color: var(--color-brand-primary);
background-color: var(--color-brand-primary-bg);
border-color: var(--color-brand-primary-light);
}
/* Semantic usage */
.error-text { color: var(--color-error-main); }
.success-text { color: var(--color-success-dark); }
.warning-bg { background: var(--color-warning-bg); }
/* Accent usage */
.highlight { background: var(--color-accent-yellow-bg); }
.purple-badge { background: var(--color-accent-purple-bg); color: var(--color-accent-purple-dark); }
Typography
Font family: Mulish (Google Fonts). All sizes, weights, and spacing from design-tokens.ts. Switch to Dev mode to reveal CSS variables and copy snippets on each style.
The quick brown fox jumps over the lazy dog. A compact rhythm for headlines, hero copy, and display text where lines sit close together.
The quick brown fox jumps over the lazy dog. The standard reading rhythm for body copy, UI labels, and most interface text.
The quick brown fox jumps over the lazy dog. Open breathing room for long-form prose, descriptions, and content-heavy layouts.
/* Type scale via CSS variables */ h1 { font-size: var(--font-size-h1); font-weight: 800; letter-spacing: var(--letter-spacing-tight); } h2 { font-size: var(--font-size-h2); font-weight: 700; } h3 { font-size: var(--font-size-h3); font-weight: 400; } body { font-family: var(--font-family-base); font-size: var(--font-size-body); line-height: var(--line-height-base); font-weight: var(--font-weight-regular); } /* Label / caps pattern */ .label { font-size: 11px; font-weight: var(--font-weight-bold); text-transform: uppercase; letter-spacing: var(--letter-spacing-wide); } /* Mono — code, tokens, values */ code { font-family: var(--font-family-mono); font-size: var(--font-size-small); }
Spacing & Foundations
7-step spacing scale, border radius, elevation, border widths, icon sizes, z-index, and breakpoints — all drawn directly from design-tokens.ts. Dev mode reveals CSS variable names and rem values.
/* Spacing */ .card { padding: var(--space-xl); } .card-header { padding: var(--space-lg) var(--space-xl); gap: var(--space-sm); } .badge { padding: var(--space-xxs) var(--space-xs); } /* Radius */ .btn { border-radius: var(--radius-md); } .card { border-radius: var(--radius-lg); } .pill { border-radius: var(--radius-full); } /* Elevation */ .card { box-shadow: var(--shadow-sm); } .dropdown { box-shadow: var(--shadow-lg); } .modal { box-shadow: var(--shadow-xl); } /* Borders */ .input { border: var(--border-default) solid var(--color-neutral-300); } .divider{ height: var(--border-hairline); } .focus { border: var(--border-strong) solid var(--color-brand-primary); } /* Z-index */ .modal { z-index: var(--z-modal); } .dropdown { z-index: var(--z-dropdown); } .toast { z-index: var(--z-toast); }
Buttons
4 variants × 3 sizes × 4 states shown inline. Hover any live button to see the interaction. Focus rings use brand primary at 14% opacity.
<button class="btn btn-md btn-primary">Primary</button> <button class="btn btn-md btn-secondary">Secondary</button> <button class="btn btn-md btn-ghost">Ghost</button> <button class="btn btn-md btn-danger">Danger</button> <button class="btn btn-md btn-primary" disabled>Disabled</button>
<button class="btn btn-sm btn-primary">Small</button> <button class="btn btn-md btn-primary">Medium</button> <button class="btn btn-lg btn-primary">Large</button>
<button class="btn btn-md btn-primary"> <svg width="14" height="14" ...>...</svg> New project </button> <!-- Icon-only (add title for accessibility) --> <button class="btn btn-md btn-ghost" style="padding:8px;" title="Settings"> <svg width="16" height="16" ...>...</svg> </button>
Inputs
All states shown inline. Focus ring uses brand primary at 14% opacity. Error uses error-main at 14% opacity.
<!-- Default --> <div class="field"> <label class="field-label">Label</label> <input class="input" placeholder="Enter value…"> <span class="field-hint">Helper text</span> </div> <!-- Error --> <div class="field"> <label class="field-label">Email</label> <input class="input is-error" value="bad@@email"> <span class="field-err-msg">Invalid email address</span> </div> <!-- Disabled --> <input class="input" disabled value="Locked value">
<!-- Textarea -->
<textarea class="input" rows="4" placeholder="Long-form text…"></textarea>
<!-- Select with arrow -->
<div class="select-wrap">
<select class="input">
<option value="" disabled selected>Choose…</option>
<option>Option A</option>
<option>Option B</option>
</select>
</div>
Badges
Status indicators using semantic color tokens. All 10 colour variants shown. The dot can be removed with .badge-no-dot. Pill shape with .badge-pill.
<span class="badge badge-info">Info</span> <span class="badge badge-success">Success</span> <span class="badge badge-error">Error</span> <span class="badge badge-warning">Warning</span> <span class="badge badge-neutral">Neutral</span> <span class="badge badge-brand">Brand</span> <!-- No dot --> <span class="badge badge-info badge-no-dot">Info</span> <!-- Pill shape --> <span class="badge badge-success badge-pill">Success</span>
Cards
Surface containers using elevation, radius-lg, and border tokens. Hover any card to see the lift interaction. Stat cards use the full spacing and typography scale.
<div class="card">
<div class="card-img-area">
<!-- image or illustration -->
<div class="card-badge-overlay">
<span class="badge badge-success badge-pill">Live</span>
</div>
</div>
<div class="card-body">
<div class="card-eyebrow">Category</div>
<div class="card-title">Card Title</div>
<div class="card-text">Supporting description text.</div>
</div>
<div class="card-footer">
<span class="card-meta">Updated 2h ago</span>
<button class="btn btn-sm btn-ghost">View</button>
</div>
</div>
<div class="stat-card">
<div class="stat-eyebrow">Total projects</div>
<div class="stat-value">142</div>
<div class="stat-change up">
<!-- up arrow svg -->
+12% this month
</div>
</div>
Alerts
In-page feedback banners using semantic colour tokens. Left-border accent establishes visual hierarchy at a glance. Dismiss button optional.
<div class="alert alert-success">
<span class="alert-icon">✓</span>
<div class="alert-body">
<div class="alert-title">Changes saved</div>
<div class="alert-text">Supporting detail.</div>
</div>
<button class="alert-dismiss" aria-label="Dismiss">✕</button>
</div>
<!-- Variants: alert-error · alert-warning · alert-info -->
<div class="alert alert-warning">
<span class="alert-icon">⚠</span>
<div class="alert-body">
<div class="alert-title">Unsaved changes</div>
<div class="alert-text">These will be lost if you navigate away.</div>
<div style="display:flex;gap:8px;margin-top:10px;">
<button class="btn btn-sm btn-danger">Discard</button>
<button class="btn btn-sm btn-ghost">Keep editing</button>
</div>
</div>
</div>
Dropdowns
Contextual menus triggered by a button. Supports items, icons, dividers, grouped headers, and danger actions. Shown open for demo purposes.
<div class="dropdown">
<button class="btn btn-md btn-secondary" onclick="toggleDropdown(this, event)">
Options
<svg ...></svg>
</button>
<div class="dropdown-menu">
<button class="dropdown-item active">Dashboard</button>
<button class="dropdown-item">Analytics</button>
<div class="dropdown-divider"></div>
<button class="dropdown-item danger">Delete</button>
</div>
</div>
<!-- Group header -->
<div class="dropdown-header">Account</div>
<!-- With icon -->
<button class="dropdown-item">
<span class="dropdown-item-icon">✎</span>Edit
</button>
<!-- Disabled -->
<button class="dropdown-item disabled">Manage roles</button>
<div class="dropdown">
<button class="btn btn-sm btn-ghost" onclick="toggleDropdown(this, event)">
Status: Active
<svg ...></svg>
</button>
<div class="dropdown-menu">
<button class="dropdown-item">All statuses</button>
<button class="dropdown-item active">Active</button>
<button class="dropdown-item">Pending</button>
</div>
</div>
Toggles
Binary on/off switches. Use in place of a checkbox when the change takes immediate effect without a form submit.
<!-- Off -->
<label class="toggle-wrap">
<span class="toggle">
<input type="checkbox">
<span class="toggle-slider"></span>
</span>
<span class="toggle-label">Notifications</span>
</label>
<!-- On -->
<label class="toggle-wrap">
<span class="toggle">
<input type="checkbox" checked>
<span class="toggle-slider"></span>
</span>
<span class="toggle-label">Notifications</span>
</label>
<!-- Disabled -->
<label class="toggle-wrap" style="cursor:not-allowed;opacity:0.5;">
<span class="toggle">
<input type="checkbox" disabled>
<span class="toggle-slider"></span>
</span>
<span class="toggle-label">Label</span>
</label>
<div style="display:flex;align-items:center;justify-content:space-between;padding:16px 20px;">
<div>
<div class="toggle-label" style="font-weight:700;">Email notifications</div>
<div class="toggle-sublabel">Receive updates about your account.</div>
</div>
<label class="toggle-wrap" style="margin-left:16px;flex-shrink:0;">
<span class="toggle">
<input type="checkbox" checked>
<span class="toggle-slider"></span>
</span>
</label>
</div>
<!-- Small: 32×18px --> <span class="toggle" style="width:32px;height:18px;"> ... </span> <!-- Default: 40×22px (no override needed) --> <span class="toggle"> ... </span> <!-- Large: 52×28px --> <span class="toggle" style="width:52px;height:28px;"> ... </span>
Checkboxes
Multi-select controls. Use when the user can pick zero or more options from a list, or to confirm a single boolean preference.
<input type="checkbox" class="checkbox-input">
<input type="checkbox" class="checkbox-input" checked>
<input type="checkbox" class="checkbox-input" disabled>
<input type="checkbox" class="checkbox-input" checked disabled>
<!-- Indeterminate requires JS -->
<input type="checkbox" class="checkbox-input" id="chk">
<script>document.getElementById('chk').indeterminate = true;</script>
<!-- Label only -->
<label class="checkbox-wrap">
<input type="checkbox" class="checkbox-input" checked>
<span class="checkbox-label">Remember me</span>
</label>
<!-- Label + description -->
<label class="checkbox-wrap">
<input type="checkbox" class="checkbox-input" checked>
<span>
<span class="checkbox-label">Email notifications</span>
<span class="checkbox-sublabel">Alerts when someone mentions you.</span>
</span>
</label>
<!-- Select-all header (indeterminate when partial) -->
<div style="display:flex;align-items:center;gap:12px;padding:12px 16px;">
<input type="checkbox" class="checkbox-input" id="select-all">
<span class="checkbox-label" style="font-weight:700;">Permissions</span>
</div>
<!-- Row -->
<label style="display:flex;align-items:center;gap:12px;padding:11px 16px;">
<input type="checkbox" class="checkbox-input" checked>
<span>
<span class="checkbox-label">Read</span>
<span class="checkbox-sublabel">View content and data.</span>
</span>
</label>
<!-- JS: wire up select-all -->
<script>
const all = document.getElementById('select-all');
const items = document.querySelectorAll('.checkbox-input:not(#select-all)');
all.addEventListener('change', () => items.forEach(c => c.checked = all.checked));
items.forEach(c => c.addEventListener('change', () => {
const checked = [...items].filter(i => i.checked).length;
all.indeterminate = checked > 0 && checked < items.length;
all.checked = checked === items.length;
}));
</script>
Radio Buttons
Single-select controls. Use when exactly one option must be chosen from a visible list. For binary choices, prefer a toggle; for many options, prefer a select.
<input type="radio" class="radio-input" name="group"> <input type="radio" class="radio-input" name="group" checked> <input type="radio" class="radio-input" name="group" disabled> <input type="radio" class="radio-input" name="group" checked disabled>
<!-- All radios in the same group share name="…" -->
<label class="radio-wrap">
<input type="radio" class="radio-input" name="notify" checked>
<span>
<span class="radio-label">Mentions only</span>
<span class="radio-sublabel">Only when you are directly mentioned.</span>
</span>
</label>
<label class="radio-wrap">
<input type="radio" class="radio-input" name="notify">
<span class="radio-label">Digest</span>
</label>
<label class="radio-card">
<input type="radio" class="radio-input" name="plan" checked>
<span style="flex:1;">
<span class="radio-label">Pro</span>
<span class="radio-sublabel">Unlimited projects and priority support.</span>
</span>
</label>
<!-- CSS drives the selected state -->
<!-- .radio-card:has(.radio-input:checked) -->
<!-- border-color: var(--color-brand-primary) -->
<!-- background: var(--color-brand-primary-bg) -->
Modals
Overlay dialogs that interrupt the current flow to request input or confirm an action. Keep content concise — use a page instead for complex multi-step tasks.
<!-- Backdrop -->
<div class="modal-backdrop" onclick="closeModal(this)">
<div class="modal" onclick="event.stopPropagation()">
<div class="modal-header">
<span class="modal-title">Edit profile</span>
<button class="modal-close" onclick="closeModal()">✕</button>
</div>
<div class="modal-body">
<!-- content -->
</div>
<div class="modal-footer">
<button class="btn btn-md btn-ghost">Cancel</button>
<button class="btn btn-md btn-primary">Save changes</button>
</div>
</div>
</div>
This will make your updates visible to all users immediately. You can unpublish at any time from the content settings page.
This will permanently delete Acme Rebrand and all its files. This action cannot be undone.
<!-- Confirm -->
<div class="modal-footer">
<button class="btn btn-md btn-ghost">Cancel</button>
<button class="btn btn-md btn-primary">Publish</button>
</div>
<!-- Destructive: warning icon in header + btn-danger -->
<div class="modal-header">
<span style="display:flex;align-items:center;gap:8px;">
<span style="width:28px;height:28px;border-radius:9999px;
background:var(--color-error-bg);display:inline-flex;
align-items:center;justify-content:center;">⚠</span>
<span class="modal-title">Delete project?</span>
</span>
<button class="modal-close">✕</button>
</div>
<div class="modal-footer">
<button class="btn btn-md btn-ghost">Cancel</button>
<button class="btn btn-md btn-danger">Delete project</button>
</div>
function openModal(id) {
document.getElementById(id).style.display = 'flex';
document.body.style.overflow = 'hidden';
}
function closeModal(id) {
document.getElementById(id).style.display = 'none';
document.body.style.overflow = '';
}
// Close on backdrop click
document.querySelectorAll('.modal-backdrop').forEach(el => {
el.addEventListener('click', e => {
if (e.target === el) closeModal(el.id);
});
});
// Close on Escape
document.addEventListener('keydown', e => {
if (e.key === 'Escape') {
document.querySelectorAll('.modal-backdrop')
.forEach(el => closeModal(el.id));
}
});
Tooltips
Short contextual labels that appear on hover or focus. Keep copy under ~60 characters. Never put interactive content (links, buttons) inside a tooltip.
<!-- Add placement class to the anchor --> <span class="tooltip-anchor tooltip-top"> <button class="btn btn-sm btn-ghost">Top</button> <span class="tooltip-bubble">Tooltip on top</span> </span> <!-- tooltip-top | tooltip-right | tooltip-bottom | tooltip-left -->
<!-- Dark (default) --> <span class="tooltip-anchor tooltip-top"> <button>...</button> <span class="tooltip-bubble">Default dark tooltip</span> </span> <!-- Light --> <span class="tooltip-anchor tooltip-top tooltip-light"> <button>...</button> <span class="tooltip-bubble">Light surface tooltip</span> </span> <!-- Multiline --> <span class="tooltip-bubble" style="white-space:normal;max-width:180px;"> Longer tooltip that wraps when needed. </span>
<!-- Icon button -->
<span class="tooltip-anchor tooltip-top">
<button class="btn btn-sm btn-ghost" aria-label="Download">
<svg ...></svg>
</button>
<span class="tooltip-bubble">Download</span>
</span>
<!-- Disabled button (wrap in span — disabled blocks pointer events) -->
<span class="tooltip-anchor tooltip-top">
<button class="btn btn-md btn-primary" disabled
style="pointer-events:none;">Submit</button>
<span class="tooltip-bubble">Complete all required fields.</span>
</span>
<!-- CSS-only hover (no JS needed) -->
<style>
.tooltip-bubble { display: none; }
.tooltip-anchor:hover .tooltip-bubble,
.tooltip-anchor:focus-within .tooltip-bubble { display: block; }
</style>
Tables
Structured data display with sortable columns, status badges, row actions, and bulk selection. Always provide a column header; avoid more than 6–7 columns on one view.
| Name | Role | Department | Joined | Location |
|---|---|---|---|---|
| Sarah Chen | Product Designer | Design | 2021-03-14 | San Francisco, CA |
| Marcus Webb | Engineering Lead | Engineering | 2019-07-02 | Austin, TX |
| Priya Nair | Data Analyst | Analytics | 2022-11-28 | New York, NY |
| James Okafor | Marketing Manager | Marketing | 2020-05-19 | Chicago, IL |
| Luna Reyes | Customer Success | Support | 2023-01-09 | Remote |
<div class="ds-table-wrap">
<table class="ds-table">
<thead>
<tr>
<th class="sortable sorted">Name <span class="sort-icon">↑</span></th>
<th class="sortable">Role</th>
<th>Location</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>Sarah Chen</strong></td>
<td>Product Designer</td>
<td class="td-muted">San Francisco, CA</td>
</tr>
</tbody>
</table>
</div>
<!-- td-mono: monospace cell (dates, IDs, code) -->
<!-- td-muted: de-emphasised secondary text -->
<!-- th.sortable + th.sorted: sort affordance -->
| Project | Status | Owner | Due date | Progress | |
|---|---|---|---|---|---|
|
Acme Rebrand
Brand & identity
|
Active | Sarah Chen | 2026-04-30 |
72%
|
|
|
Q2 Campaign
Marketing
|
In review | James Okafor | 2026-05-15 |
45%
|
|
|
API v3 Docs
Engineering
|
Blocked | Marcus Webb | 2026-03-31 |
20%
|
|
|
Onboarding Flow
Product
|
Draft | Priya Nair | 2026-06-01 |
8%
|
|
<!-- Status badge in cell -->
<td><span class="badge badge-success">Active</span></td>
<!-- Progress bar in cell -->
<td>
<div style="display:flex;align-items:center;gap:8px;">
<div style="flex:1;height:4px;background:var(--color-neutral-100);
border-radius:9999px;overflow:hidden;">
<div style="width:72%;height:100%;
background:var(--color-success-main);"></div>
</div>
<span style="font-size:11px;font-weight:700;">72%</span>
</div>
</td>
<!-- Row actions -->
<td>
<div class="table-row-actions">
<button class="row-action">Edit</button>
<button class="row-action row-action-danger">Delete</button>
</div>
</td>
| Invoice | Client | Amount | Status | Issued | |
|---|---|---|---|---|---|
| #INV-0041 | Acme Corp | $4,800.00 | Paid | 2026-02-01 | |
| #INV-0040 | Bright Labs | $2,250.00 | Pending | 2026-01-18 | |
| #INV-0039 | Nova Studio | $9,100.00 | Overdue | 2026-01-05 | |
| #INV-0038 | Drift Media | $1,600.00 | Draft | 2025-12-22 |
<!-- Bulk action bar (shown when rows are selected) -->
<div style="display:flex;align-items:center;gap:12px;padding:10px 16px;
background:var(--color-brand-primary-bg);
border:1px solid var(--color-brand-primary-light);">
<span style="font-weight:700;color:var(--color-brand-primary);">2 selected</span>
<div style="margin-left:auto;display:flex;gap:8px;">
<button class="btn btn-sm btn-ghost">Export</button>
<button class="btn btn-sm btn-danger">Delete</button>
</div>
</div>
<!-- Checkbox column header (indeterminate = partial selection) -->
<th><input type="checkbox" class="checkbox-input" id="select-all"></th>
<!-- Selected row -->
<tr class="row-selected">
<td><input type="checkbox" class="checkbox-input" checked></td>
...
</tr>
Form Layout
Grid-based layouts for arranging fields. Single-column for simple flows; two- or three-column grids for dense settings panels. Always group related fields together.
<div class="form-block">
<div class="form-block-header">
<div class="form-block-title">Sign in</div>
<div class="form-block-desc">Enter your credentials.</div>
</div>
<div class="form-block-body">
<div class="field">
<label class="field-label">Email <span class="req">*</span></label>
<input class="input" type="email" placeholder="[email protected]">
</div>
<div class="field">
<label class="field-label">Password <span class="req">*</span></label>
<input class="input" type="password">
<span class="field-hint">Minimum 8 characters.</span>
</div>
</div>
<div class="form-block-footer">
<button class="btn btn-md btn-primary">Sign in</button>
</div>
</div>
<div class="form-block-body">
<!-- Equal two-column row -->
<div class="form-row form-row-2">
<div class="field"> ... </div>
<div class="field"> ... </div>
</div>
<!-- Unequal: 2fr 1fr 1fr (address + city + postcode) -->
<div class="form-row"
style="grid-template-columns:2fr 1fr 1fr;">
<div class="field"> ... </div>
<div class="field"> ... </div>
<div class="field"> ... </div>
</div>
</div>
<!-- Footer: cancel left, save right -->
<div class="form-block-footer">
<button class="btn btn-md btn-ghost">Cancel</button>
<div class="form-footer-right">
<button class="btn btn-md btn-primary">Save changes</button>
</div>
</div>
<!-- Each section is its own form-block -->
<div class="form-block"> ... </div>
<div class="form-block"> ... </div>
<!-- Danger zone: tinted header + coloured border -->
<div class="form-block"
style="border-color:var(--color-error-light);">
<div class="form-block-header"
style="background:var(--color-error-bg);
border-bottom-color:var(--color-error-light);">
<div class="form-block-title"
style="color:var(--color-error-dark);">Danger zone</div>
</div>
...
</div>
<!-- Sticky save footer -->
<div style="position:sticky;bottom:0;
background:var(--color-neutral-0);
border:1px solid var(--color-neutral-200);
border-radius:var(--radius-lg);
padding:14px 20px;
box-shadow:var(--shadow-md);">
<button class="btn btn-md btn-ghost">Discard</button>
<button class="btn btn-md btn-primary">Save all changes</button>
</div>