Replace BEM class API with bare elements, ARIA roles, and data attributes
Every component's public styling contract moves from `.ad-*` BEM classes to
semantic HTML: bare elements where the tag IS the component (button, input,
select, dialog, fieldset/form, table), real ARIA roles doing double duty as
selectors (role=tablist/tab/tabpanel, role=status, role=tooltip, role=switch),
and data-* attributes for variants/state (data-variant, data-size, data-tone,
data-elevated, data-dashed, data-block, data-fixed). A small, deliberate set
of sanctioned single classes remains for genuinely generic containers with no
element/attribute to hang a selector on (.icon-button, .badge, .tag, .card,
.avatar, .switch, .field, .btn for anchor-as-button, plus a few doc-only
layout helpers). BEM double-punctuation (__/--) is gone entirely.
Notable upgrades beyond renaming:
- Dialog is now the native <dialog> element (::backdrop, browser-centered),
replacing the old three-div overlay/demo stack.
- Checkbox/Radio use native controls tinted with accent-color instead of a
hand-drawn SVG-checkmark box.
- Tabs follows the real WAI-ARIA tabs pattern with zero classes.
- Card-of-rows is a real <ul class="card"> of bare <li> rows; clickable rows
wrap their content in a real <button>/<a> instead of a --clickable
modifier on a non-interactive element.
Updates all 16 component pages (demo markup + API reference tables), both
patterns (loop, archive), the affected foundations pages, and index/rationale
/install prose and worked examples. The component API tables themselves now
render as bare <table>/<code> instead of .ad-table/.ad-token-name. Site
chrome (sidebar/header/footer classes, layouts) is untouched except for the
two call sites that were consuming Button's old classes.
Verified with `bundle exec rake build` and Playwright screenshots of every
component/pattern/foundation page in light and dark against the prior
screenshots — visually unchanged except two flagged, expected deltas: the
static (non-modal) Dialog demo has no ::backdrop dimming since that
pseudo-element only exists for a dialog shown via .showModal(), and
Checkbox/Radio render the browser's native check/dot instead of the old
custom flat shape.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013jsk87pdEh7a2BNyqk5vJQ
diff --git a/_components/badge.md b/_components/badge.md
index b54d7b7..a06dadd 100644
--- a/_components/badge.md
+++ b/_components/badge.md
@@ -8,21 +8,25 @@ summary: >-
Labels are catalog-style: short, mono, uppercase — `SAVED`, `RECOMMENDED`,
`OVERDUE`. Never a click handler on one; a Badge is a label, not a button.
+A `<span>` is too generic to style bare — it shows up all over running prose
+for unrelated reasons — so Badge keeps one class. Tone is an attribute, not a
+modifier class.
+
## Tones
<figure class="example">
<figcaption>Tones</figcaption>
<div>
-<span class="ad-badge">Unassigned</span>
-<span class="ad-badge ad-badge--accent">Draft</span>
-<span class="ad-badge ad-badge--success">Saved</span>
-<span class="ad-badge ad-badge--danger">Overdue</span>
+<span class="badge">Unassigned</span>
+<span class="badge" data-tone="accent">Draft</span>
+<span class="badge" data-tone="success">Saved</span>
+<span class="badge" data-tone="danger">Overdue</span>
</div>
<details open><summary>Markup</summary>
-<pre><code><span class="ad-badge">Unassigned</span>
-<span class="ad-badge ad-badge--accent">Draft</span>
-<span class="ad-badge ad-badge--success">Saved</span>
-<span class="ad-badge ad-badge--danger">Overdue</span></code></pre>
+<pre><code><span class="badge">Unassigned</span>
+<span class="badge" data-tone="accent">Draft</span>
+<span class="badge" data-tone="success">Saved</span>
+<span class="badge" data-tone="danger">Overdue</span></code></pre>
</details>
</figure>
@@ -31,17 +35,17 @@ Labels are catalog-style: short, mono, uppercase — `SAVED`, `RECOMMENDED`,
<figure class="example">
<figcaption>Badge set</figcaption>
<div>
-<div class="ad-badge-set">
- <span class="ad-badge ad-badge--accent">Recommended</span>
- <span class="ad-badge">Every 2 weeks</span>
- <span class="ad-badge ad-badge--danger">3d overdue</span>
+<div class="badge-set">
+ <span class="badge" data-tone="accent">Recommended</span>
+ <span class="badge">Every 2 weeks</span>
+ <span class="badge" data-tone="danger">3d overdue</span>
</div>
</div>
<details><summary>Markup</summary>
-<pre><code><div class="ad-badge-set">
- <span class="ad-badge ad-badge--accent">Recommended</span>
- <span class="ad-badge">Every 2 weeks</span>
- <span class="ad-badge ad-badge--danger">3d overdue</span>
+<pre><code><div class="badge-set">
+ <span class="badge" data-tone="accent">Recommended</span>
+ <span class="badge">Every 2 weeks</span>
+ <span class="badge" data-tone="danger">3d overdue</span>
</div></code></pre>
</details>
</figure>
@@ -56,11 +60,10 @@ Labels are catalog-style: short, mono, uppercase — `SAVED`, `RECOMMENDED`,
## API
-<div class="ad-table-wrap">
- <table class="ad-table ad-table--compact">
- <thead><tr><th scope="col">Prop</th><th scope="col">Type</th><th scope="col">Notes</th></tr></thead>
- <tbody>
- <tr><td class="ad-token-name">tone</td><td class="ad-table__code">neutral | accent | success | danger</td><td>Default <code>neutral</code>.</td></tr>
- </tbody>
- </table>
-</div>
+<table>
+ <thead><tr><th scope="col">Element / attribute</th><th scope="col">Values</th><th scope="col">Notes</th></tr></thead>
+ <tbody>
+ <tr><td><code>span.badge</code></td><td></td><td>Required base.</td></tr>
+ <tr><td><code>data-tone</code></td><td><code>accent | success | danger</code></td><td>Omit for the default neutral tone.</td></tr>
+ </tbody>
+</table>
diff --git a/_components/button.md b/_components/button.md
index 03f421e..e249fe4 100644
--- a/_components/button.md
+++ b/_components/button.md
@@ -16,18 +16,18 @@ is destructive and irreversible only.
<figure class="example">
<figcaption>Variants</figcaption>
<div>
-<button type="button" class="ad-btn">Cancel</button>
-<button type="button" class="ad-btn ad-btn--primary">Save</button>
-<button type="button" class="ad-btn ad-btn--dark">Finish setup</button>
-<button type="button" class="ad-btn ad-btn--ghost">Dismiss</button>
-<button type="button" class="ad-btn ad-btn--danger">Delete</button>
+<button type="button">Cancel</button>
+<button type="button" data-variant="primary">Save</button>
+<button type="button" data-variant="dark">Finish setup</button>
+<button type="button" data-variant="ghost">Dismiss</button>
+<button type="button" data-variant="danger">Delete</button>
</div>
<details open><summary>Markup</summary>
-<pre><code><button type="button" class="ad-btn">Cancel</button>
-<button type="button" class="ad-btn ad-btn--primary">Save</button>
-<button type="button" class="ad-btn ad-btn--dark">Finish setup</button>
-<button type="button" class="ad-btn ad-btn--ghost">Dismiss</button>
-<button type="button" class="ad-btn ad-btn--danger">Delete</button></code></pre>
+<pre><code><button type="button">Cancel</button>
+<button type="button" data-variant="primary">Save</button>
+<button type="button" data-variant="dark">Finish setup</button>
+<button type="button" data-variant="ghost">Dismiss</button>
+<button type="button" data-variant="danger">Delete</button></code></pre>
</details>
</figure>
@@ -39,14 +39,14 @@ grow a step of its own.
<figure class="example">
<figcaption>Sizes</figcaption>
<div>
-<button type="button" class="ad-btn ad-btn--primary ad-btn--sm">Small</button>
-<button type="button" class="ad-btn ad-btn--primary">Medium</button>
-<button type="button" class="ad-btn ad-btn--primary ad-btn--lg">Large</button>
+<button type="button" data-variant="primary" data-size="sm">Small</button>
+<button type="button" data-variant="primary">Medium</button>
+<button type="button" data-variant="primary" data-size="lg">Large</button>
</div>
<details><summary>Markup</summary>
-<pre><code><button type="button" class="ad-btn ad-btn--primary ad-btn--sm">Small</button>
-<button type="button" class="ad-btn ad-btn--primary">Medium</button>
-<button type="button" class="ad-btn ad-btn--primary ad-btn--lg">Large</button></code></pre>
+<pre><code><button type="button" data-variant="primary" data-size="sm">Small</button>
+<button type="button" data-variant="primary">Medium</button>
+<button type="button" data-variant="primary" data-size="lg">Large</button></code></pre>
</details>
</figure>
@@ -58,12 +58,12 @@ disabled primary still reads as the primary.
<figure class="example">
<figcaption>States</figcaption>
<div>
-<button type="button" class="ad-btn ad-btn--primary" disabled="">Disabled</button>
-<button type="button" class="ad-btn" aria-disabled="true">Not yet available</button>
+<button type="button" data-variant="primary" disabled="">Disabled</button>
+<button type="button" aria-disabled="true">Not yet available</button>
</div>
<details><summary>Markup</summary>
-<pre><code><button type="button" class="ad-btn ad-btn--primary" disabled="">Disabled</button>
-<button type="button" class="ad-btn" aria-disabled="true">Not yet available</button></code></pre>
+<pre><code><button type="button" data-variant="primary" disabled="">Disabled</button>
+<button type="button" aria-disabled="true">Not yet available</button></code></pre>
</details>
</figure>
@@ -86,13 +86,13 @@ button itself.
<figure class="example">
<figcaption>Icon</figcaption>
<div>
-<button type="button" class="ad-btn ad-btn--primary">
+<button type="button" data-variant="primary">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.9" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M12 5v14M5 12h14" /></svg>
New
</button>
</div>
<details><summary>Markup</summary>
-<pre><code><button type="button" class="ad-btn ad-btn--primary">
+<pre><code><button type="button" data-variant="primary">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.9" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M12 5v14M5 12h14" /></svg>
New
</button></code></pre>
@@ -101,20 +101,23 @@ button itself.
## Groups
+`[role="group"]` is already the correct ARIA role for a toolbar of related
+buttons, so it doubles as the styling hook — no wrapper class.
+
<figure class="example">
<figcaption>Button group</figcaption>
<div>
-<div class="ad-btn-group" role="group" aria-label="View">
- <button type="button" class="ad-btn ad-btn--sm">List</button>
- <button type="button" class="ad-btn ad-btn--sm">Board</button>
- <button type="button" class="ad-btn ad-btn--sm">Calendar</button>
+<div role="group" aria-label="View">
+ <button type="button" data-size="sm">List</button>
+ <button type="button" data-size="sm">Board</button>
+ <button type="button" data-size="sm">Calendar</button>
</div>
</div>
<details><summary>Markup</summary>
-<pre><code><div class="ad-btn-group" role="group" aria-label="View">
- <button type="button" class="ad-btn ad-btn--sm">List</button>
- <button type="button" class="ad-btn ad-btn--sm">Board</button>
- <button type="button" class="ad-btn ad-btn--sm">Calendar</button>
+<pre><code><div role="group" aria-label="View">
+ <button type="button" data-size="sm">List</button>
+ <button type="button" data-size="sm">Board</button>
+ <button type="button" data-size="sm">Calendar</button>
</div></code></pre>
</details>
</figure>
@@ -124,52 +127,43 @@ button itself.
<figure class="example">
<figcaption>Block</figcaption>
<div data-layout="stack">
-<button type="button" class="ad-btn ad-btn--primary ad-btn--block">Take a photo</button>
+<button type="button" data-variant="primary" data-block>Take a photo</button>
</div>
<details><summary>Markup</summary>
-<pre><code><button type="button" class="ad-btn ad-btn--primary ad-btn--block">Take a photo</button></code></pre>
+<pre><code><button type="button" data-variant="primary" data-block>Take a photo</button></code></pre>
</details>
</figure>
## Links that act like buttons
-An `<a>` with `.ad-btn` is fine when the thing it does is navigate. A
-`<button>` is required when it changes something.
+An `<a>` with `.btn` is fine when the thing it does is navigate. A `<button>`
+is required when it changes something. `.btn` exists only because a bare `a`
+selector would catch every ordinary prose link on the site — it is the one
+class Button needs, and only anchors reach for it.
<figure class="example">
<figcaption>Anchor as button</figcaption>
<div>
-<a class="ad-btn ad-btn--primary" href="/arbitrary-definitions-design-system/components/icon-button/">Next: IconButton</a>
-<a class="ad-btn ad-btn--ghost" href="/arbitrary-definitions-design-system/foundations/color/">Back to color</a>
+<a class="btn" data-variant="primary" href="/arbitrary-definitions-design-system/components/icon-button/">Next: IconButton</a>
+<a class="btn" data-variant="ghost" href="/arbitrary-definitions-design-system/foundations/color/">Back to color</a>
</div>
<details><summary>Markup</summary>
-<pre><code><a class="ad-btn ad-btn--primary" href="/arbitrary-definitions-design-system/components/icon-button/">Next: IconButton</a>
-<a class="ad-btn ad-btn--ghost" href="/arbitrary-definitions-design-system/foundations/color/">Back to color</a></code></pre>
+<pre><code><a class="btn" data-variant="primary" href="/arbitrary-definitions-design-system/components/icon-button/">Next: IconButton</a>
+<a class="btn" data-variant="ghost" href="/arbitrary-definitions-design-system/foundations/color/">Back to color</a></code></pre>
</details>
</figure>
## API
-<div class="ad-table-wrap">
- <table class="ad-table ad-table--compact">
- <thead><tr><th scope="col">Prop</th><th scope="col">Type</th><th scope="col">Notes</th></tr></thead>
- <tbody>
- <tr><td class="ad-token-name">variant</td><td class="ad-table__code">default | primary | dark | ghost | danger</td><td>Default <code>default</code>.</td></tr>
- <tr><td class="ad-token-name">size</td><td class="ad-table__code">sm | md | lg</td><td>Padding only.</td></tr>
- <tr><td class="ad-token-name">icon</td><td class="ad-table__code">node</td><td>Rendered before the label.</td></tr>
- <tr><td class="ad-token-name">disabled</td><td class="ad-table__code">boolean</td><td>Removes the button from the tab order.</td></tr>
- </tbody>
- </table>
-</div>
-
-<div class="ad-table-wrap">
- <table class="ad-table ad-table--compact">
- <thead><tr><th scope="col">Class</th><th scope="col">Effect</th></tr></thead>
- <tbody>
- <tr><td class="ad-token-name">.ad-btn</td><td>Required base. Sets geometry, focus, and transitions.</td></tr>
- <tr><td class="ad-token-name">.ad-btn--primary / --dark / --ghost / --danger</td><td>Variant fills.</td></tr>
- <tr><td class="ad-token-name">.ad-btn--sm / --lg</td><td>Height, padding, and font size in one step.</td></tr>
- <tr><td class="ad-token-name">.ad-btn--block</td><td>Full width of its container.</td></tr>
- </tbody>
- </table>
-</div>
+<table>
+ <thead><tr><th scope="col">Element / attribute</th><th scope="col">Values</th><th scope="col">Notes</th></tr></thead>
+ <tbody>
+ <tr><td><code>button</code></td><td></td><td>Required base. A bare <code><button></code> is styled directly — no class. Use <code><a class="btn"></code> instead when the control navigates.</td></tr>
+ <tr><td><code>data-variant</code></td><td><code>primary | dark | ghost | danger</code></td><td>Omit for the default, bordered look.</td></tr>
+ <tr><td><code>data-size</code></td><td><code>sm | lg</code></td><td>Omit for the default (medium) size. Padding only.</td></tr>
+ <tr><td><code>data-block</code></td><td>boolean</td><td>Full width of its container.</td></tr>
+ <tr><td><code>disabled</code></td><td>boolean</td><td>Removes the button from the tab order.</td></tr>
+ <tr><td><code>aria-disabled</code></td><td><code>"true"</code></td><td>Looks disabled, stays focusable.</td></tr>
+ <tr><td><code>[role="group"]</code></td><td></td><td>Wrap adjacent buttons in a group to join their borders and radii.</td></tr>
+ </tbody>
+</table>
diff --git a/_components/card-row.md b/_components/card-row.md
index e59e0b8..3a6f34f 100644
--- a/_components/card-row.md
+++ b/_components/card-row.md
@@ -5,44 +5,50 @@ summary: >-
settings rows. The first row's top border removes itself automatically.
---
+CardRow is a bare `<li>` inside a `<ul class="card">` — the divider styling
+comes entirely from the `.card > li` descendant selector, so there is no
+class of CardRow's own.
+
## Basic
<figure class="example">
<figcaption>Rows</figcaption>
<div>
-<div class="ad-card" style="max-width: 22rem;">
- <div class="ad-card-row">Renew passport</div>
- <div class="ad-card-row">Dentist checkup</div>
- <div class="ad-card-row">Rotate mattress</div>
-</div>
+<ul class="card" style="max-width: 22rem;">
+ <li>Renew passport</li>
+ <li>Dentist checkup</li>
+ <li>Rotate mattress</li>
+</ul>
</div>
<details open><summary>Markup</summary>
-<pre><code><div class="ad-card" style="max-width: 22rem;">
- <div class="ad-card-row">Renew passport</div>
- <div class="ad-card-row">Dentist checkup</div>
- <div class="ad-card-row">Rotate mattress</div>
-</div></code></pre>
+<pre><code><ul class="card" style="max-width: 22rem;">
+ <li>Renew passport</li>
+ <li>Dentist checkup</li>
+ <li>Rotate mattress</li>
+</ul></code></pre>
</details>
</figure>
## Clickable
-`--clickable` adds a hover fill and a pointer cursor. Use a real `<button>`
-element when the row navigates or opens something.
+Wrap the row's content in a real `<button>` or `<a>` when the row navigates
+or opens something — an accessible clickable row, not a `<li>` with a click
+handler and no keyboard support. It fills the row edge-to-edge and picks up
+a hover fill on its own; no `--clickable` modifier to remember.
<figure class="example">
<figcaption>Clickable rows</figcaption>
<div>
-<div class="ad-card" style="max-width: 22rem;">
- <button type="button" class="ad-card-row ad-card-row--clickable">Tax return 2025.pdf</button>
- <button type="button" class="ad-card-row ad-card-row--clickable">Warranty card.pdf</button>
-</div>
+<ul class="card" style="max-width: 22rem;">
+ <li><button type="button">Tax return 2025.pdf</button></li>
+ <li><button type="button">Warranty card.pdf</button></li>
+</ul>
</div>
<details><summary>Markup</summary>
-<pre><code><div class="ad-card" style="max-width: 22rem;">
- <button type="button" class="ad-card-row ad-card-row--clickable">Tax return 2025.pdf</button>
- <button type="button" class="ad-card-row ad-card-row--clickable">Warranty card.pdf</button>
-</div></code></pre>
+<pre><code><ul class="card" style="max-width: 22rem;">
+ <li><button type="button">Tax return 2025.pdf</button></li>
+ <li><button type="button">Warranty card.pdf</button></li>
+</ul></code></pre>
</details>
</figure>
@@ -55,9 +61,9 @@ patterns for it in context).
<figure class="example">
<figcaption>Leading icon, trailing meta</figcaption>
<div>
-<div class="ad-card" style="max-width: 24rem;">
- <div class="ad-card-row">
- <span class="ad-avatar ad-avatar--square">
+<ul class="card" style="max-width: 24rem;">
+ <li>
+ <span class="avatar" data-square>
<svg width="17" height="17" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M21 8 12 3 3 8v8l9 5 9-5V8Z" /><path d="M3 8l9 5 9-5M12 13v8" /></svg>
</span>
<div style="flex:1; min-width:0;">
@@ -65,13 +71,13 @@ patterns for it in context).
<div class="type-label" style="margin-top: 2px;">PDF · 2.1MB</div>
</div>
<span class="type-mono" style="color: var(--ad-color-text-tertiary);">2h</span>
- </div>
-</div>
+ </li>
+</ul>
</div>
<details><summary>Markup</summary>
-<pre><code><div class="ad-card" style="max-width: 24rem;">
- <div class="ad-card-row">
- <span class="ad-avatar ad-avatar--square">
+<pre><code><ul class="card" style="max-width: 24rem;">
+ <li>
+ <span class="avatar" data-square>
<svg width="17" height="17" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M21 8 12 3 3 8v8l9 5 9-5V8Z" /><path d="M3 8l9 5 9-5M12 13v8" /></svg>
</span>
<div style="flex:1; min-width:0;">
@@ -79,18 +85,17 @@ patterns for it in context).
<div class="type-label" style="margin-top: 2px;">PDF · 2.1MB</div>
</div>
<span class="type-mono" style="color: var(--ad-color-text-tertiary);">2h</span>
- </div>
-</div></code></pre>
+ </li>
+</ul></code></pre>
</details>
</figure>
## API
-<div class="ad-table-wrap">
- <table class="ad-table ad-table--compact">
- <thead><tr><th scope="col">Prop</th><th scope="col">Type</th><th scope="col">Notes</th></tr></thead>
- <tbody>
- <tr><td class="ad-token-name">onClick</td><td class="ad-table__code">function</td><td>Reach for <code>.ad-card-row--clickable</code> and a <code><button></code>.</td></tr>
- </tbody>
- </table>
-</div>
+<table>
+ <thead><tr><th scope="col">Element / attribute</th><th scope="col">Values</th><th scope="col">Notes</th></tr></thead>
+ <tbody>
+ <tr><td><code>li</code></td><td></td><td>Required — a direct child of <code>ul.card</code>. No class of its own.</td></tr>
+ <tr><td><code>li > button</code> / <code>li > a</code></td><td></td><td>Wrap the row's content to make it clickable, in place of an <code>onClick</code> prop.</td></tr>
+ </tbody>
+</table>
diff --git a/_components/card.md b/_components/card.md
index d4a0b16..55016b2 100644
--- a/_components/card.md
+++ b/_components/card.md
@@ -5,50 +5,55 @@ summary: >-
the one focused card in a view — a dialog, the primary capture card.
---
-## Flat vs. elevated
+A `<div>`/`<article>` is too generic to style bare, so Card keeps one
+sanctioned class. `[data-elevated]` is for the single most-elevated element on
+screen — never a whole grid of cards at once.
-Flat is the default everywhere. `--elevated` is for the single most-elevated
-element on screen — never a whole grid of cards at once.
+## Flat vs. elevated
<figure class="example">
<figcaption>Flat and elevated</figcaption>
<div data-surface="sunken">
-<div class="ad-card" style="width: 14rem; padding: var(--ad-space-s);">Flat — the default.</div>
-<div class="ad-card ad-card--elevated" style="width: 14rem; padding: var(--ad-space-s);">Elevated — one per view.</div>
+<div class="card" style="width: 14rem; padding: var(--ad-space-s);">Flat — the default.</div>
+<div class="card" data-elevated style="width: 14rem; padding: var(--ad-space-s);">Elevated — one per view.</div>
</div>
<details><summary>Markup</summary>
-<pre><code><div class="ad-card" style="width: 14rem; padding: var(--ad-space-s);">Flat — the default.</div>
-<div class="ad-card ad-card--elevated" style="width: 14rem; padding: var(--ad-space-s);">Elevated — one per view.</div></code></pre>
+<pre><code><div class="card" style="width: 14rem; padding: var(--ad-space-s);">Flat — the default.</div>
+<div class="card" data-elevated style="width: 14rem; padding: var(--ad-space-s);">Elevated — one per view.</div></code></pre>
</details>
</figure>
## As a content tile
+Used standalone (not as a row-list), `.card` is fine on its own — `.card-body`
+and `.card-footer` are two more sanctioned classes, for the one shape inside
+it that has no element of its own to hang a selector on.
+
<figure class="example">
<figcaption>Content tile</figcaption>
<div>
-<article class="ad-card" style="max-width: 18rem;">
- <div class="ad-card__body">
- <p class="type-label">Recommended</p>
- <h3 class="ad-card__title">Rename the caution color</h3>
- <p class="ad-card__text">Three people have called it "yellow" in review this month.</p>
+<article class="card" style="max-width: 18rem;">
+ <div class="card-body">
+ <p class="type-label" style="margin:0;">Recommended</p>
+ <h3 style="margin:0; font: 600 var(--ad-step-0)/1.3 var(--ad-font-sans);">Rename the caution color</h3>
+ <p style="margin:0; color: var(--ad-color-text-secondary); font-size: var(--ad-step--1);">Three people have called it "yellow" in review this month.</p>
</div>
- <div class="ad-card__footer">
- <button type="button" class="ad-btn ad-btn--primary ad-btn--sm">Approve</button>
- <button type="button" class="ad-btn ad-btn--ghost ad-btn--sm">Dismiss</button>
+ <div class="card-footer">
+ <button type="button" data-variant="primary" data-size="sm">Approve</button>
+ <button type="button" data-variant="ghost" data-size="sm">Dismiss</button>
</div>
</article>
</div>
<details><summary>Markup</summary>
-<pre><code><article class="ad-card" style="max-width: 18rem;">
- <div class="ad-card__body">
- <p class="type-label">Recommended</p>
- <h3 class="ad-card__title">Rename the caution color</h3>
- <p class="ad-card__text">Three people have called it "yellow" in review this month.</p>
+<pre><code><article class="card" style="max-width: 18rem;">
+ <div class="card-body">
+ <p class="type-label" style="margin:0;">Recommended</p>
+ <h3 style="margin:0; font: 600 var(--ad-step-0)/1.3 var(--ad-font-sans);">Rename the caution color</h3>
+ <p style="margin:0; color: var(--ad-color-text-secondary); font-size: var(--ad-step--1);">Three people have called it "yellow" in review this month.</p>
</div>
- <div class="ad-card__footer">
- <button type="button" class="ad-btn ad-btn--primary ad-btn--sm">Approve</button>
- <button type="button" class="ad-btn ad-btn--ghost ad-btn--sm">Dismiss</button>
+ <div class="card-footer">
+ <button type="button" data-variant="primary" data-size="sm">Approve</button>
+ <button type="button" data-variant="ghost" data-size="sm">Dismiss</button>
</div>
</article></code></pre>
</details>
@@ -58,33 +63,35 @@ element on screen — never a whole grid of cards at once.
Card's most common job in this system is holding a list of
[CardRow](<%= relative_url('/components/card-row/') %>)s: entry lists, task
-lists, settings rows.
+lists, settings rows. In that shape Card is a real `<ul class="card">` and
+CardRow is a bare `<li>` — the divider between rows comes from the `.card >
+li` descendant selector, no second class needed.
<figure class="example">
<figcaption>Card of rows</figcaption>
<div>
-<div class="ad-card" style="max-width: 22rem;">
- <div class="ad-card-row">Dishwasher manual</div>
- <div class="ad-card-row">Sofa receipt</div>
- <div class="ad-card-row">Passport scan</div>
-</div>
+<ul class="card" style="max-width: 22rem;">
+ <li>Dishwasher manual</li>
+ <li>Sofa receipt</li>
+ <li>Passport scan</li>
+</ul>
</div>
<details><summary>Markup</summary>
-<pre><code><div class="ad-card" style="max-width: 22rem;">
- <div class="ad-card-row">Dishwasher manual</div>
- <div class="ad-card-row">Sofa receipt</div>
- <div class="ad-card-row">Passport scan</div>
-</div></code></pre>
+<pre><code><ul class="card" style="max-width: 22rem;">
+ <li>Dishwasher manual</li>
+ <li>Sofa receipt</li>
+ <li>Passport scan</li>
+</ul></code></pre>
</details>
</figure>
## API
-<div class="ad-table-wrap">
- <table class="ad-table ad-table--compact">
- <thead><tr><th scope="col">Prop</th><th scope="col">Type</th><th scope="col">Notes</th></tr></thead>
- <tbody>
- <tr><td class="ad-token-name">elevated</td><td class="ad-table__code">boolean</td><td>Applies <code>--ad-shadow-float</code>. One per view.</td></tr>
- </tbody>
- </table>
-</div>
+<table>
+ <thead><tr><th scope="col">Element / attribute</th><th scope="col">Values</th><th scope="col">Notes</th></tr></thead>
+ <tbody>
+ <tr><td><code>.card</code></td><td></td><td>Required base, on a <code><div></code>/<code><article></code> for a standalone tile or a <code><ul></code> for a list of rows.</td></tr>
+ <tr><td><code>data-elevated</code></td><td>boolean</td><td>Applies <code>--ad-shadow-float</code>. One per view.</td></tr>
+ <tr><td><code>> li</code></td><td></td><td>A CardRow, when Card is a <code><ul></code>. See <a href="<%= relative_url('/components/card-row/') %>">CardRow</a>.</td></tr>
+ </tbody>
+</table>
diff --git a/_components/checkbox.md b/_components/checkbox.md
index c39b52b..8b2dd9d 100644
--- a/_components/checkbox.md
+++ b/_components/checkbox.md
@@ -1,37 +1,36 @@
---
title: Checkbox
-summary: Flat checkbox, fills with the accent when checked. No bounce.
+summary: Native checkbox, tinted with the accent color. No hand-drawn box.
---
## Basic
+`input[type="checkbox"]` is the component — styled bare with `accent-color`
+rather than a custom box, so the browser draws its own check. This is a real
+reduction, not just a rename: it deletes the hand-drawn SVG-checkmark markup
+that a `.ad-choice__box` span used to need. The wrapping `<label>` is told
+apart from Field's label structurally — it `:has()` a checkbox as a direct
+child — so it needs no class either.
+
<figure class="example">
<figcaption>Checkbox</figcaption>
<div>
-<label class="ad-choice ad-choice--checkbox">
+<label>
<input type="checkbox" />
- <span class="ad-choice__box"></span>
Notify me
</label>
-<label class="ad-choice ad-choice--checkbox">
+<label>
<input type="checkbox" checked="" />
- <span class="ad-choice__box">
- <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M20 6 9 17l-5-5" /></svg>
- </span>
Ship on merge
</label>
</div>
<details open><summary>Markup</summary>
-<pre><code><label class="ad-choice ad-choice--checkbox">
+<pre><code><label>
<input type="checkbox" />
- <span class="ad-choice__box"></span>
Notify me
</label>
-<label class="ad-choice ad-choice--checkbox">
+<label>
<input type="checkbox" checked="" />
- <span class="ad-choice__box">
- <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M20 6 9 17l-5-5" /></svg>
- </span>
Ship on merge
</label></code></pre>
</details>
@@ -42,16 +41,14 @@ summary: Flat checkbox, fills with the accent when checked. No bounce.
<figure class="example">
<figcaption>Disabled</figcaption>
<div>
-<label class="ad-choice ad-choice--checkbox" aria-disabled="true">
+<label>
<input type="checkbox" disabled="" />
- <span class="ad-choice__box"></span>
Not available on this plan
</label>
</div>
<details><summary>Markup</summary>
-<pre><code><label class="ad-choice ad-choice--checkbox" aria-disabled="true">
+<pre><code><label>
<input type="checkbox" disabled="" />
- <span class="ad-choice__box"></span>
Not available on this plan
</label></code></pre>
</details>
@@ -59,34 +56,32 @@ summary: Flat checkbox, fills with the accent when checked. No bounce.
## In a fieldset
+`<fieldset>`/`<legend>` are styled bare too — they already mean this.
+
<figure class="example">
<figcaption>Fieldset</figcaption>
<div>
-<fieldset class="ad-fieldset">
+<fieldset>
<legend>Notify me about</legend>
- <label class="ad-choice ad-choice--checkbox">
+ <label>
<input type="checkbox" checked="" />
- <span class="ad-choice__box"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M20 6 9 17l-5-5" /></svg></span>
Overdue tasks
</label>
- <label class="ad-choice ad-choice--checkbox">
+ <label>
<input type="checkbox" />
- <span class="ad-choice__box"></span>
Weekly digest
</label>
</fieldset>
</div>
<details><summary>Markup</summary>
-<pre><code><fieldset class="ad-fieldset">
+<pre><code><fieldset>
<legend>Notify me about</legend>
- <label class="ad-choice ad-choice--checkbox">
+ <label>
<input type="checkbox" checked="" />
- <span class="ad-choice__box"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M20 6 9 17l-5-5" /></svg></span>
Overdue tasks
</label>
- <label class="ad-choice ad-choice--checkbox">
+ <label>
<input type="checkbox" />
- <span class="ad-choice__box"></span>
Weekly digest
</label>
</fieldset></code></pre>
@@ -95,13 +90,12 @@ summary: Flat checkbox, fills with the accent when checked. No bounce.
## API
-<div class="ad-table-wrap">
- <table class="ad-table ad-table--compact">
- <thead><tr><th scope="col">Prop</th><th scope="col">Type</th><th scope="col">Notes</th></tr></thead>
- <tbody>
- <tr><td class="ad-token-name">checked</td><td class="ad-table__code">boolean</td><td>Required.</td></tr>
- <tr><td class="ad-token-name">label</td><td class="ad-table__code">node</td><td></td></tr>
- <tr><td class="ad-token-name">disabled</td><td class="ad-table__code">boolean</td><td></td></tr>
- </tbody>
- </table>
-</div>
+<table>
+ <thead><tr><th scope="col">Element / attribute</th><th scope="col">Values</th><th scope="col">Notes</th></tr></thead>
+ <tbody>
+ <tr><td><code>input[type="checkbox"]</code></td><td></td><td>Required base — no class. Tinted with <code>accent-color</code>.</td></tr>
+ <tr><td><code>checked</code></td><td>boolean</td><td></td></tr>
+ <tr><td><code>disabled</code></td><td>boolean</td><td></td></tr>
+ <tr><td><code>label</code></td><td>text content</td><td>Wrap the input directly — <code>label:has(> input[type="checkbox"])</code> is what applies the row layout.</td></tr>
+ </tbody>
+</table>
diff --git a/_components/dialog.md b/_components/dialog.md
index 749bbe8..2b17ca8 100644
--- a/_components/dialog.md
+++ b/_components/dialog.md
@@ -5,41 +5,43 @@ summary: >-
between title, body, and footer.
---
-The example below is shown open, scoped to its own preview box rather than
-the real viewport-covering overlay, so it can sit safely inside a
-documentation page. Wire real open/close state — and a `position: fixed`
-overlay — in the consuming app; this is a pixel recreation for documentation,
-not a functioning modal.
+Dialog is the native `<dialog>` element: a `<header>` for the title, a
+`<footer>` for the button row, and everything else in between is the body —
+no wrapper classes at all.
+
+The example below is shown with a plain `open` attribute, scoped to its own
+preview box rather than the real viewport-covering overlay, so it renders
+without JavaScript and can sit safely inside a documentation page. It
+centers itself using the browser's own default `<dialog>` styling
+(`position: absolute; inset: 0; margin: auto`) against the preview box,
+which is why it needs that box to be `position: relative`. Call
+`.showModal()` in the consuming app for the real fixed-overlay behavior —
+a plain `open` attribute never paints a `::backdrop`, since that
+pseudo-element only exists for a dialog shown modally.
<figure class="example">
<figcaption>Dialog</figcaption>
<div data-layout="stack">
-<div class="ad-dialog-demo">
- <div class="ad-dialog__overlay">
- <div class="ad-dialog">
- <div class="ad-dialog__title">Delete series?</div>
- <div class="ad-dialog__body">This removes all of its task history. This cannot be undone.</div>
- <div class="ad-dialog__footer">
- <button type="button" class="ad-btn">Cancel</button>
- <button type="button" class="ad-btn ad-btn--danger">Delete</button>
- </div>
- </div>
- </div>
+<div class="dialog-demo">
+ <dialog open>
+ <header>Delete series?</header>
+ <p>This removes all of its task history. This cannot be undone.</p>
+ <footer>
+ <button type="button">Cancel</button>
+ <button type="button" data-variant="danger">Delete</button>
+ </footer>
+ </dialog>
</div>
</div>
<details open><summary>Markup</summary>
-<pre><code><div class="ad-dialog-demo">
- <div class="ad-dialog__overlay">
- <div class="ad-dialog">
- <div class="ad-dialog__title">Delete series?</div>
- <div class="ad-dialog__body">This removes all of its task history. This cannot be undone.</div>
- <div class="ad-dialog__footer">
- <button type="button" class="ad-btn">Cancel</button>
- <button type="button" class="ad-btn ad-btn--danger">Delete</button>
- </div>
- </div>
- </div>
-</div></code></pre>
+<pre><code><dialog open>
+ <header>Delete series?</header>
+ <p>This removes all of its task history. This cannot be undone.</p>
+ <footer>
+ <button type="button">Cancel</button>
+ <button type="button" data-variant="danger">Delete</button>
+ </footer>
+</dialog></code></pre>
</details>
</figure>
@@ -50,37 +52,30 @@ A body-only dialog for a notice rather than a decision.
<figure class="example">
<figcaption>No footer</figcaption>
<div data-layout="stack">
-<div class="ad-dialog-demo" style="min-height: 14rem;">
- <div class="ad-dialog__overlay">
- <div class="ad-dialog" style="width: 20rem;">
- <div class="ad-dialog__title">Backup complete</div>
- <div class="ad-dialog__body">412 files, 1.2GB, archived to the household drive.</div>
- </div>
- </div>
+<div class="dialog-demo" style="min-height: 14rem;">
+ <dialog open style="width: 20rem;">
+ <header>Backup complete</header>
+ <p>412 files, 1.2GB, archived to the household drive.</p>
+ </dialog>
</div>
</div>
<details><summary>Markup</summary>
-<pre><code><div class="ad-dialog-demo" style="min-height: 14rem;">
- <div class="ad-dialog__overlay">
- <div class="ad-dialog" style="width: 20rem;">
- <div class="ad-dialog__title">Backup complete</div>
- <div class="ad-dialog__body">412 files, 1.2GB, archived to the household drive.</div>
- </div>
- </div>
-</div></code></pre>
+<pre><code><dialog open style="width: 20rem;">
+ <header>Backup complete</header>
+ <p>412 files, 1.2GB, archived to the household drive.</p>
+</dialog></code></pre>
</details>
</figure>
## API
-<div class="ad-table-wrap">
- <table class="ad-table ad-table--compact">
- <thead><tr><th scope="col">Prop</th><th scope="col">Type</th><th scope="col">Notes</th></tr></thead>
- <tbody>
- <tr><td class="ad-token-name">open</td><td class="ad-table__code">boolean</td><td>Required.</td></tr>
- <tr><td class="ad-token-name">title</td><td class="ad-table__code">string</td><td>Optional — a body-only dialog omits the title bar.</td></tr>
- <tr><td class="ad-token-name">onClose</td><td class="ad-table__code">function</td><td>Fired on overlay click.</td></tr>
- <tr><td class="ad-token-name">footer</td><td class="ad-table__code">node</td><td>Usually a pair of Buttons — the least committing action first.</td></tr>
- </tbody>
- </table>
-</div>
+<table>
+ <thead><tr><th scope="col">Element / attribute</th><th scope="col">Values</th><th scope="col">Notes</th></tr></thead>
+ <tbody>
+ <tr><td><code>dialog</code></td><td></td><td>Required base. Call <code>.showModal()</code> to open it for real — a plain <code>open</code> attribute is for static, always-open documentation only.</td></tr>
+ <tr><td><code>dialog > header</code></td><td></td><td>Optional — a body-only dialog omits it.</td></tr>
+ <tr><td><code>dialog > footer</code></td><td></td><td>Optional. Usually a pair of Buttons — the least committing action first.</td></tr>
+ <tr><td><code>dialog::backdrop</code></td><td></td><td>The dimmed overlay. Only renders once the dialog is shown via <code>.showModal()</code>.</td></tr>
+ <tr><td><code>close</code> event</td><td></td><td>Fired on cancel — wire it, or a form's <code>method="dialog"</code>, instead of an <code>onClose</code> prop.</td></tr>
+ </tbody>
+</table>
diff --git a/_components/field.md b/_components/field.md
index 46d55fe..413b04b 100644
--- a/_components/field.md
+++ b/_components/field.md
@@ -7,21 +7,28 @@ summary: >-
## Basic
+Field is a `<label>` that wraps its caption text together with the control —
+association is native, no `for`/`id` pair to keep in sync. A `<label>` plays
+three different roles in this system (this wrapper, Checkbox/Radio's row,
+Switch's row), so it cannot be styled bare without one leaking into another;
+`.field` is the one of the three with no distinguishing attribute, so it is
+the one that keeps a class. Hint text is a native `<small>`.
+
<figure class="example">
<figcaption>Field</figcaption>
<div>
-<div class="ad-field" style="max-width: 20rem;">
- <label class="ad-field__label" for="field-name">Name</label>
- <input class="ad-input" id="field-name" type="text" placeholder="Call Mom" />
- <span class="ad-field__hint">First line becomes the display name.</span>
-</div>
+<label class="field" style="max-width: 20rem;">
+ Name
+ <input type="text" placeholder="Call Mom" />
+ <small>First line becomes the display name.</small>
+</label>
</div>
<details open><summary>Markup</summary>
-<pre><code><div class="ad-field" style="max-width: 20rem;">
- <label class="ad-field__label" for="field-name">Name</label>
- <input class="ad-input" id="field-name" type="text" placeholder="Call Mom" />
- <span class="ad-field__hint">First line becomes the display name.</span>
-</div></code></pre>
+<pre><code><label class="field" style="max-width: 20rem;">
+ Name
+ <input type="text" placeholder="Call Mom" />
+ <small>First line becomes the display name.</small>
+</label></code></pre>
</details>
</figure>
@@ -29,80 +36,82 @@ summary: >-
Field wraps [Input](<%= relative_url('/components/input/') %>),
[Select](<%= relative_url('/components/select/') %>), or anything else — it
-only owns the label and the hint line, never the control's own styling.
+only owns the caption and the hint line, never the control's own styling.
<figure class="example">
<figcaption>Field with a select</figcaption>
<div>
-<div class="ad-field" style="max-width: 20rem;">
- <label class="ad-field__label" for="field-unit">Unit</label>
- <select class="ad-select" id="field-unit">
+<label class="field" style="max-width: 20rem;">
+ Unit
+ <select>
<option>Day</option>
<option selected="">Week</option>
<option>Month</option>
<option>Year</option>
</select>
-</div>
+</label>
</div>
<details><summary>Markup</summary>
-<pre><code><div class="ad-field" style="max-width: 20rem;">
- <label class="ad-field__label" for="field-unit">Unit</label>
- <select class="ad-select" id="field-unit">
+<pre><code><label class="field" style="max-width: 20rem;">
+ Unit
+ <select>
<option>Day</option>
<option selected="">Week</option>
<option>Month</option>
<option>Year</option>
</select>
-</div></code></pre>
+</label></code></pre>
</details>
</figure>
## In a row
+`.form-row` is a sanctioned class for the one shape here with no element of
+its own — a row grouping two fields side by side.
+
<figure class="example">
<figcaption>Two fields in a row</figcaption>
<div>
-<div class="ad-form" style="max-width: none;">
- <div class="ad-form__row">
- <div class="ad-field">
- <label class="ad-field__label" for="field-every">Every</label>
- <input class="ad-input" id="field-every" type="number" placeholder="2" />
- </div>
- <div class="ad-field">
- <label class="ad-field__label" for="field-unit-2">Unit</label>
- <select class="ad-select" id="field-unit-2">
+<form style="max-width: none;">
+ <div class="form-row">
+ <label class="field">
+ Every
+ <input type="number" placeholder="2" />
+ </label>
+ <label class="field">
+ Unit
+ <select>
<option>Day</option><option selected="">Week</option><option>Month</option>
</select>
- </div>
+ </label>
</div>
-</div>
+</form>
</div>
<details><summary>Markup</summary>
-<pre><code><div class="ad-form" style="max-width: none;">
- <div class="ad-form__row">
- <div class="ad-field">
- <label class="ad-field__label" for="field-every">Every</label>
- <input class="ad-input" id="field-every" type="number" placeholder="2" />
- </div>
- <div class="ad-field">
- <label class="ad-field__label" for="field-unit-2">Unit</label>
- <select class="ad-select" id="field-unit-2">
+<pre><code><form style="max-width: none;">
+ <div class="form-row">
+ <label class="field">
+ Every
+ <input type="number" placeholder="2" />
+ </label>
+ <label class="field">
+ Unit
+ <select>
<option>Day</option><option selected="">Week</option><option>Month</option>
</select>
- </div>
+ </label>
</div>
-</div></code></pre>
+</form></code></pre>
</details>
</figure>
## API
-<div class="ad-table-wrap">
- <table class="ad-table ad-table--compact">
- <thead><tr><th scope="col">Prop</th><th scope="col">Type</th><th scope="col">Notes</th></tr></thead>
- <tbody>
- <tr><td class="ad-token-name">label</td><td class="ad-table__code">string</td><td>Mono, uppercase, tracked.</td></tr>
- <tr><td class="ad-token-name">hint</td><td class="ad-table__code">string</td><td>Renders below the control.</td></tr>
- </tbody>
- </table>
-</div>
+<table>
+ <thead><tr><th scope="col">Element / attribute</th><th scope="col">Values</th><th scope="col">Notes</th></tr></thead>
+ <tbody>
+ <tr><td><code>label.field</code></td><td></td><td>Required base. Wraps the caption text and the control together.</td></tr>
+ <tr><td><code>> small</code></td><td>string</td><td>The hint. Renders below the control.</td></tr>
+ <tr><td><code>> small[role="alert"]</code></td><td>string</td><td>An error instead of a hint — see <a href="<%= relative_url('/components/input/') %>">Input</a>.</td></tr>
+ </tbody>
+</table>
diff --git a/_components/icon-button.md b/_components/icon-button.md
index 7bcbfa6..1e7e9bf 100644
--- a/_components/icon-button.md
+++ b/_components/icon-button.md
@@ -12,27 +12,32 @@ an icon button sitting alone rather than in a toolbar. `accent` is a soft
accent tint, for a toggled or active state — the segmented-tabs pattern this
system does not otherwise reach for.
+A square icon-only button is still a `<button>`, but there is no attribute
+that tells it apart from a plain [Button](<%= relative_url('/components/button/') %>)
+sitting alone — `variant`/`size` mean different things for each — so
+`.icon-button` is the one class this component needs.
+
<figure class="example">
<figcaption>Variants</figcaption>
<div>
-<button type="button" class="ad-icon-btn" aria-label="Delete">
+<button type="button" class="icon-button" aria-label="Delete">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M4 7h16M9 7V4h6v3M6 7l1 13h10l1-13" /></svg>
</button>
-<button type="button" class="ad-icon-btn ad-icon-btn--outline" aria-label="Settings">
+<button type="button" class="icon-button" data-variant="outline" aria-label="Settings">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="12" cy="12" r="3" /><path d="M12 2.5v3M12 18.5v3M3.6 7.2l2.6 1.5M17.8 15.3l2.6 1.5M3.6 16.8l2.6-1.5M17.8 8.7l2.6-1.5" /></svg>
</button>
-<button type="button" class="ad-icon-btn ad-icon-btn--accent" aria-label="Filter (active)">
+<button type="button" class="icon-button" data-variant="accent" aria-label="Filter (active)">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M4 5h16l-6 8v5l-4 2v-7z" /></svg>
</button>
</div>
<details open><summary>Markup</summary>
-<pre><code><button type="button" class="ad-icon-btn" aria-label="Delete">
+<pre><code><button type="button" class="icon-button" aria-label="Delete">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M4 7h16M9 7V4h6v3M6 7l1 13h10l1-13" /></svg>
</button>
-<button type="button" class="ad-icon-btn ad-icon-btn--outline" aria-label="Settings">
+<button type="button" class="icon-button" data-variant="outline" aria-label="Settings">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="12" cy="12" r="3" /><path d="M12 2.5v3M12 18.5v3M3.6 7.2l2.6 1.5M17.8 15.3l2.6 1.5M3.6 16.8l2.6-1.5M17.8 8.7l2.6-1.5" /></svg>
</button>
-<button type="button" class="ad-icon-btn ad-icon-btn--accent" aria-label="Filter (active)">
+<button type="button" class="icon-button" data-variant="accent" aria-label="Filter (active)">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M4 5h16l-6 8v5l-4 2v-7z" /></svg>
</button></code></pre>
</details>
@@ -43,14 +48,14 @@ system does not otherwise reach for.
<figure class="example">
<figcaption>Sizes</figcaption>
<div>
-<button type="button" class="ad-icon-btn ad-icon-btn--outline ad-icon-btn--sm" aria-label="Back"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.9" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M15 18l-6-6 6-6" /></svg></button>
-<button type="button" class="ad-icon-btn ad-icon-btn--outline" aria-label="Back"><svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.9" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M15 18l-6-6 6-6" /></svg></button>
-<button type="button" class="ad-icon-btn ad-icon-btn--outline ad-icon-btn--lg" aria-label="Back"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.9" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M15 18l-6-6 6-6" /></svg></button>
+<button type="button" class="icon-button" data-variant="outline" data-size="sm" aria-label="Back"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.9" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M15 18l-6-6 6-6" /></svg></button>
+<button type="button" class="icon-button" data-variant="outline" aria-label="Back"><svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.9" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M15 18l-6-6 6-6" /></svg></button>
+<button type="button" class="icon-button" data-variant="outline" data-size="lg" aria-label="Back"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.9" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M15 18l-6-6 6-6" /></svg></button>
</div>
<details><summary>Markup</summary>
-<pre><code><button type="button" class="ad-icon-btn ad-icon-btn--outline ad-icon-btn--sm" aria-label="Back"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.9" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M15 18l-6-6 6-6" /></svg></button>
-<button type="button" class="ad-icon-btn ad-icon-btn--outline" aria-label="Back"><svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.9" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M15 18l-6-6 6-6" /></svg></button>
-<button type="button" class="ad-icon-btn ad-icon-btn--outline ad-icon-btn--lg" aria-label="Back"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.9" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M15 18l-6-6 6-6" /></svg></button></code></pre>
+<pre><code><button type="button" class="icon-button" data-variant="outline" data-size="sm" aria-label="Back"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.9" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M15 18l-6-6 6-6" /></svg></button>
+<button type="button" class="icon-button" data-variant="outline" aria-label="Back"><svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.9" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M15 18l-6-6 6-6" /></svg></button>
+<button type="button" class="icon-button" data-variant="outline" data-size="lg" aria-label="Back"><svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.9" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M15 18l-6-6 6-6" /></svg></button></code></pre>
</details>
</figure>
@@ -61,33 +66,31 @@ An icon-only control almost always wants a [Tooltip](<%= relative_url('/componen
<figure class="example">
<figcaption>With a tooltip</figcaption>
<div>
-<span class="ad-tooltip">
- <button type="button" class="ad-icon-btn" aria-label="Delete">
+<span>
+ <button type="button" class="icon-button" aria-label="Delete" aria-describedby="icon-btn-tip">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M4 7h16M9 7V4h6v3M6 7l1 13h10l1-13" /></svg>
</button>
- <span class="ad-tooltip__bubble">Delete</span>
+ <span role="tooltip" id="icon-btn-tip">Delete</span>
</span>
</div>
<details><summary>Markup</summary>
-<pre><code><span class="ad-tooltip">
- <button type="button" class="ad-icon-btn" aria-label="Delete">
+<pre><code><span>
+ <button type="button" class="icon-button" aria-label="Delete" aria-describedby="icon-btn-tip">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M4 7h16M9 7V4h6v3M6 7l1 13h10l1-13" /></svg>
</button>
- <span class="ad-tooltip__bubble">Delete</span>
+ <span role="tooltip" id="icon-btn-tip">Delete</span>
</span></code></pre>
</details>
</figure>
## API
-<div class="ad-table-wrap">
- <table class="ad-table ad-table--compact">
- <thead><tr><th scope="col">Prop</th><th scope="col">Type</th><th scope="col">Notes</th></tr></thead>
- <tbody>
- <tr><td class="ad-token-name">icon</td><td class="ad-table__code">node</td><td>Required.</td></tr>
- <tr><td class="ad-token-name">label</td><td class="ad-table__code">string</td><td>Required — becomes <code>aria-label</code> and <code>title</code>.</td></tr>
- <tr><td class="ad-token-name">size</td><td class="ad-table__code">sm | md | lg</td><td>28 / 34 / 40px square.</td></tr>
- <tr><td class="ad-token-name">variant</td><td class="ad-table__code">ghost | outline | accent</td><td>Default <code>ghost</code>.</td></tr>
- </tbody>
- </table>
-</div>
+<table>
+ <thead><tr><th scope="col">Element / attribute</th><th scope="col">Values</th><th scope="col">Notes</th></tr></thead>
+ <tbody>
+ <tr><td><code>button.icon-button</code></td><td></td><td>Required base — the one class in this component, since nothing distinguishes it from Button structurally.</td></tr>
+ <tr><td><code>aria-label</code></td><td>string</td><td>Required — becomes the accessible name and the native tooltip.</td></tr>
+ <tr><td><code>data-size</code></td><td><code>sm | lg</code></td><td>28 / 40px square. Omit for the default 34px.</td></tr>
+ <tr><td><code>data-variant</code></td><td><code>outline | accent</code></td><td>Omit for the default, transparent ghost look.</td></tr>
+ </tbody>
+</table>
diff --git a/_components/input.md b/_components/input.md
index 0c0a54b..ed90739 100644
--- a/_components/input.md
+++ b/_components/input.md
@@ -7,13 +7,16 @@ summary: >-
## Basic
+`<input>` is the component — styled bare, no class. Nothing else on this
+site uses a text input, so there is nothing for the selector to clash with.
+
<figure class="example">
<figcaption>Input</figcaption>
<div>
-<input class="ad-input" type="text" placeholder="Dishwasher manual" style="max-width: 20rem;" />
+<input type="text" placeholder="Dishwasher manual" style="max-width: 20rem;" />
</div>
<details open><summary>Markup</summary>
-<pre><code><input class="ad-input" type="text" placeholder="Dishwasher manual" style="max-width: 20rem;" /></code></pre>
+<pre><code><input type="text" placeholder="Dishwasher manual" style="max-width: 20rem;" /></code></pre>
</details>
</figure>
@@ -22,48 +25,49 @@ summary: >-
<figure class="example">
<figcaption>States</figcaption>
<div data-layout="stack">
-<input class="ad-input" type="text" value="Focused" style="max-width: 20rem; border-color: var(--ad-color-accent); box-shadow: 0 0 0 2px var(--ad-color-accent-soft);" />
-<input class="ad-input" type="text" value="Disabled" disabled="" style="max-width: 20rem;" />
+<input type="text" value="Focused" style="max-width: 20rem; border-color: var(--ad-color-accent); box-shadow: 0 0 0 2px var(--ad-color-accent-soft);" />
+<input type="text" value="Disabled" disabled="" style="max-width: 20rem;" />
</div>
<details><summary>Markup</summary>
-<pre><code><input class="ad-input" type="text" value="Focused" style="max-width: 20rem; border-color: var(--ad-color-accent); box-shadow: 0 0 0 2px var(--ad-color-accent-soft);" />
-<input class="ad-input" type="text" value="Disabled" disabled="" style="max-width: 20rem;" /></code></pre>
+<pre><code><input type="text" value="Focused" style="max-width: 20rem; border-color: var(--ad-color-accent); box-shadow: 0 0 0 2px var(--ad-color-accent-soft);" />
+<input type="text" value="Disabled" disabled="" style="max-width: 20rem;" /></code></pre>
</details>
</figure>
## Invalid
Invalid state is expressed on the control itself, so it works with native
-form validation, plus an error line below.
+form validation, plus an error line below. `role="alert"` is a real
+live-region role for validation text, so it doubles as the selector that
+turns a `<small>` hint red.
<figure class="example">
<figcaption>Invalid</figcaption>
<div>
-<div class="ad-field" style="max-width: 20rem;">
- <label class="ad-field__label" for="input-accent">Accent hex</label>
- <input class="ad-input" id="input-accent" type="text" value="not-a-color" aria-invalid="true" aria-describedby="input-accent-error" />
- <p class="ad-field__error" id="input-accent-error">Not a valid hex color.</p>
-</div>
+<label class="field" style="max-width: 20rem;">
+ Accent hex
+ <input type="text" value="not-a-color" aria-invalid="true" />
+ <small role="alert">Not a valid hex color.</small>
+</label>
</div>
<details><summary>Markup</summary>
-<pre><code><div class="ad-field" style="max-width: 20rem;">
- <label class="ad-field__label" for="input-accent">Accent hex</label>
- <input class="ad-input" id="input-accent" type="text" value="not-a-color" aria-invalid="true" aria-describedby="input-accent-error" />
- <p class="ad-field__error" id="input-accent-error">Not a valid hex color.</p>
-</div></code></pre>
+<pre><code><label class="field" style="max-width: 20rem;">
+ Accent hex
+ <input type="text" value="not-a-color" aria-invalid="true" />
+ <small role="alert">Not a valid hex color.</small>
+</label></code></pre>
</details>
</figure>
## API
-<div class="ad-table-wrap">
- <table class="ad-table ad-table--compact">
- <thead><tr><th scope="col">Prop</th><th scope="col">Type</th><th scope="col">Notes</th></tr></thead>
- <tbody>
- <tr><td class="ad-token-name">type</td><td class="ad-table__code">string</td><td>Any native input type.</td></tr>
- <tr><td class="ad-token-name">placeholder</td><td class="ad-table__code">string</td><td></td></tr>
- <tr><td class="ad-token-name">disabled</td><td class="ad-table__code">boolean</td><td></td></tr>
- <tr><td class="ad-token-name">aria-invalid</td><td class="ad-table__code">"true"</td><td>Reddens the border and fill.</td></tr>
- </tbody>
- </table>
-</div>
+<table>
+ <thead><tr><th scope="col">Element / attribute</th><th scope="col">Values</th><th scope="col">Notes</th></tr></thead>
+ <tbody>
+ <tr><td><code>input</code></td><td></td><td>Required base — no class.</td></tr>
+ <tr><td><code>type</code></td><td>string</td><td>Any native input type except <code>checkbox</code>/<code>radio</code>, which are their own components.</td></tr>
+ <tr><td><code>placeholder</code></td><td>string</td><td></td></tr>
+ <tr><td><code>disabled</code></td><td>boolean</td><td></td></tr>
+ <tr><td><code>aria-invalid</code></td><td><code>"true"</code></td><td>Reddens the border and fill.</td></tr>
+ </tbody>
+</table>
diff --git a/_components/radio.md b/_components/radio.md
index 35c6682..bbad8c9 100644
--- a/_components/radio.md
+++ b/_components/radio.md
@@ -1,6 +1,6 @@
---
title: Radio
-summary: Flat radio button, the same visual language as Checkbox.
+summary: Native radio, the same accent-color treatment as Checkbox.
---
## Basic
@@ -8,26 +8,22 @@ summary: Flat radio button, the same visual language as Checkbox.
<figure class="example">
<figcaption>Radio</figcaption>
<div>
-<label class="ad-choice ad-choice--radio">
+<label>
<input type="radio" name="demo-radio-1" checked="" />
- <span class="ad-choice__box"></span>
Weekly
</label>
-<label class="ad-choice ad-choice--radio">
+<label>
<input type="radio" name="demo-radio-1" />
- <span class="ad-choice__box"></span>
Monthly
</label>
</div>
<details open><summary>Markup</summary>
-<pre><code><label class="ad-choice ad-choice--radio">
+<pre><code><label>
<input type="radio" name="demo-radio-1" checked="" />
- <span class="ad-choice__box"></span>
Weekly
</label>
-<label class="ad-choice ad-choice--radio">
+<label>
<input type="radio" name="demo-radio-1" />
- <span class="ad-choice__box"></span>
Monthly
</label></code></pre>
</details>
@@ -38,41 +34,35 @@ summary: Flat radio button, the same visual language as Checkbox.
<figure class="example">
<figcaption>Interval</figcaption>
<div>
-<fieldset class="ad-fieldset">
+<fieldset>
<legend>Repeat</legend>
- <label class="ad-choice ad-choice--radio">
+ <label>
<input type="radio" name="demo-radio-2" checked="" />
- <span class="ad-choice__box"></span>
Every week
</label>
- <label class="ad-choice ad-choice--radio">
+ <label>
<input type="radio" name="demo-radio-2" />
- <span class="ad-choice__box"></span>
Every month
</label>
- <label class="ad-choice ad-choice--radio">
+ <label>
<input type="radio" name="demo-radio-2" />
- <span class="ad-choice__box"></span>
Every year
</label>
</fieldset>
</div>
<details><summary>Markup</summary>
-<pre><code><fieldset class="ad-fieldset">
+<pre><code><fieldset>
<legend>Repeat</legend>
- <label class="ad-choice ad-choice--radio">
+ <label>
<input type="radio" name="demo-radio-2" checked="" />
- <span class="ad-choice__box"></span>
Every week
</label>
- <label class="ad-choice ad-choice--radio">
+ <label>
<input type="radio" name="demo-radio-2" />
- <span class="ad-choice__box"></span>
Every month
</label>
- <label class="ad-choice ad-choice--radio">
+ <label>
<input type="radio" name="demo-radio-2" />
- <span class="ad-choice__box"></span>
Every year
</label>
</fieldset></code></pre>
@@ -81,13 +71,12 @@ summary: Flat radio button, the same visual language as Checkbox.
## API
-<div class="ad-table-wrap">
- <table class="ad-table ad-table--compact">
- <thead><tr><th scope="col">Prop</th><th scope="col">Type</th><th scope="col">Notes</th></tr></thead>
- <tbody>
- <tr><td class="ad-token-name">checked</td><td class="ad-table__code">boolean</td><td>Required.</td></tr>
- <tr><td class="ad-token-name">name</td><td class="ad-table__code">string</td><td>Groups mutually exclusive radios.</td></tr>
- <tr><td class="ad-token-name">label</td><td class="ad-table__code">node</td><td></td></tr>
- </tbody>
- </table>
-</div>
+<table>
+ <thead><tr><th scope="col">Element / attribute</th><th scope="col">Values</th><th scope="col">Notes</th></tr></thead>
+ <tbody>
+ <tr><td><code>input[type="radio"]</code></td><td></td><td>Required base — no class. Tinted with <code>accent-color</code>.</td></tr>
+ <tr><td><code>checked</code></td><td>boolean</td><td></td></tr>
+ <tr><td><code>name</code></td><td>string</td><td>Groups mutually exclusive radios.</td></tr>
+ <tr><td><code>label</code></td><td>text content</td><td>Wrap the input directly — <code>label:has(> input[type="radio"])</code> is what applies the row layout.</td></tr>
+ </tbody>
+</table>
diff --git a/_components/select.md b/_components/select.md
index 27e3463..dc76c73 100644
--- a/_components/select.md
+++ b/_components/select.md
@@ -5,10 +5,12 @@ summary: Native select, styled to match Input exactly.
## Basic
+`<select>` is the component — styled bare, no class.
+
<figure class="example">
<figcaption>Select</figcaption>
<div>
-<select class="ad-select" style="max-width: 20rem;">
+<select style="max-width: 20rem;">
<option>Day</option>
<option selected="">Week</option>
<option>Month</option>
@@ -16,7 +18,7 @@ summary: Native select, styled to match Input exactly.
</select>
</div>
<details open><summary>Markup</summary>
-<pre><code><select class="ad-select" style="max-width: 20rem;">
+<pre><code><select style="max-width: 20rem;">
<option>Day</option>
<option selected="">Week</option>
<option>Month</option>
@@ -30,10 +32,10 @@ summary: Native select, styled to match Input exactly.
<figure class="example">
<figcaption>Disabled</figcaption>
<div>
-<select class="ad-select" disabled="" style="max-width: 20rem;"><option>Week</option></select>
+<select disabled="" style="max-width: 20rem;"><option>Week</option></select>
</div>
<details><summary>Markup</summary>
-<pre><code><select class="ad-select" disabled="" style="max-width: 20rem;"><option>Week</option></select></code></pre>
+<pre><code><select disabled="" style="max-width: 20rem;"><option>Week</option></select></code></pre>
</details>
</figure>
@@ -48,12 +50,11 @@ summary: Native select, styled to match Input exactly.
## API
-<div class="ad-table-wrap">
- <table class="ad-table ad-table--compact">
- <thead><tr><th scope="col">Prop</th><th scope="col">Type</th><th scope="col">Notes</th></tr></thead>
- <tbody>
- <tr><td class="ad-token-name">options</td><td class="ad-table__code">{value, label}[]</td><td></td></tr>
- <tr><td class="ad-token-name">disabled</td><td class="ad-table__code">boolean</td><td></td></tr>
- </tbody>
- </table>
-</div>
+<table>
+ <thead><tr><th scope="col">Element / attribute</th><th scope="col">Values</th><th scope="col">Notes</th></tr></thead>
+ <tbody>
+ <tr><td><code>select</code></td><td></td><td>Required base — no class.</td></tr>
+ <tr><td><code>option</code></td><td></td><td></td></tr>
+ <tr><td><code>disabled</code></td><td>boolean</td><td></td></tr>
+ </tbody>
+</table>
diff --git a/_components/switch.md b/_components/switch.md
index 2b01070..bf02bf4 100644
--- a/_components/switch.md
+++ b/_components/switch.md
@@ -7,29 +7,37 @@ summary: >-
## Basic
+`role="switch"` is the correct ARIA role for a toggle, so it doubles as the
+attribute that tells Switch's `input[type="checkbox"]` apart from a plain
+Checkbox — real accessibility markup doing double duty as a style hook. The
+track and thumb still need `.switch`, one sanctioned fallback class: the
+thumb's independent position and transition need a second element, more than
+`appearance: none` plus pseudo-elements on the input itself can cleanly
+carry.
+
<figure class="example">
<figcaption>Switch</figcaption>
<div>
-<label class="ad-switch">
- <input type="checkbox" checked="" />
- <span class="ad-switch__track"></span>
+<label class="switch">
+ <input type="checkbox" role="switch" checked="" />
+ <span></span>
Dark mode
</label>
-<label class="ad-switch">
- <input type="checkbox" />
- <span class="ad-switch__track"></span>
+<label class="switch">
+ <input type="checkbox" role="switch" />
+ <span></span>
Weekly digest
</label>
</div>
<details open><summary>Markup</summary>
-<pre><code><label class="ad-switch">
- <input type="checkbox" checked="" />
- <span class="ad-switch__track"></span>
+<pre><code><label class="switch">
+ <input type="checkbox" role="switch" checked="" />
+ <span></span>
Dark mode
</label>
-<label class="ad-switch">
- <input type="checkbox" />
- <span class="ad-switch__track"></span>
+<label class="switch">
+ <input type="checkbox" role="switch" />
+ <span></span>
Weekly digest
</label></code></pre>
</details>
@@ -40,16 +48,16 @@ summary: >-
<figure class="example">
<figcaption>Disabled</figcaption>
<div>
-<label class="ad-switch">
- <input type="checkbox" checked="" disabled="" />
- <span class="ad-switch__track"></span>
+<label class="switch">
+ <input type="checkbox" role="switch" checked="" disabled="" />
+ <span></span>
Always on for this plan
</label>
</div>
<details><summary>Markup</summary>
-<pre><code><label class="ad-switch">
- <input type="checkbox" checked="" disabled="" />
- <span class="ad-switch__track"></span>
+<pre><code><label class="switch">
+ <input type="checkbox" role="switch" checked="" disabled="" />
+ <span></span>
Always on for this plan
</label></code></pre>
</details>
@@ -57,13 +65,13 @@ summary: >-
## API
-<div class="ad-table-wrap">
- <table class="ad-table ad-table--compact">
- <thead><tr><th scope="col">Prop</th><th scope="col">Type</th><th scope="col">Notes</th></tr></thead>
- <tbody>
- <tr><td class="ad-token-name">checked</td><td class="ad-table__code">boolean</td><td>Required.</td></tr>
- <tr><td class="ad-token-name">label</td><td class="ad-table__code">node</td><td></td></tr>
- <tr><td class="ad-token-name">disabled</td><td class="ad-table__code">boolean</td><td></td></tr>
- </tbody>
- </table>
-</div>
+<table>
+ <thead><tr><th scope="col">Element / attribute</th><th scope="col">Values</th><th scope="col">Notes</th></tr></thead>
+ <tbody>
+ <tr><td><code>label.switch</code></td><td></td><td>Required wrapper.</td></tr>
+ <tr><td><code>input[type="checkbox"][role="switch"]</code></td><td></td><td>Required — the role is what makes it a Switch instead of a Checkbox.</td></tr>
+ <tr><td><code>> span</code></td><td></td><td>Required — the track and thumb. No class, just structural position.</td></tr>
+ <tr><td><code>checked</code></td><td>boolean</td><td></td></tr>
+ <tr><td><code>disabled</code></td><td>boolean</td><td></td></tr>
+ </tbody>
+</table>
diff --git a/_components/tabs.md b/_components/tabs.md
index 2488877..eee397d 100644
--- a/_components/tabs.md
+++ b/_components/tabs.md
@@ -6,35 +6,43 @@ summary: >-
move, Home/End jump to the ends.
---
+Tabs follows the real WAI-ARIA tabs pattern: `[role="tablist"]` contains
+`[role="tab"]` buttons controlling `[role="tabpanel"]`s. Those roles are
+required for the pattern to be accessible in the first place, so they are the
+entire styling contract too — no classes anywhere in this component. The
+keyboard wiring (`assets/js/system.js`) only looks for `[data-tabs]` /
+`[role=tab]`, so the root wrapper keeps that one data attribute and needs
+nothing else.
+
## Underline
Mono, with an accent underline. For primary section navigation — a project's
-own tabs, never a filter.
+own tabs, never a filter. This is the default; no `data-variant` needed.
<figure class="example">
<figcaption>Underline</figcaption>
<div data-layout="stack">
-<div class="ad-tabs ad-tabs--underline" data-tabs="">
- <div class="ad-tabs__list" role="tablist" aria-label="Repository">
- <button class="ad-tabs__tab" role="tab" id="tabs-code-tab" aria-controls="tabs-code-panel" aria-selected="true" tabindex="0">Code</button>
- <button class="ad-tabs__tab" role="tab" id="tabs-ci-tab" aria-controls="tabs-ci-panel" aria-selected="false" tabindex="-1">CI</button>
- <button class="ad-tabs__tab" role="tab" id="tabs-issues-tab" aria-controls="tabs-issues-panel" aria-selected="false" tabindex="-1">Issues</button>
+<div data-tabs="">
+ <div role="tablist" aria-label="Repository">
+ <button role="tab" id="tabs-code-tab" aria-controls="tabs-code-panel" aria-selected="true" tabindex="0">Code</button>
+ <button role="tab" id="tabs-ci-tab" aria-controls="tabs-ci-panel" aria-selected="false" tabindex="-1">CI</button>
+ <button role="tab" id="tabs-issues-tab" aria-controls="tabs-issues-panel" aria-selected="false" tabindex="-1">Issues</button>
</div>
- <div class="ad-tabs__panel" role="tabpanel" id="tabs-code-panel" aria-labelledby="tabs-code-tab" tabindex="0">Repository contents.</div>
- <div class="ad-tabs__panel" role="tabpanel" id="tabs-ci-panel" aria-labelledby="tabs-ci-tab" tabindex="0" hidden="">Build history.</div>
- <div class="ad-tabs__panel" role="tabpanel" id="tabs-issues-panel" aria-labelledby="tabs-issues-tab" tabindex="0" hidden="">Open issues.</div>
+ <div role="tabpanel" id="tabs-code-panel" aria-labelledby="tabs-code-tab" tabindex="0">Repository contents.</div>
+ <div role="tabpanel" id="tabs-ci-panel" aria-labelledby="tabs-ci-tab" tabindex="0" hidden="">Build history.</div>
+ <div role="tabpanel" id="tabs-issues-panel" aria-labelledby="tabs-issues-tab" tabindex="0" hidden="">Open issues.</div>
</div>
</div>
<details open><summary>Markup</summary>
-<pre><code><div class="ad-tabs ad-tabs--underline" data-tabs="">
- <div class="ad-tabs__list" role="tablist" aria-label="Repository">
- <button class="ad-tabs__tab" role="tab" id="tabs-code-tab" aria-controls="tabs-code-panel" aria-selected="true" tabindex="0">Code</button>
- <button class="ad-tabs__tab" role="tab" id="tabs-ci-tab" aria-controls="tabs-ci-panel" aria-selected="false" tabindex="-1">CI</button>
- <button class="ad-tabs__tab" role="tab" id="tabs-issues-tab" aria-controls="tabs-issues-panel" aria-selected="false" tabindex="-1">Issues</button>
+<pre><code><div data-tabs="">
+ <div role="tablist" aria-label="Repository">
+ <button role="tab" id="tabs-code-tab" aria-controls="tabs-code-panel" aria-selected="true" tabindex="0">Code</button>
+ <button role="tab" id="tabs-ci-tab" aria-controls="tabs-ci-panel" aria-selected="false" tabindex="-1">CI</button>
+ <button role="tab" id="tabs-issues-tab" aria-controls="tabs-issues-panel" aria-selected="false" tabindex="-1">Issues</button>
</div>
- <div class="ad-tabs__panel" role="tabpanel" id="tabs-code-panel" aria-labelledby="tabs-code-tab" tabindex="0">Repository contents.</div>
- <div class="ad-tabs__panel" role="tabpanel" id="tabs-ci-panel" aria-labelledby="tabs-ci-tab" tabindex="0" hidden="">Build history.</div>
- <div class="ad-tabs__panel" role="tabpanel" id="tabs-issues-panel" aria-labelledby="tabs-issues-tab" tabindex="0" hidden="">Open issues.</div>
+ <div role="tabpanel" id="tabs-code-panel" aria-labelledby="tabs-code-tab" tabindex="0">Repository contents.</div>
+ <div role="tabpanel" id="tabs-ci-panel" aria-labelledby="tabs-ci-tab" tabindex="0" hidden="">Build history.</div>
+ <div role="tabpanel" id="tabs-issues-panel" aria-labelledby="tabs-issues-tab" tabindex="0" hidden="">Open issues.</div>
</div></code></pre>
</details>
</figure>
@@ -42,32 +50,34 @@ own tabs, never a filter.
## Segmented
A pill track; the active segment lifts to the surface. For filters — *All ·
-Overdue · Upcoming* — never primary navigation.
+Overdue · Upcoming* — never primary navigation. `data-variant="segmented"` on
+the tablist is the only attribute either variant needs, since neither has a
+natural one of its own.
<figure class="example">
<figcaption>Segmented</figcaption>
<div data-layout="stack">
-<div class="ad-tabs ad-tabs--segmented" data-tabs="">
- <div class="ad-tabs__list" role="tablist" aria-label="Filter">
- <button class="ad-tabs__tab" role="tab" id="tabs-all-tab" aria-controls="tabs-all-panel" aria-selected="true" tabindex="0">All</button>
- <button class="ad-tabs__tab" role="tab" id="tabs-overdue-tab" aria-controls="tabs-overdue-panel" aria-selected="false" tabindex="-1">Overdue</button>
- <button class="ad-tabs__tab" role="tab" id="tabs-upcoming-tab" aria-controls="tabs-upcoming-panel" aria-selected="false" tabindex="-1">Upcoming</button>
+<div data-tabs="">
+ <div role="tablist" data-variant="segmented" aria-label="Filter">
+ <button role="tab" id="tabs-all-tab" aria-controls="tabs-all-panel" aria-selected="true" tabindex="0">All</button>
+ <button role="tab" id="tabs-overdue-tab" aria-controls="tabs-overdue-panel" aria-selected="false" tabindex="-1">Overdue</button>
+ <button role="tab" id="tabs-upcoming-tab" aria-controls="tabs-upcoming-panel" aria-selected="false" tabindex="-1">Upcoming</button>
</div>
- <div class="ad-tabs__panel" role="tabpanel" id="tabs-all-panel" aria-labelledby="tabs-all-tab" tabindex="0">7 series.</div>
- <div class="ad-tabs__panel" role="tabpanel" id="tabs-overdue-panel" aria-labelledby="tabs-overdue-tab" tabindex="0" hidden="">2 series.</div>
- <div class="ad-tabs__panel" role="tabpanel" id="tabs-upcoming-panel" aria-labelledby="tabs-upcoming-tab" tabindex="0" hidden="">5 series.</div>
+ <div role="tabpanel" id="tabs-all-panel" aria-labelledby="tabs-all-tab" tabindex="0">7 series.</div>
+ <div role="tabpanel" id="tabs-overdue-panel" aria-labelledby="tabs-overdue-tab" tabindex="0" hidden="">2 series.</div>
+ <div role="tabpanel" id="tabs-upcoming-panel" aria-labelledby="tabs-upcoming-tab" tabindex="0" hidden="">5 series.</div>
</div>
</div>
<details><summary>Markup</summary>
-<pre><code><div class="ad-tabs ad-tabs--segmented" data-tabs="">
- <div class="ad-tabs__list" role="tablist" aria-label="Filter">
- <button class="ad-tabs__tab" role="tab" id="tabs-all-tab" aria-controls="tabs-all-panel" aria-selected="true" tabindex="0">All</button>
- <button class="ad-tabs__tab" role="tab" id="tabs-overdue-tab" aria-controls="tabs-overdue-panel" aria-selected="false" tabindex="-1">Overdue</button>
- <button class="ad-tabs__tab" role="tab" id="tabs-upcoming-tab" aria-controls="tabs-upcoming-panel" aria-selected="false" tabindex="-1">Upcoming</button>
+<pre><code><div data-tabs="">
+ <div role="tablist" data-variant="segmented" aria-label="Filter">
+ <button role="tab" id="tabs-all-tab" aria-controls="tabs-all-panel" aria-selected="true" tabindex="0">All</button>
+ <button role="tab" id="tabs-overdue-tab" aria-controls="tabs-overdue-panel" aria-selected="false" tabindex="-1">Overdue</button>
+ <button role="tab" id="tabs-upcoming-tab" aria-controls="tabs-upcoming-panel" aria-selected="false" tabindex="-1">Upcoming</button>
</div>
- <div class="ad-tabs__panel" role="tabpanel" id="tabs-all-panel" aria-labelledby="tabs-all-tab" tabindex="0">7 series.</div>
- <div class="ad-tabs__panel" role="tabpanel" id="tabs-overdue-panel" aria-labelledby="tabs-overdue-tab" tabindex="0" hidden="">2 series.</div>
- <div class="ad-tabs__panel" role="tabpanel" id="tabs-upcoming-panel" aria-labelledby="tabs-upcoming-tab" tabindex="0" hidden="">5 series.</div>
+ <div role="tabpanel" id="tabs-all-panel" aria-labelledby="tabs-all-tab" tabindex="0">7 series.</div>
+ <div role="tabpanel" id="tabs-overdue-panel" aria-labelledby="tabs-overdue-tab" tabindex="0" hidden="">2 series.</div>
+ <div role="tabpanel" id="tabs-upcoming-panel" aria-labelledby="tabs-upcoming-tab" tabindex="0" hidden="">5 series.</div>
</div></code></pre>
</details>
</figure>
@@ -81,13 +91,13 @@ Overdue · Upcoming* — never primary navigation.
## API
-<div class="ad-table-wrap">
- <table class="ad-table ad-table--compact">
- <thead><tr><th scope="col">Prop</th><th scope="col">Type</th><th scope="col">Notes</th></tr></thead>
- <tbody>
- <tr><td class="ad-token-name">items</td><td class="ad-table__code">{value, label}[]</td><td>Required.</td></tr>
- <tr><td class="ad-token-name">value</td><td class="ad-table__code">string</td><td>The selected item's value.</td></tr>
- <tr><td class="ad-token-name">variant</td><td class="ad-table__code">underline | segmented</td><td>Default <code>underline</code>.</td></tr>
- </tbody>
- </table>
-</div>
+<table>
+ <thead><tr><th scope="col">Element / attribute</th><th scope="col">Values</th><th scope="col">Notes</th></tr></thead>
+ <tbody>
+ <tr><td><code>[data-tabs]</code></td><td></td><td>Required on the root wrapper — what the keyboard wiring in <code>system.js</code> looks for.</td></tr>
+ <tr><td><code>[role="tablist"]</code></td><td></td><td>Required. Contains the tab buttons.</td></tr>
+ <tr><td><code>[role="tab"]</code></td><td></td><td>Required, one per item. <code>aria-selected</code> and <code>tabindex</code> are managed by the JS.</td></tr>
+ <tr><td><code>[role="tabpanel"]</code></td><td></td><td>Required, one per item, referenced by the tab's <code>aria-controls</code>.</td></tr>
+ <tr><td><code>data-variant</code></td><td><code>segmented</code></td><td>On the tablist. Omit for the default underline look.</td></tr>
+ </tbody>
+</table>
diff --git a/_components/tag.md b/_components/tag.md
index 247d8da..7f03722 100644
--- a/_components/tag.md
+++ b/_components/tag.md
@@ -1,28 +1,32 @@
---
title: Tag
summary: >-
- A removable mono chip for freeform tags — locations, categories. `--dashed`
+ A removable mono chip for freeform tags — locations, categories. `[data-dashed]`
renders an "add tag" affordance instead of a value.
---
## Default and removable
+Same reasoning as Badge: a `<span>` is too generic to select bare, so Tag
+keeps one class. The remove control is a real `<button>`, styled through the
+descendant selector `.tag button` — no class of its own.
+
<figure class="example">
<figcaption>Tags</figcaption>
<div>
-<span class="ad-tag">location:garage</span>
-<span class="ad-tag">category:appliance</span>
-<span class="ad-tag">
+<span class="tag">location:garage</span>
+<span class="tag">category:appliance</span>
+<span class="tag">
category:receipt
- <button type="button" class="ad-tag__remove" aria-label="Remove tag">×</button>
+ <button type="button" aria-label="Remove tag">×</button>
</span>
</div>
<details open><summary>Markup</summary>
-<pre><code><span class="ad-tag">location:garage</span>
-<span class="ad-tag">category:appliance</span>
-<span class="ad-tag">
+<pre><code><span class="tag">location:garage</span>
+<span class="tag">category:appliance</span>
+<span class="tag">
category:receipt
- <button type="button" class="ad-tag__remove" aria-label="Remove tag">×</button>
+ <button type="button" aria-label="Remove tag">×</button>
</span></code></pre>
</details>
</figure>
@@ -32,27 +36,26 @@ summary: >-
<figure class="example">
<figcaption>Add tag</figcaption>
<div>
-<div class="ad-tag-set">
- <span class="ad-tag">location:garage</span>
- <span class="ad-tag ad-tag--dashed">+ add tag</span>
+<div class="tag-set">
+ <span class="tag">location:garage</span>
+ <span class="tag" data-dashed>+ add tag</span>
</div>
</div>
<details><summary>Markup</summary>
-<pre><code><div class="ad-tag-set">
- <span class="ad-tag">location:garage</span>
- <span class="ad-tag ad-tag--dashed">+ add tag</span>
+<pre><code><div class="tag-set">
+ <span class="tag">location:garage</span>
+ <span class="tag" data-dashed>+ add tag</span>
</div></code></pre>
</details>
</figure>
## API
-<div class="ad-table-wrap">
- <table class="ad-table ad-table--compact">
- <thead><tr><th scope="col">Prop</th><th scope="col">Type</th><th scope="col">Notes</th></tr></thead>
- <tbody>
- <tr><td class="ad-token-name">onRemove</td><td class="ad-table__code">function</td><td>Renders the × button when present.</td></tr>
- <tr><td class="ad-token-name">dashed</td><td class="ad-table__code">boolean</td><td>Renders an "add tag" affordance instead of a value.</td></tr>
- </tbody>
- </table>
-</div>
+<table>
+ <thead><tr><th scope="col">Element / attribute</th><th scope="col">Values</th><th scope="col">Notes</th></tr></thead>
+ <tbody>
+ <tr><td><code>span.tag</code></td><td></td><td>Required base.</td></tr>
+ <tr><td><code>button</code></td><td></td><td>Nest a real <code><button aria-label="Remove tag"></code> to render the × affordance — styled via <code>.tag button</code>, no class needed.</td></tr>
+ <tr><td><code>data-dashed</code></td><td>boolean</td><td>Renders an "add tag" affordance instead of a value.</td></tr>
+ </tbody>
+</table>
diff --git a/_components/toast.md b/_components/toast.md
index f43f59f..e01cc2c 100644
--- a/_components/toast.md
+++ b/_components/toast.md
@@ -8,23 +8,28 @@ summary: >-
## Basic
-The example below stays in document flow so it does not cover the page;
-`.ad-toast--fixed` is what actually pins it bottom-right in a real app.
+`role="status"` is the correct live-region role for a transient status
+message, so it doubles as the entire styling hook — no base class. The
+example below stays in document flow so it does not cover the page;
+`[data-fixed]` is what actually pins it bottom-right in a real app. The
+action and dismiss buttons are plain `<button>`s told apart by
+`aria-label="Dismiss"`, an attribute the icon-only close control needs
+anyway.
<figure class="example">
<figcaption>Toast</figcaption>
<div>
-<div class="ad-toast">
- <span class="ad-toast__message">Deleted "Call Mom"</span>
- <button type="button" class="ad-toast__action">Undo</button>
- <button type="button" class="ad-toast__dismiss" aria-label="Dismiss">×</button>
+<div role="status">
+ <span>Deleted "Call Mom"</span>
+ <button type="button">Undo</button>
+ <button type="button" aria-label="Dismiss">×</button>
</div>
</div>
<details open><summary>Markup</summary>
-<pre><code><div class="ad-toast">
- <span class="ad-toast__message">Deleted "Call Mom"</span>
- <button type="button" class="ad-toast__action">Undo</button>
- <button type="button" class="ad-toast__dismiss" aria-label="Dismiss">×</button>
+<pre><code><div role="status">
+ <span>Deleted "Call Mom"</span>
+ <button type="button">Undo</button>
+ <button type="button" aria-label="Dismiss">×</button>
</div></code></pre>
</details>
</figure>
@@ -34,28 +39,28 @@ The example below stays in document flow so it does not cover the page;
<figure class="example">
<figcaption>No action</figcaption>
<div>
-<div class="ad-toast">
- <span class="ad-toast__message">Series created</span>
- <button type="button" class="ad-toast__dismiss" aria-label="Dismiss">×</button>
+<div role="status">
+ <span>Series created</span>
+ <button type="button" aria-label="Dismiss">×</button>
</div>
</div>
<details><summary>Markup</summary>
-<pre><code><div class="ad-toast">
- <span class="ad-toast__message">Series created</span>
- <button type="button" class="ad-toast__dismiss" aria-label="Dismiss">×</button>
+<pre><code><div role="status">
+ <span>Series created</span>
+ <button type="button" aria-label="Dismiss">×</button>
</div></code></pre>
</details>
</figure>
## API
-<div class="ad-table-wrap">
- <table class="ad-table ad-table--compact">
- <thead><tr><th scope="col">Prop</th><th scope="col">Type</th><th scope="col">Notes</th></tr></thead>
- <tbody>
- <tr><td class="ad-token-name">message</td><td class="ad-table__code">string</td><td>Required.</td></tr>
- <tr><td class="ad-token-name">actionLabel</td><td class="ad-table__code">string</td><td>Renders a destructive-colored action, typically "Undo".</td></tr>
- <tr><td class="ad-token-name">onDismiss</td><td class="ad-table__code">function</td><td></td></tr>
- </tbody>
- </table>
-</div>
+<table>
+ <thead><tr><th scope="col">Element / attribute</th><th scope="col">Values</th><th scope="col">Notes</th></tr></thead>
+ <tbody>
+ <tr><td><code>[role="status"]</code></td><td></td><td>Required base, on a <code><div></code>.</td></tr>
+ <tr><td><code>> span</code></td><td>string</td><td>The message. Required.</td></tr>
+ <tr><td><code>> button</code></td><td></td><td>Renders a destructive-colored action, typically "Undo".</td></tr>
+ <tr><td><code>> button[aria-label="Dismiss"]</code></td><td></td><td>The × control — the label is what tells it apart from the action button.</td></tr>
+ <tr><td><code>data-fixed</code></td><td>boolean</td><td>Pins the toast bottom-right of the viewport. Omit to stay in document flow.</td></tr>
+ </tbody>
+</table>
diff --git a/_components/tooltip.md b/_components/tooltip.md
index 8ace3dd..7753dd5 100644
--- a/_components/tooltip.md
+++ b/_components/tooltip.md
@@ -7,24 +7,31 @@ summary: >-
## Basic
+`role="tooltip"` is the correct ARIA role for the bubble itself, so it
+doubles as the styling hook — no class on the bubble. The wrapping `<span>`
+has no attribute of its own, but it does not need one: `:has()` selects "a
+span that contains a `[role=tooltip]` bubble" precisely, so the wrapper stays
+bare too. Pair the trigger's `aria-describedby` with the bubble's `id` so the
+tooltip is announced, not just shown.
+
Hover or tab to the button below.
<figure class="example">
<figcaption>Tooltip</figcaption>
<div>
-<span class="ad-tooltip">
- <button type="button" class="ad-icon-btn" aria-label="Delete">
+<span>
+ <button type="button" class="icon-button" aria-label="Delete" aria-describedby="tooltip-basic">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M4 7h16M9 7V4h6v3M6 7l1 13h10l1-13" /></svg>
</button>
- <span class="ad-tooltip__bubble">Delete</span>
+ <span role="tooltip" id="tooltip-basic">Delete</span>
</span>
</div>
<details open><summary>Markup</summary>
-<pre><code><span class="ad-tooltip">
- <button type="button" class="ad-icon-btn" aria-label="Delete">
+<pre><code><span>
+ <button type="button" class="icon-button" aria-label="Delete" aria-describedby="tooltip-basic">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M4 7h16M9 7V4h6v3M6 7l1 13h10l1-13" /></svg>
</button>
- <span class="ad-tooltip__bubble">Delete</span>
+ <span role="tooltip" id="tooltip-basic">Delete</span>
</span></code></pre>
</details>
</figure>
@@ -36,27 +43,26 @@ A Tooltip wraps whatever it labels — most often an [IconButton](<%= relative_u
<figure class="example">
<figcaption>On a badge</figcaption>
<div>
-<span class="ad-tooltip">
- <span class="ad-badge ad-badge--danger" tabindex="0">3d overdue</span>
- <span class="ad-tooltip__bubble">Due Tuesday, was Saturday</span>
+<span>
+ <span class="badge" data-tone="danger" tabindex="0" aria-describedby="tooltip-badge">3d overdue</span>
+ <span role="tooltip" id="tooltip-badge">Due Tuesday, was Saturday</span>
</span>
</div>
<details><summary>Markup</summary>
-<pre><code><span class="ad-tooltip">
- <span class="ad-badge ad-badge--danger" tabindex="0">3d overdue</span>
- <span class="ad-tooltip__bubble">Due Tuesday, was Saturday</span>
+<pre><code><span>
+ <span class="badge" data-tone="danger" tabindex="0" aria-describedby="tooltip-badge">3d overdue</span>
+ <span role="tooltip" id="tooltip-badge">Due Tuesday, was Saturday</span>
</span></code></pre>
</details>
</figure>
## API
-<div class="ad-table-wrap">
- <table class="ad-table ad-table--compact">
- <thead><tr><th scope="col">Prop</th><th scope="col">Type</th><th scope="col">Notes</th></tr></thead>
- <tbody>
- <tr><td class="ad-token-name">label</td><td class="ad-table__code">string</td><td>Required.</td></tr>
- <tr><td class="ad-token-name">children</td><td class="ad-table__code">node</td><td>The trigger element. Give it a real accessible name of its own — a tooltip is not one.</td></tr>
- </tbody>
- </table>
-</div>
+<table>
+ <thead><tr><th scope="col">Element / attribute</th><th scope="col">Values</th><th scope="col">Notes</th></tr></thead>
+ <tbody>
+ <tr><td><code>span</code></td><td></td><td>Required wrapper around the trigger and the bubble. Bare — matched structurally via <code>:has()</code>, no class.</td></tr>
+ <tr><td><code>[role="tooltip"]</code></td><td>string content</td><td>Required — the bubble. Give it an <code>id</code>.</td></tr>
+ <tr><td><code>aria-describedby</code></td><td>the bubble's <code>id</code></td><td>Put it on the trigger element, which needs a real accessible name of its own — a tooltip is not one.</td></tr>
+ </tbody>
+</table>
diff --git a/_foundations/color.md b/_foundations/color.md
index 077dac8..f4ed690 100644
--- a/_foundations/color.md
+++ b/_foundations/color.md
@@ -192,27 +192,27 @@ the default; the other four are the sanctioned menu a project can switch to.
<ul class="ad-accent-grid">
<li>
<div class="ad-accent-demo" style="background: #3a6a63;">teal (default)</div>
- <div class="ad-specimen__meta"><span class="ad-token-name">#3a6a63</span><span>6.14:1 on white</span></div>
+ <div class="ad-specimen__meta"><code>#3a6a63</code><span>6.14:1 on white</span></div>
<p class="ad-muted" style="font-size: var(--ad-step--2); margin: var(--ad-space-3xs) 0 0;">Default. Neutral, technical.</p>
</li>
<li>
<div class="ad-accent-demo" style="background: #9a5a3c;">clay</div>
- <div class="ad-specimen__meta"><span class="ad-token-name">#9a5a3c</span><span>5.38:1 on white</span></div>
+ <div class="ad-specimen__meta"><code>#9a5a3c</code><span>5.38:1 on white</span></div>
<p class="ad-muted" style="font-size: var(--ad-step--2); margin: var(--ad-space-3xs) 0 0;">Warm, archival — domus's flavor.</p>
</li>
<li>
<div class="ad-accent-demo" style="background: #2b4a78;">ink-blue</div>
- <div class="ad-specimen__meta"><span class="ad-token-name">#2b4a78</span><span>8.92:1 on white</span></div>
+ <div class="ad-specimen__meta"><code>#2b4a78</code><span>8.92:1 on white</span></div>
<p class="ad-muted" style="font-size: var(--ad-step--2); margin: var(--ad-space-3xs) 0 0;">Sober, utilitarian.</p>
</li>
<li>
<div class="ad-accent-demo" style="background: #93701f;">ochre</div>
- <div class="ad-specimen__meta"><span class="ad-token-name">#93701f</span><span>4.59:1 on white</span></div>
+ <div class="ad-specimen__meta"><code>#93701f</code><span>4.59:1 on white</span></div>
<p class="ad-muted" style="font-size: var(--ad-step--2); margin: var(--ad-space-3xs) 0 0;">Dry, papery.</p>
</li>
<li>
<div class="ad-accent-demo" style="background: #6a3a5c;">plum</div>
- <div class="ad-specimen__meta"><span class="ad-token-name">#6a3a5c</span><span>8.85:1 on white</span></div>
+ <div class="ad-specimen__meta"><code>#6a3a5c</code><span>8.85:1 on white</span></div>
<p class="ad-muted" style="font-size: var(--ad-step--2); margin: var(--ad-space-3xs) 0 0;">Quiet, personal.</p>
</li>
</ul>
@@ -238,8 +238,7 @@ the right half is the dark one.
<p class="ad-muted">Backgrounds, from the page itself up to a card sitting on it.</p>
-<div class="ad-table-wrap">
- <table class="ad-table ad-table--compact">
+ <table>
<thead>
<tr>
<th scope="col">Token</th>
@@ -256,11 +255,11 @@ the right half is the dark one.
<span style="background: #faf9f7"></span>
<span style="background: #0e0d0b"></span>
</span>
- <span class="ad-token-name">--ad-color-surface-page</span>
+ <code>--ad-color-surface-page</code>
</div>
</td>
- <td class="ad-table__code">neutral.0</td>
- <td class="ad-table__code">neutral_dark.0</td>
+ <td><code>neutral.0</code></td>
+ <td><code>neutral_dark.0</code></td>
<td>The page itself. Nothing sits behind it.</td>
</tr>
<tr>
@@ -270,11 +269,11 @@ the right half is the dark one.
<span style="background: #fdfdfb"></span>
<span style="background: #151412"></span>
</span>
- <span class="ad-token-name">--ad-color-surface-card</span>
+ <code>--ad-color-surface-card</code>
</div>
</td>
- <td class="ad-table__code">neutral.50</td>
- <td class="ad-table__code">neutral_dark.50</td>
+ <td><code>neutral.50</code></td>
+ <td><code>neutral_dark.50</code></td>
<td>Cards, dialogs, toasts — anything that reads as a sheet on the page.</td>
</tr>
<tr>
@@ -284,11 +283,11 @@ the right half is the dark one.
<span style="background: #f1f0ed"></span>
<span style="background: #1e1d1a"></span>
</span>
- <span class="ad-token-name">--ad-color-surface-fill</span>
+ <code>--ad-color-surface-fill</code>
</div>
</td>
- <td class="ad-table__code">neutral.100</td>
- <td class="ad-table__code">neutral_dark.100</td>
+ <td><code>neutral.100</code></td>
+ <td><code>neutral_dark.100</code></td>
<td>Recessed fill — a hovered row, a segmented-tab track, a photo placeholder.</td>
</tr>
<tr>
@@ -298,23 +297,21 @@ the right half is the dark one.
<span style="background: #e8e6e2"></span>
<span style="background: #252421"></span>
</span>
- <span class="ad-token-name">--ad-color-surface-fill-2</span>
+ <code>--ad-color-surface-fill-2</code>
</div>
</td>
- <td class="ad-table__code">neutral.150</td>
- <td class="ad-table__code">neutral_dark.150</td>
+ <td><code>neutral.150</code></td>
+ <td><code>neutral_dark.150</code></td>
<td>A second, deeper fill step — the diagonal-grain stripe inside an empty photo slot.</td>
</tr>
</tbody>
</table>
-</div>
### Borders
<p class="ad-muted">Two weights. Hairlines do most of the elevation work in this system, so there was never a reason for a third.</p>
-<div class="ad-table-wrap">
- <table class="ad-table ad-table--compact">
+ <table>
<thead>
<tr>
<th scope="col">Token</th>
@@ -331,11 +328,11 @@ the right half is the dark one.
<span style="background: #dcdad7"></span>
<span style="background: #2c2b29"></span>
</span>
- <span class="ad-token-name">--ad-color-border-hairline</span>
+ <code>--ad-color-border-hairline</code>
</div>
</td>
- <td class="ad-table__code">neutral.200</td>
- <td class="ad-table__code">neutral_dark.200</td>
+ <td><code>neutral.200</code></td>
+ <td><code>neutral_dark.200</code></td>
<td>Card edges, row dividers, dialog dividers. The default edge everywhere.</td>
</tr>
<tr>
@@ -345,23 +342,21 @@ the right half is the dark one.
<span style="background: #c0bdb9"></span>
<span style="background: #44423f"></span>
</span>
- <span class="ad-token-name">--ad-color-border-default</span>
+ <code>--ad-color-border-default</code>
</div>
</td>
- <td class="ad-table__code">neutral.300</td>
- <td class="ad-table__code">neutral_dark.300</td>
+ <td><code>neutral.300</code></td>
+ <td><code>neutral_dark.300</code></td>
<td>Input and button borders — anything a reader has to recognize as a control.</td>
</tr>
</tbody>
</table>
-</div>
### Text
<p class="ad-muted">Three levels of emphasis, deliberately not four. Past three, a reader stops perceiving a hierarchy and starts perceiving noise.</p>
-<div class="ad-table-wrap">
- <table class="ad-table ad-table--compact">
+ <table>
<thead>
<tr>
<th scope="col">Token</th>
@@ -378,11 +373,11 @@ the right half is the dark one.
<span style="background: #100f0d"></span>
<span style="background: #e9e8e6"></span>
</span>
- <span class="ad-token-name">--ad-color-text-primary</span>
+ <code>--ad-color-text-primary</code>
</div>
</td>
- <td class="ad-table__code">neutral.900</td>
- <td class="ad-table__code">neutral_dark.900</td>
+ <td><code>neutral.900</code></td>
+ <td><code>neutral_dark.900</code></td>
<td>Body copy, headings, and every control label.</td>
</tr>
<tr>
@@ -392,11 +387,11 @@ the right half is the dark one.
<span style="background: #5f5d59"></span>
<span style="background: #a09e9a"></span>
</span>
- <span class="ad-token-name">--ad-color-text-secondary</span>
+ <code>--ad-color-text-secondary</code>
</div>
</td>
- <td class="ad-table__code">neutral.600</td>
- <td class="ad-table__code">neutral_dark.600</td>
+ <td><code>neutral.600</code></td>
+ <td><code>neutral_dark.600</code></td>
<td>Supporting copy, hints, mono meta values, type-label's color.</td>
</tr>
<tr>
@@ -406,23 +401,21 @@ the right half is the dark one.
<span style="background: #888681"></span>
<span style="background: #7c7a76"></span>
</span>
- <span class="ad-token-name">--ad-color-text-tertiary</span>
+ <code>--ad-color-text-tertiary</code>
</div>
</td>
- <td class="ad-table__code">neutral.500</td>
- <td class="ad-table__code">neutral_dark.500</td>
+ <td><code>neutral.500</code></td>
+ <td><code>neutral_dark.500</code></td>
<td>Placeholders and de-emphasized metadata only — never body text. This step is intentionally sub-AA against the page; see the caveat on the Color page.</td>
</tr>
</tbody>
</table>
-</div>
### Accent
<p class="ad-muted">One swappable accent per project, held constant across a whole view. `-ink` and `-soft` re-derive from `--ad-color-accent` via `color-mix()` exactly as the source computes them, so retheming the accent retones both automatically.</p>
-<div class="ad-table-wrap">
- <table class="ad-table ad-table--compact">
+ <table>
<thead>
<tr>
<th scope="col">Token</th>
@@ -439,11 +432,11 @@ the right half is the dark one.
<span style="background: #3a6a63"></span>
<span style="background: #3a6a63"></span>
</span>
- <span class="ad-token-name">--ad-color-accent</span>
+ <code>--ad-color-accent</code>
</div>
</td>
- <td class="ad-table__code">accent.teal</td>
- <td class="ad-table__code">accent.teal</td>
+ <td><code>accent.teal</code></td>
+ <td><code>accent.teal</code></td>
<td>Primary buttons, links, focus affordance, the active tab underline. Never two accents in one view.</td>
</tr>
<tr>
@@ -453,45 +446,43 @@ the right half is the dark one.
<span style="background: #ffffff"></span>
<span style="background: #ffffff"></span>
</span>
- <span class="ad-token-name">--ad-color-accent-fg</span>
+ <code>--ad-color-accent-fg</code>
</div>
</td>
- <td class="ad-table__code">#ffffff</td>
- <td class="ad-table__code">#ffffff</td>
+ <td><code>#ffffff</code></td>
+ <td><code>#ffffff</code></td>
<td>Label and icon color on a filled accent surface. Fixed white, not a ramp reference.</td>
</tr>
<tr>
<td>
<div class="ad-alias-row">
<span class="ad-alias-chip" aria-hidden="true" style="background: repeating-linear-gradient(45deg, var(--ad-color-surface-fill) 0 4px, var(--ad-color-surface-card) 4px 8px);"></span>
- <span class="ad-token-name">--ad-color-accent-ink</span>
+ <code>--ad-color-accent-ink</code>
</div>
</td>
- <td class="ad-table__code">color-mix(in oklch, var(--ad-color-accent) 78%, black)</td>
- <td class="ad-table__code">color-mix(in oklch, var(--ad-color-accent) 78%, black)</td>
+ <td><code>color-mix(in oklch, var(--ad-color-accent) 78%, black)</code></td>
+ <td><code>color-mix(in oklch, var(--ad-color-accent) 78%, black)</code></td>
<td>Text sitting on an `-soft` accent tint — an IconButton's active state, an accent avatar's initials.</td>
</tr>
<tr>
<td>
<div class="ad-alias-row">
<span class="ad-alias-chip" aria-hidden="true" style="background: repeating-linear-gradient(45deg, var(--ad-color-surface-fill) 0 4px, var(--ad-color-surface-card) 4px 8px);"></span>
- <span class="ad-token-name">--ad-color-accent-soft</span>
+ <code>--ad-color-accent-soft</code>
</div>
</td>
- <td class="ad-table__code">color-mix(in oklch, var(--ad-color-accent) 12%, white)</td>
- <td class="ad-table__code">color-mix(in oklch, var(--ad-color-accent) 12%, white)</td>
+ <td><code>color-mix(in oklch, var(--ad-color-accent) 12%, white)</code></td>
+ <td><code>color-mix(in oklch, var(--ad-color-accent) 12%, white)</code></td>
<td>The tint behind an accent Badge, a toggled IconButton, an accent avatar chip.</td>
</tr>
</tbody>
</table>
-</div>
### Status
<p class="ad-muted">Exactly two states, both fixed — they read the same in every project and are never swapped the way the accent is.</p>
-<div class="ad-table-wrap">
- <table class="ad-table ad-table--compact">
+ <table>
<thead>
<tr>
<th scope="col">Token</th>
@@ -508,22 +499,22 @@ the right half is the dark one.
<span style="background: #3f7a4a"></span>
<span style="background: #3f7a4a"></span>
</span>
- <span class="ad-token-name">--ad-color-success</span>
+ <code>--ad-color-success</code>
</div>
</td>
- <td class="ad-table__code">#3f7a4a</td>
- <td class="ad-table__code">#3f7a4a</td>
+ <td><code>#3f7a4a</code></td>
+ <td><code>#3f7a4a</code></td>
<td>Success text and icons — a completed task's check, a saved-state badge.</td>
</tr>
<tr>
<td>
<div class="ad-alias-row">
<span class="ad-alias-chip" aria-hidden="true" style="background: repeating-linear-gradient(45deg, var(--ad-color-surface-fill) 0 4px, var(--ad-color-surface-card) 4px 8px);"></span>
- <span class="ad-token-name">--ad-color-success-bg</span>
+ <code>--ad-color-success-bg</code>
</div>
</td>
- <td class="ad-table__code">color-mix(in oklch, var(--ad-color-success) 12%, white)</td>
- <td class="ad-table__code">color-mix(in oklch, var(--ad-color-success) 12%, white)</td>
+ <td><code>color-mix(in oklch, var(--ad-color-success) 12%, white)</code></td>
+ <td><code>color-mix(in oklch, var(--ad-color-success) 12%, white)</code></td>
<td>Tint behind a success Badge. Mixed against literal white, so it stays constant across themes.</td>
</tr>
<tr>
@@ -533,27 +524,26 @@ the right half is the dark one.
<span style="background: #ad3a2c"></span>
<span style="background: #ad3a2c"></span>
</span>
- <span class="ad-token-name">--ad-color-danger</span>
+ <code>--ad-color-danger</code>
</div>
</td>
- <td class="ad-table__code">#ad3a2c</td>
- <td class="ad-table__code">#ad3a2c</td>
+ <td><code>#ad3a2c</code></td>
+ <td><code>#ad3a2c</code></td>
<td>Overdue/destructive text, the Toast undo action, a danger Button's fill.</td>
</tr>
<tr>
<td>
<div class="ad-alias-row">
<span class="ad-alias-chip" aria-hidden="true" style="background: repeating-linear-gradient(45deg, var(--ad-color-surface-fill) 0 4px, var(--ad-color-surface-card) 4px 8px);"></span>
- <span class="ad-token-name">--ad-color-danger-bg</span>
+ <code>--ad-color-danger-bg</code>
</div>
</td>
- <td class="ad-table__code">color-mix(in oklch, var(--ad-color-danger) 10%, white)</td>
- <td class="ad-table__code">color-mix(in oklch, var(--ad-color-danger) 10%, white)</td>
+ <td><code>color-mix(in oklch, var(--ad-color-danger) 10%, white)</code></td>
+ <td><code>color-mix(in oklch, var(--ad-color-danger) 10%, white)</code></td>
<td>Tint behind a danger Badge. Mixed against literal white, so it stays constant across themes.</td>
</tr>
</tbody>
</table>
-</div>
## Contrast that has to hold
@@ -567,14 +557,13 @@ and checked by hand rather than by a build-time test.
<p class="ad-muted">WCAG 2.1 SC 1.4.3 (normal-size text)</p>
-<div class="ad-table-wrap">
- <table class="ad-table ad-table--compact">
+ <table>
<thead>
<tr>
<th scope="col">Pairing</th>
<th scope="col">Where it shows up</th>
- <th scope="col" class="ad-table__num">Light</th>
- <th scope="col" class="ad-table__num">Dark</th>
+ <th scope="col" class="num">Light</th>
+ <th scope="col" class="num">Dark</th>
</tr>
</thead>
<tbody>
@@ -582,60 +571,59 @@ and checked by hand rather than by a build-time test.
<td>
<span class="ad-alias-row">
<span class="ad-alias-chip" aria-hidden="true" style="background: #faf9f7; color: #100f0d; display: grid; place-items: center; font-size: 10px;">Aa</span>
- <span class="ad-token-name">color-text-primary<br><span class="ad-subtle">on color-surface-page</span></span>
+ <code>color-text-primary<br><span class="ad-subtle">on color-surface-page</span></code>
</span>
</td>
<td>Body copy</td>
- <td class="ad-table__num">18.21</td>
- <td class="ad-table__num">15.86</td>
+ <td class="num">18.21</td>
+ <td class="num">15.86</td>
</tr>
<tr>
<td>
<span class="ad-alias-row">
<span class="ad-alias-chip" aria-hidden="true" style="background: #faf9f7; color: #5f5d59; display: grid; place-items: center; font-size: 10px;">Aa</span>
- <span class="ad-token-name">color-text-secondary<br><span class="ad-subtle">on color-surface-page</span></span>
+ <code>color-text-secondary<br><span class="ad-subtle">on color-surface-page</span></code>
</span>
</td>
<td>Supporting copy</td>
- <td class="ad-table__num">6.24</td>
- <td class="ad-table__num">7.26</td>
+ <td class="num">6.24</td>
+ <td class="num">7.26</td>
</tr>
<tr>
<td>
<span class="ad-alias-row">
<span class="ad-alias-chip" aria-hidden="true" style="background: #3a6a63; color: #ffffff; display: grid; place-items: center; font-size: 10px;">Aa</span>
- <span class="ad-token-name">color-accent-fg<br><span class="ad-subtle">on color-accent</span></span>
+ <code>color-accent-fg<br><span class="ad-subtle">on color-accent</span></code>
</span>
</td>
<td>The label on a primary or dark-variant Button.</td>
- <td class="ad-table__num">6.14</td>
- <td class="ad-table__num">6.14</td>
+ <td class="num">6.14</td>
+ <td class="num">6.14</td>
</tr>
<tr>
<td>
<span class="ad-alias-row">
<span class="ad-alias-chip" aria-hidden="true" style="background: #ffffff; color: #3f7a4a; display: grid; place-items: center; font-size: 10px;">Aa</span>
- <span class="ad-token-name">color-success<br><span class="ad-subtle">on color-accent-fg</span></span>
+ <code>color-success<br><span class="ad-subtle">on color-accent-fg</span></code>
</span>
</td>
<td>Success text on a light backdrop — proxied by color-accent-fg</td>
- <td class="ad-table__num">5.13</td>
- <td class="ad-table__num">5.13</td>
+ <td class="num">5.13</td>
+ <td class="num">5.13</td>
</tr>
<tr>
<td>
<span class="ad-alias-row">
<span class="ad-alias-chip" aria-hidden="true" style="background: #ffffff; color: #ad3a2c; display: grid; place-items: center; font-size: 10px;">Aa</span>
- <span class="ad-token-name">color-danger<br><span class="ad-subtle">on color-accent-fg</span></span>
+ <code>color-danger<br><span class="ad-subtle">on color-accent-fg</span></code>
</span>
</td>
<td>Overdue/destructive text on a light backdrop</td>
- <td class="ad-table__num">6.14</td>
- <td class="ad-table__num">6.14</td>
+ <td class="num">6.14</td>
+ <td class="num">6.14</td>
</tr>
</tbody>
</table>
-</div>
<div class="ad-callout">
<p style="margin:0; font-size: var(--ad-step--1);">
diff --git a/_foundations/elevation.md b/_foundations/elevation.md
index 3ddfed4..ef35bda 100644
--- a/_foundations/elevation.md
+++ b/_foundations/elevation.md
@@ -16,12 +16,12 @@ is the default everywhere. Shadow is the exception, not the baseline.
<ul class="ad-elevation-grid">
<li>
<div class="ad-elevation-demo" style="box-shadow: 0 1px 2px rgba(20, 18, 14, 0.05);"></div>
- <div class="ad-specimen__meta"><span class="ad-token-name">shadow-sm</span></div>
+ <div class="ad-specimen__meta"><code>shadow-sm</code></div>
<p class="ad-muted" style="font-size: var(--ad-step--2); margin: var(--ad-space-3xs) 0 0;">Barely-there separation — a Switch thumb, a focused Input.</p>
</li>
<li>
<div class="ad-elevation-demo" style="box-shadow: 0 2px 6px rgba(20, 18, 14, 0.06), 0 10px 24px -12px rgba(20, 18, 14, 0.18);"></div>
- <div class="ad-specimen__meta"><span class="ad-token-name">shadow-float</span></div>
+ <div class="ad-specimen__meta"><code>shadow-float</code></div>
<p class="ad-muted" style="font-size: var(--ad-step--2); margin: var(--ad-space-3xs) 0 0;">The one floating layer in a view. Apply it to at most one element on screen at a time.</p>
</li>
</ul>
@@ -29,30 +29,30 @@ is the default everywhere. Shadow is the exception, not the baseline.
<figure class="example">
<figcaption>Flat vs. float</figcaption>
<div data-layout="grid" data-surface="sunken">
-<article class="ad-card">
- <div class="ad-card__body">
- <h3 class="ad-card__title" style="font-size: var(--ad-step--1);">Flat</h3>
- <p class="ad-card__text">The default. A hairline border, no shadow.</p>
+<article class="card">
+ <div class="card-body">
+ <h3 style="margin:0; font: 600 var(--ad-step--1)/1.3 var(--ad-font-sans);">Flat</h3>
+ <p style="margin:0; color: var(--ad-color-text-secondary); font-size: var(--ad-step--1);">The default. A hairline border, no shadow.</p>
</div>
</article>
-<article class="ad-card ad-card--elevated">
- <div class="ad-card__body">
- <h3 class="ad-card__title" style="font-size: var(--ad-step--1);">Elevated</h3>
- <p class="ad-card__text">shadow-float. Reserved for one element per view — a Dialog, a Toast.</p>
+<article class="card" data-elevated>
+ <div class="card-body">
+ <h3 style="margin:0; font: 600 var(--ad-step--1)/1.3 var(--ad-font-sans);">Elevated</h3>
+ <p style="margin:0; color: var(--ad-color-text-secondary); font-size: var(--ad-step--1);">shadow-float. Reserved for one element per view — a Dialog, a Toast.</p>
</div>
</article>
</div>
<details><summary>Markup</summary>
-<pre><code><article class="ad-card">
- <div class="ad-card__body">
- <h3 class="ad-card__title" style="font-size: var(--ad-step--1);">Flat</h3>
- <p class="ad-card__text">The default. A hairline border, no shadow.</p>
+<pre><code><article class="card">
+ <div class="card-body">
+ <h3>Flat</h3>
+ <p>The default. A hairline border, no shadow.</p>
</div>
</article>
-<article class="ad-card ad-card--elevated">
- <div class="ad-card__body">
- <h3 class="ad-card__title" style="font-size: var(--ad-step--1);">Elevated</h3>
- <p class="ad-card__text">shadow-float. Reserved for one element per view — a Dialog, a Toast.</p>
+<article class="card" data-elevated>
+ <div class="card-body">
+ <h3>Elevated</h3>
+ <p>shadow-float. Reserved for one element per view — a Dialog, a Toast.</p>
</div>
</article></code></pre>
</details>
diff --git a/_foundations/motion.md b/_foundations/motion.md
index d53cc70..5d294c9 100644
--- a/_foundations/motion.md
+++ b/_foundations/motion.md
@@ -11,23 +11,21 @@ summary: >-
Short ease transitions on hover and focus only — no bounce, no springy
easing, no page-transition choreography.
-<div class="ad-table-wrap">
- <table class="ad-table ad-table--compact">
+ <table>
<thead><tr><th scope="col">Token</th><th scope="col">Value</th><th scope="col">Use for</th></tr></thead>
<tbody>
<tr>
- <td class="ad-token-name">--ad-dur-fast</td>
- <td class="ad-table__code">0.12s</td>
+ <td><code>--ad-dur-fast</code></td>
+ <td><code>0.12s</code></td>
<td>Hover and focus color/border transitions — the default.</td>
</tr>
<tr>
- <td class="ad-token-name">--ad-dur-base</td>
- <td class="ad-table__code">0.16s</td>
+ <td><code>--ad-dur-base</code></td>
+ <td><code>0.16s</code></td>
<td>Switch track and thumb — the one moving part in the system.</td>
</tr>
</tbody>
</table>
-</div>
## One easing
@@ -38,7 +36,7 @@ There is exactly one curve in the system. Hover the tile below to run it.
<div class="ad-motion-demo" tabindex="0">
<span class="ad-motion-demo__dot"></span>
</div>
- <div class="ad-specimen__meta"><span class="ad-token-name">--ad-ease</span></div>
+ <div class="ad-specimen__meta"><code>--ad-ease</code></div>
<p class="ad-muted" style="font-size: var(--ad-step--2); margin: var(--ad-space-3xs) 0 0;">The only easing curve in the system. Fast out, settles gently, applied to every transition.</p>
</li>
</ul>
@@ -65,27 +63,27 @@ instead of traveling.
<figure class="example">
<figcaption>Motion in practice</figcaption>
<div>
-<label class="ad-switch">
- <input type="checkbox" checked>
- <span class="ad-switch__track"></span>
- <span>Dark mode</span>
+<label class="switch">
+ <input type="checkbox" role="switch" checked>
+ <span></span>
+ Dark mode
</label>
-<label class="ad-switch">
- <input type="checkbox">
- <span class="ad-switch__track"></span>
- <span>Weekly digest</span>
+<label class="switch">
+ <input type="checkbox" role="switch">
+ <span></span>
+ Weekly digest
</label>
</div>
<details><summary>Markup</summary>
-<pre><code><label class="ad-switch">
- <input type="checkbox" checked>
- <span class="ad-switch__track"></span>
- <span>Dark mode</span>
+<pre><code><label class="switch">
+ <input type="checkbox" role="switch" checked>
+ <span></span>
+ Dark mode
</label>
-<label class="ad-switch">
- <input type="checkbox">
- <span class="ad-switch__track"></span>
- <span>Weekly digest</span>
+<label class="switch">
+ <input type="checkbox" role="switch">
+ <span></span>
+ Weekly digest
</label></code></pre>
</details>
</figure>
diff --git a/_foundations/space.md b/_foundations/space.md
index e7e4850..b27bb3c 100644
--- a/_foundations/space.md
+++ b/_foundations/space.md
@@ -9,47 +9,47 @@ summary: >-
<div class="ad-ruler">
<div class="ad-ruler__row">
- <span class="ad-token-name">space-3xs</span>
+ <code>space-3xs</code>
<span class="ad-ruler__num">5px</span>
<span class="ad-ruler__bar" style="width: clamp(0.3125rem, 0.3125rem + 0vw, 0.3125rem); min-width: 1px;"></span>
</div>
<div class="ad-ruler__row">
- <span class="ad-token-name">space-2xs</span>
+ <code>space-2xs</code>
<span class="ad-ruler__num">9 → 10px</span>
<span class="ad-ruler__bar" style="width: clamp(0.5625rem, 0.5369rem + 0.1136vw, 0.625rem); min-width: 1px;"></span>
</div>
<div class="ad-ruler__row">
- <span class="ad-token-name">space-xs</span>
+ <code>space-xs</code>
<span class="ad-ruler__num">14 → 15px</span>
<span class="ad-ruler__bar" style="width: clamp(0.875rem, 0.8494rem + 0.1136vw, 0.9375rem); min-width: 1px;"></span>
</div>
<div class="ad-ruler__row">
- <span class="ad-token-name">space-s</span>
+ <code>space-s</code>
<span class="ad-ruler__num">18 → 20px</span>
<span class="ad-ruler__bar" style="width: clamp(1.125rem, 1.0739rem + 0.2273vw, 1.25rem); min-width: 1px;"></span>
</div>
<div class="ad-ruler__row">
- <span class="ad-token-name">space-m</span>
+ <code>space-m</code>
<span class="ad-ruler__num">27 → 30px</span>
<span class="ad-ruler__bar" style="width: clamp(1.6875rem, 1.6108rem + 0.3409vw, 1.875rem); min-width: 1px;"></span>
</div>
<div class="ad-ruler__row">
- <span class="ad-token-name">space-l</span>
+ <code>space-l</code>
<span class="ad-ruler__num">36 → 40px</span>
<span class="ad-ruler__bar" style="width: clamp(2.25rem, 2.1477rem + 0.4545vw, 2.5rem); min-width: 1px;"></span>
</div>
<div class="ad-ruler__row">
- <span class="ad-token-name">space-xl</span>
+ <code>space-xl</code>
<span class="ad-ruler__num">54 → 60px</span>
<span class="ad-ruler__bar" style="width: clamp(3.375rem, 3.2216rem + 0.6818vw, 3.75rem); min-width: 1px;"></span>
</div>
<div class="ad-ruler__row">
- <span class="ad-token-name">space-2xl</span>
+ <code>space-2xl</code>
<span class="ad-ruler__num">72 → 80px</span>
<span class="ad-ruler__bar" style="width: clamp(4.5rem, 4.2955rem + 0.9091vw, 5rem); min-width: 1px;"></span>
</div>
<div class="ad-ruler__row">
- <span class="ad-token-name">space-3xl</span>
+ <code>space-3xl</code>
<span class="ad-ruler__num">108 → 120px</span>
<span class="ad-ruler__bar" style="width: clamp(6.75rem, 6.4432rem + 1.3636vw, 7.5rem); min-width: 1px;"></span>
</div>
@@ -71,42 +71,42 @@ summary: >-
<figure class="example">
<figcaption>The same card at three densities</figcaption>
<div data-layout="grid">
-<article class="ad-card">
- <div class="ad-card__body" style="gap: var(--ad-space-3xs); padding: var(--ad-space-xs);">
- <h3 class="ad-card__title" style="font-size: var(--ad-step--1);">Compact</h3>
- <p class="ad-card__text">space-xs padding, space-3xs gap. For dense lists.</p>
+<article class="card">
+ <div class="card-body" style="gap: var(--ad-space-3xs); padding: var(--ad-space-xs);">
+ <h3 style="margin:0; font: 600 var(--ad-step--1)/1.3 var(--ad-font-sans);">Compact</h3>
+ <p style="margin:0; color: var(--ad-color-text-secondary); font-size: var(--ad-step--1);">space-xs padding, space-3xs gap. For dense lists.</p>
</div>
</article>
-<article class="ad-card">
- <div class="ad-card__body">
- <h3 class="ad-card__title" style="font-size: var(--ad-step--1);">Default</h3>
- <p class="ad-card__text">space-m padding, space-2xs gap. What Card ships with.</p>
+<article class="card">
+ <div class="card-body">
+ <h3 style="margin:0; font: 600 var(--ad-step--1)/1.3 var(--ad-font-sans);">Default</h3>
+ <p style="margin:0; color: var(--ad-color-text-secondary); font-size: var(--ad-step--1);">space-m padding, space-2xs gap. What Card ships with.</p>
</div>
</article>
-<article class="ad-card">
- <div class="ad-card__body" style="gap: var(--ad-space-s); padding: var(--ad-space-l);">
- <h3 class="ad-card__title" style="font-size: var(--ad-step--1);">Roomy</h3>
- <p class="ad-card__text">space-l padding, space-s gap. For a card that is the whole screen.</p>
+<article class="card">
+ <div class="card-body" style="gap: var(--ad-space-s); padding: var(--ad-space-l);">
+ <h3 style="margin:0; font: 600 var(--ad-step--1)/1.3 var(--ad-font-sans);">Roomy</h3>
+ <p style="margin:0; color: var(--ad-color-text-secondary); font-size: var(--ad-step--1);">space-l padding, space-s gap. For a card that is the whole screen.</p>
</div>
</article>
</div>
<details><summary>Markup</summary>
-<pre><code><article class="ad-card">
- <div class="ad-card__body" style="gap: var(--ad-space-3xs); padding: var(--ad-space-xs);">
- <h3 class="ad-card__title" style="font-size: var(--ad-step--1);">Compact</h3>
- <p class="ad-card__text">space-xs padding, space-3xs gap. For dense lists.</p>
+<pre><code><article class="card">
+ <div class="card-body" style="gap: var(--ad-space-3xs); padding: var(--ad-space-xs);">
+ <h3>Compact</h3>
+ <p>space-xs padding, space-3xs gap. For dense lists.</p>
</div>
</article>
-<article class="ad-card">
- <div class="ad-card__body">
- <h3 class="ad-card__title" style="font-size: var(--ad-step--1);">Default</h3>
- <p class="ad-card__text">space-m padding, space-2xs gap. What Card ships with.</p>
+<article class="card">
+ <div class="card-body">
+ <h3>Default</h3>
+ <p>space-m padding, space-2xs gap. What Card ships with.</p>
</div>
</article>
-<article class="ad-card">
- <div class="ad-card__body" style="gap: var(--ad-space-s); padding: var(--ad-space-l);">
- <h3 class="ad-card__title" style="font-size: var(--ad-step--1);">Roomy</h3>
- <p class="ad-card__text">space-l padding, space-s gap. For a card that is the whole screen.</p>
+<article class="card">
+ <div class="card-body" style="gap: var(--ad-space-s); padding: var(--ad-space-l);">
+ <h3>Roomy</h3>
+ <p>space-l padding, space-s gap. For a card that is the whole screen.</p>
</div>
</article></code></pre>
</details>
@@ -122,7 +122,7 @@ except true pills.
<li>
<div class="ad-radius-demo" style="border-radius: 4px;"></div>
<div class="ad-specimen__meta">
- <span class="ad-token-name">radius-sm</span>
+ <code>radius-sm</code>
<span>4px</span>
</div>
<p class="ad-muted" style="font-size: var(--ad-step--2); margin: var(--ad-space-3xs) 0 0;">Buttons, inputs, badges, tags.</p>
@@ -130,7 +130,7 @@ except true pills.
<li>
<div class="ad-radius-demo" style="border-radius: 6px;"></div>
<div class="ad-specimen__meta">
- <span class="ad-token-name">radius-md</span>
+ <code>radius-md</code>
<span>6px</span>
</div>
<p class="ad-muted" style="font-size: var(--ad-step--2); margin: var(--ad-space-3xs) 0 0;">IconButton, the segmented-tabs track, avatar chips.</p>
@@ -138,7 +138,7 @@ except true pills.
<li>
<div class="ad-radius-demo" style="border-radius: 10px;"></div>
<div class="ad-specimen__meta">
- <span class="ad-token-name">radius-lg</span>
+ <code>radius-lg</code>
<span>10px</span>
</div>
<p class="ad-muted" style="font-size: var(--ad-step--2); margin: var(--ad-space-3xs) 0 0;">Cards, dialogs, toasts, photo slots — the system's "surface" radius.</p>
@@ -146,7 +146,7 @@ except true pills.
<li>
<div class="ad-radius-demo" style="border-radius: 999px;"></div>
<div class="ad-specimen__meta">
- <span class="ad-token-name">radius-full</span>
+ <code>radius-full</code>
<span>999px</span>
</div>
<p class="ad-muted" style="font-size: var(--ad-step--2); margin: var(--ad-space-3xs) 0 0;">True pills only — Badge, Tag, Switch, round avatars.</p>
diff --git a/_foundations/typography.md b/_foundations/typography.md
index 046fd87..38725b1 100644
--- a/_foundations/typography.md
+++ b/_foundations/typography.md
@@ -10,7 +10,7 @@ summary: >-
<div class="ad-specimen">
<div class="ad-specimen__meta">
- <span class="ad-token-name">--ad-font-sans</span>
+ <code>--ad-font-sans</code>
<span>Interface text — every component, every screen. Web-safe; nothing is loaded for it.</span>
</div>
<div class="ad-specimen__sample" style="font-family: var(--ad-font-sans); font-size: 1.5rem;">
@@ -19,7 +19,7 @@ summary: >-
</div>
<div class="ad-specimen">
<div class="ad-specimen__meta">
- <span class="ad-token-name">--ad-font-mono</span>
+ <code>--ad-font-mono</code>
<span>Every label, meta value, technical/catalog value, and code sample. The system's one loaded webfont.</span>
</div>
<div class="ad-specimen__sample" style="font-family: var(--ad-font-mono); font-size: 1.5rem;">
@@ -28,7 +28,7 @@ summary: >-
</div>
<div class="ad-specimen">
<div class="ad-specimen__meta">
- <span class="ad-token-name">--ad-font-serif</span>
+ <code>--ad-font-serif</code>
<span>Opt-in reading variant for a project that wants an editorial, archival feel (domus-style). Not loaded by default; web-safe only.</span>
</div>
<div class="ad-specimen__sample" style="font-family: var(--ad-font-serif); font-size: 1.5rem;">
@@ -53,7 +53,7 @@ breakpoint in between.
<div class="ad-specimen">
<div class="ad-specimen__meta">
- <span class="ad-token-name">--ad-step--2</span>
+ <code>--ad-step--2</code>
<span>12.5 → 12.8px</span>
<span>type-label — eyebrows, catalog labels, meta.</span>
</div>
@@ -63,7 +63,7 @@ breakpoint in between.
</div>
<div class="ad-specimen">
<div class="ad-specimen__meta">
- <span class="ad-token-name">--ad-step--1</span>
+ <code>--ad-step--1</code>
<span>15 → 16px</span>
<span>type-body-sm and type-mono — secondary copy, technical values.</span>
</div>
@@ -73,7 +73,7 @@ breakpoint in between.
</div>
<div class="ad-specimen">
<div class="ad-specimen__meta">
- <span class="ad-token-name">--ad-step-0</span>
+ <code>--ad-step-0</code>
<span>18 → 20px</span>
<span>type-body — the base size, and the body element's own size.</span>
</div>
@@ -83,7 +83,7 @@ breakpoint in between.
</div>
<div class="ad-specimen">
<div class="ad-specimen__meta">
- <span class="ad-token-name">--ad-step-1</span>
+ <code>--ad-step-1</code>
<span>22 → 25px</span>
<span>type-h3 — card and subsection headings.</span>
</div>
@@ -93,7 +93,7 @@ breakpoint in between.
</div>
<div class="ad-specimen">
<div class="ad-specimen__meta">
- <span class="ad-token-name">--ad-step-2</span>
+ <code>--ad-step-2</code>
<span>26 → 31px</span>
<span>type-h2 — section headings.</span>
</div>
@@ -103,7 +103,7 @@ breakpoint in between.
</div>
<div class="ad-specimen">
<div class="ad-specimen__meta">
- <span class="ad-token-name">--ad-step-3</span>
+ <code>--ad-step-3</code>
<span>31 → 39px</span>
<span>type-h1 — screen titles.</span>
</div>
@@ -113,7 +113,7 @@ breakpoint in between.
</div>
<div class="ad-specimen">
<div class="ad-specimen__meta">
- <span class="ad-token-name">--ad-step-4</span>
+ <code>--ad-step-4</code>
<span>37 → 49px</span>
<span>type-display — one hero title per screen, at most.</span>
</div>
@@ -123,7 +123,7 @@ breakpoint in between.
</div>
<div class="ad-specimen">
<div class="ad-specimen__meta">
- <span class="ad-token-name">--ad-step-5</span>
+ <code>--ad-step-5</code>
<span>45 → 61px</span>
<span>Reserved headroom. No compound style claims it yet.</span>
</div>
@@ -138,77 +138,75 @@ Real, specific numbers from the source — not a generic ramp compressed to
fit. Each class sets family, size, weight, line-height, and (where the
source specifies one) letter-spacing in a single declaration.
-<div class="ad-table-wrap">
- <table class="ad-table ad-table--compact">
+ <table>
<thead><tr><th scope="col">Class</th><th scope="col">Weight</th><th scope="col">Size</th><th scope="col">Line-height</th><th scope="col">Tracking</th><th scope="col">Use for</th></tr></thead>
<tbody>
<tr>
- <td class="ad-token-name">.type-display</td>
+ <td><code>.type-display</code></td>
<td>600</td>
- <td class="ad-table__code">--ad-step-4</td>
+ <td><code>--ad-step-4</code></td>
<td>1.08</td>
<td>-0.015em</td>
<td>Page-level hero title, once per screen.</td>
</tr>
<tr>
- <td class="ad-token-name">.type-h1</td>
+ <td><code>.type-h1</code></td>
<td>600</td>
- <td class="ad-table__code">--ad-step-3</td>
+ <td><code>--ad-step-3</code></td>
<td>1.12</td>
<td>-0.012em</td>
<td>Screen title.</td>
</tr>
<tr>
- <td class="ad-token-name">.type-h2</td>
+ <td><code>.type-h2</code></td>
<td>600</td>
- <td class="ad-table__code">--ad-step-2</td>
+ <td><code>--ad-step-2</code></td>
<td>1.16</td>
<td>-0.01em</td>
<td>Section heading.</td>
</tr>
<tr>
- <td class="ad-token-name">.type-h3</td>
+ <td><code>.type-h3</code></td>
<td>600</td>
- <td class="ad-table__code">--ad-step-1</td>
+ <td><code>--ad-step-1</code></td>
<td>1.25</td>
<td>normal</td>
<td>Card / subsection heading.</td>
</tr>
<tr>
- <td class="ad-token-name">.type-body</td>
+ <td><code>.type-body</code></td>
<td>400</td>
- <td class="ad-table__code">--ad-step-0</td>
+ <td><code>--ad-step-0</code></td>
<td>1.55</td>
<td>normal</td>
<td>Body copy.</td>
</tr>
<tr>
- <td class="ad-token-name">.type-body-sm</td>
+ <td><code>.type-body-sm</code></td>
<td>400</td>
- <td class="ad-table__code">--ad-step--1</td>
+ <td><code>--ad-step--1</code></td>
<td>1.5</td>
<td>normal</td>
<td>Secondary copy, hints.</td>
</tr>
<tr>
- <td class="ad-token-name">.type-label</td>
+ <td><code>.type-label</code></td>
<td>500</td>
- <td class="ad-table__code">--ad-step--2</td>
+ <td><code>--ad-step--2</code></td>
<td>1</td>
<td>0.08em</td>
<td>Eyebrows, catalog labels, meta — SAVED, RECOMMENDED, OVERDUE.</td>
</tr>
<tr>
- <td class="ad-token-name">.type-mono</td>
+ <td><code>.type-mono</code></td>
<td>400</td>
- <td class="ad-table__code">--ad-step--1</td>
+ <td><code>--ad-step--1</code></td>
<td>1.5</td>
<td>normal</td>
<td>Technical values, IDs, dates, code.</td>
</tr>
</tbody>
</table>
-</div>
<figure class="example">
<figcaption>All eight, in order</figcaption>
diff --git a/_includes/sidebar.erb b/_includes/sidebar.erb
index 2fa2c61..d755172 100644
--- a/_includes/sidebar.erb
+++ b/_includes/sidebar.erb
@@ -1,5 +1,5 @@
<aside class="ad-doc-sidebar" data-sidebar data-collapsed="true">
- <button type="button" class="ad-btn ad-btn--secondary ad-btn--sm ad-doc-nav-toggle"
+ <button type="button" class="ad-doc-nav-toggle" data-size="sm"
data-nav-toggle aria-expanded="false" aria-controls="ad-doc-nav">
<span>Browse the system</span>
<span aria-hidden="true">☰</span>
diff --git a/_includes/theme-toggle.erb b/_includes/theme-toggle.erb
index 9a51442..7e025c1 100644
--- a/_includes/theme-toggle.erb
+++ b/_includes/theme-toggle.erb
@@ -1,4 +1,4 @@
-<button type="button" class="ad-btn ad-btn--ghost ad-btn--sm" data-theme-toggle
+<button type="button" data-variant="ghost" data-size="sm" data-theme-toggle
aria-label="Switch color theme" title="Switch color theme">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" aria-hidden="true" data-theme-icon="light">
<circle cx="12" cy="12" r="4.5" stroke="currentColor" stroke-width="2"/>
diff --git a/_layouts/default.erb b/_layouts/default.erb
index c012298..86d9845 100644
--- a/_layouts/default.erb
+++ b/_layouts/default.erb
@@ -11,7 +11,7 @@
<%= render("wordmark") %>
<div class="ad-doc-header__actions">
<%= render("theme-toggle") %>
- <a class="ad-btn ad-btn--secondary ad-btn--sm"
+ <a class="btn" data-size="sm"
href="https://github.com/<%= site.repository %>">Source</a>
</div>
</div>
diff --git a/_patterns/archive.md b/_patterns/archive.md
index fab3759..f55ffc1 100644
--- a/_patterns/archive.md
+++ b/_patterns/archive.md
@@ -23,35 +23,35 @@ document archive) would take if it were rebuilt on this token set, at its
<span style="font-weight:700; font-size: var(--ad-step-0);">Recent</span>
<span class="type-label" style="margin-left:auto;">3 tracked</span>
</div>
- <div class="ad-card">
- <div class="ad-card-row">
- <span class="ad-avatar ad-avatar--square"><svg width="17" height="17" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M21 8 12 3 3 8v8l9 5 9-5V8Z" /><path d="M3 8l9 5 9-5M12 13v8" /></svg></span>
+ <ul class="card">
+ <li>
+ <span class="avatar" data-square><svg width="17" height="17" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M21 8 12 3 3 8v8l9 5 9-5V8Z" /><path d="M3 8l9 5 9-5M12 13v8" /></svg></span>
<div style="flex:1; min-width:0;">
<div style="font-weight:550; font-size: var(--ad-step--1); white-space:nowrap; overflow:hidden; text-overflow:ellipsis;">Dishwasher manual</div>
<div class="type-label" style="margin-top:2px;">PDF · 2.1MB</div>
</div>
<span class="type-mono" style="color: var(--ad-color-text-tertiary); font-size: var(--ad-step--2);">2h</span>
- </div>
- <div class="ad-card-row">
- <span class="ad-avatar ad-avatar--square"><svg width="17" height="17" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M21 8 12 3 3 8v8l9 5 9-5V8Z" /><path d="M3 8l9 5 9-5M12 13v8" /></svg></span>
+ </li>
+ <li>
+ <span class="avatar" data-square><svg width="17" height="17" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M21 8 12 3 3 8v8l9 5 9-5V8Z" /><path d="M3 8l9 5 9-5M12 13v8" /></svg></span>
<div style="flex:1; min-width:0;">
<div style="font-weight:550; font-size: var(--ad-step--1);">Sofa receipt</div>
<div class="type-label" style="margin-top:2px;">JPG · 640KB</div>
</div>
<span class="type-mono" style="color: var(--ad-color-text-tertiary); font-size: var(--ad-step--2);">1d</span>
- </div>
- <div class="ad-card-row">
- <span class="ad-avatar ad-avatar--square"><svg width="17" height="17" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M21 8 12 3 3 8v8l9 5 9-5V8Z" /><path d="M3 8l9 5 9-5M12 13v8" /></svg></span>
+ </li>
+ <li>
+ <span class="avatar" data-square><svg width="17" height="17" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M21 8 12 3 3 8v8l9 5 9-5V8Z" /><path d="M3 8l9 5 9-5M12 13v8" /></svg></span>
<div style="flex:1; min-width:0;">
<div style="font-weight:550; font-size: var(--ad-step--1);">Passport scan</div>
<div class="type-label" style="margin-top:2px;">PDF · 890KB</div>
</div>
<span class="type-mono" style="color: var(--ad-color-text-tertiary); font-size: var(--ad-step--2);">4d</span>
- </div>
- </div>
+ </li>
+ </ul>
</div>
<div style="position:absolute; left:0; right:0; bottom:0; display:flex; justify-content:center; padding: 14px 20px 22px; background: linear-gradient(to top, var(--ad-color-surface-page) 65%, transparent);">
- <button type="button" class="ad-btn ad-btn--primary ad-btn--lg ad-btn--block">
+ <button type="button" data-variant="primary" data-size="lg" data-block>
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M3 8a2 2 0 0 1 2-2h1.5l1-1.5h9l1 1.5H20a2 2 0 0 1 2 2v10a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8Z" /><circle cx="12" cy="13" r="3.5" /></svg>
Take a photo
</button>
@@ -59,7 +59,7 @@ document archive) would take if it were rebuilt on this token set, at its
</div>
<div style="width: 300px; height: 560px; background: var(--ad-color-surface-page); border-radius: var(--ad-radius-lg); box-shadow: var(--ad-shadow-float); overflow:hidden; display:flex; flex-direction:column;">
<header style="display:flex; align-items:center; gap:8px; padding: 16px var(--ad-space-m) var(--ad-space-2xs);">
- <button type="button" class="ad-icon-btn ad-icon-btn--sm" aria-label="Back"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.9" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M15 18l-6-6 6-6" /></svg></button>
+ <button type="button" class="icon-button" data-size="sm" aria-label="Back"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.9" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M15 18l-6-6 6-6" /></svg></button>
<span style="font-weight:600; font-size: var(--ad-step-0);">Save</span>
</header>
<div style="flex:1; padding: 0 var(--ad-space-m);">
@@ -67,34 +67,34 @@ document archive) would take if it were rebuilt on this token set, at its
<svg width="17" height="17" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M21 8 12 3 3 8v8l9 5 9-5V8Z" /><path d="M3 8l9 5 9-5M12 13v8" /></svg>
<span class="type-label">Photo preview</span>
</div>
- <div style="margin-top: var(--ad-space-m); display:flex; flex-direction:column; gap: var(--ad-space-2xs);">
- <span class="type-label">Name</span>
- <input class="ad-input" placeholder="Dishwasher manual" />
- </div>
+ <label class="field" style="margin-top: var(--ad-space-m);">
+ Name
+ <input placeholder="Dishwasher manual" />
+ </label>
</div>
<div style="padding: var(--ad-space-m); display:flex; gap: var(--ad-space-2xs);">
- <button type="button" class="ad-btn" style="flex:1;">Retake</button>
- <button type="button" class="ad-btn ad-btn--primary" style="flex:1;">Save</button>
+ <button type="button" style="flex:1;">Retake</button>
+ <button type="button" data-variant="primary" style="flex:1;">Save</button>
</div>
</div>
<div style="width: 300px; height: 560px; background: var(--ad-color-surface-page); border-radius: var(--ad-radius-lg); box-shadow: var(--ad-shadow-float); overflow:hidden; display:flex; flex-direction:column;">
<header style="display:flex; align-items:center; gap:8px; padding: 16px var(--ad-space-m) var(--ad-space-2xs);">
- <button type="button" class="ad-icon-btn ad-icon-btn--sm" aria-label="Back"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.9" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M15 18l-6-6 6-6" /></svg></button>
+ <button type="button" class="icon-button" data-size="sm" aria-label="Back"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.9" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M15 18l-6-6 6-6" /></svg></button>
<span class="type-label">Archive</span>
</header>
<div style="flex:1; overflow:hidden; padding: 0 var(--ad-space-m) var(--ad-space-l);">
<h1 style="font-weight:700; font-size: var(--ad-step-2); letter-spacing:-0.01em; margin: var(--ad-space-xs) 0 var(--ad-space-2xs);">Dishwasher manual</h1>
<div style="display:flex; gap:6px; flex-wrap:wrap; margin-bottom: var(--ad-space-m);">
- <span class="ad-tag">location:kitchen</span>
- <span class="ad-tag ad-tag--dashed">+ add tag</span>
+ <span class="tag">location:kitchen</span>
+ <span class="tag" data-dashed>+ add tag</span>
</div>
<div class="type-label" style="margin-bottom: var(--ad-space-2xs);">File</div>
- <div class="ad-card">
- <div class="ad-card-row">
- <span class="ad-avatar ad-avatar--square"><svg width="17" height="17" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M21 8 12 3 3 8v8l9 5 9-5V8Z" /><path d="M3 8l9 5 9-5M12 13v8" /></svg></span>
+ <ul class="card">
+ <li>
+ <span class="avatar" data-square><svg width="17" height="17" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M21 8 12 3 3 8v8l9 5 9-5V8Z" /><path d="M3 8l9 5 9-5M12 13v8" /></svg></span>
<div style="flex:1; font-size: var(--ad-step--1);">PDF · 2.1MB</div>
- </div>
- </div>
+ </li>
+ </ul>
<div class="type-label" style="margin: var(--ad-space-m) 0 var(--ad-space-2xs);">Notes</div>
<p style="font-size: var(--ad-step--1); color: var(--ad-color-text-secondary); line-height:1.55; margin:0;">No notes yet.</p>
</div>
diff --git a/_patterns/loop.md b/_patterns/loop.md
index f133428..d1c0c84 100644
--- a/_patterns/loop.md
+++ b/_patterns/loop.md
@@ -19,62 +19,66 @@ beyond a couple of inline widths.
<header style="display:flex; align-items:center; gap: var(--ad-space-s); padding: var(--ad-space-s) var(--ad-space-l); border-bottom: 1px solid var(--ad-color-border-hairline);">
<span style="font-family: var(--ad-font-mono); font-weight:600; letter-spacing:0.02em; font-size: var(--ad-step-0);">Loop</span>
<div style="flex:1;"></div>
- <button type="button" class="ad-btn ad-btn--primary ad-btn--sm">
+ <button type="button" data-variant="primary" data-size="sm">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.9" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M12 5v14M5 12h14" /></svg>
New
</button>
- <span class="ad-avatar">km</span>
+ <span class="avatar">km</span>
</header>
<div style="display:flex;">
<div style="flex:1; min-width:0; max-width: 34rem; padding: var(--ad-space-l);">
<section style="margin-bottom: var(--ad-space-l);">
<div style="display:flex; align-items:baseline; gap: var(--ad-space-2xs); margin-bottom: var(--ad-space-s);">
<span class="type-label" style="color: var(--ad-color-danger);">Overdue</span>
- <span class="ad-badge ad-badge--danger">2</span>
- </div>
- <div class="ad-card-row">
- <button type="button" aria-label="Complete" style="all:unset; width:18px; height:18px; border-radius:4px; flex-shrink:0; cursor:pointer; border: 1.5px solid var(--ad-color-danger);"></button>
- <div style="flex:1; min-width:0;">
- <div style="font-weight:500; color: var(--ad-color-danger);">Call Mom</div>
- <div class="type-mono" style="color: var(--ad-color-text-tertiary); font-size: var(--ad-step--2);">every 2 weeks</div>
- </div>
- <span class="type-mono" style="color: var(--ad-color-danger); font-size: var(--ad-step--2); white-space:nowrap;">3d overdue</span>
- </div>
- <div class="ad-card-row">
- <button type="button" aria-label="Complete" style="all:unset; width:18px; height:18px; border-radius:4px; flex-shrink:0; cursor:pointer; border: 1.5px solid var(--ad-color-danger);"></button>
- <div style="flex:1; min-width:0;">
- <div style="font-weight:500; color: var(--ad-color-danger);">Water the ferns</div>
- <div class="type-mono" style="color: var(--ad-color-text-tertiary); font-size: var(--ad-step--2);">every 5 days</div>
- </div>
- <span class="type-mono" style="color: var(--ad-color-danger); font-size: var(--ad-step--2); white-space:nowrap;">1d overdue</span>
+ <span class="badge" data-tone="danger">2</span>
</div>
+ <ul class="card" style="border:none; border-radius:0; background:none;">
+ <li style="border-top: 1px solid var(--ad-color-border-hairline);">
+ <button type="button" aria-label="Complete" style="all:unset; width:18px; height:18px; border-radius:4px; flex-shrink:0; cursor:pointer; border: 1.5px solid var(--ad-color-danger);"></button>
+ <div style="flex:1; min-width:0;">
+ <div style="font-weight:500; color: var(--ad-color-danger);">Call Mom</div>
+ <div class="type-mono" style="color: var(--ad-color-text-tertiary); font-size: var(--ad-step--2);">every 2 weeks</div>
+ </div>
+ <span class="type-mono" style="color: var(--ad-color-danger); font-size: var(--ad-step--2); white-space:nowrap;">3d overdue</span>
+ </li>
+ <li>
+ <button type="button" aria-label="Complete" style="all:unset; width:18px; height:18px; border-radius:4px; flex-shrink:0; cursor:pointer; border: 1.5px solid var(--ad-color-danger);"></button>
+ <div style="flex:1; min-width:0;">
+ <div style="font-weight:500; color: var(--ad-color-danger);">Water the ferns</div>
+ <div class="type-mono" style="color: var(--ad-color-text-tertiary); font-size: var(--ad-step--2);">every 5 days</div>
+ </div>
+ <span class="type-mono" style="color: var(--ad-color-danger); font-size: var(--ad-step--2); white-space:nowrap;">1d overdue</span>
+ </li>
+ </ul>
</section>
<section>
<div class="type-label" style="margin-bottom: var(--ad-space-s);">Upcoming</div>
- <div class="ad-card-row">
- <button type="button" aria-label="Complete" style="all:unset; width:18px; height:18px; border-radius:4px; flex-shrink:0; cursor:pointer; border: 1.5px solid var(--ad-color-text-secondary);"></button>
- <div style="flex:1; min-width:0;">
- <div style="font-weight:500;">Renew passport</div>
- <div class="type-mono" style="color: var(--ad-color-text-tertiary); font-size: var(--ad-step--2);">every 10 years</div>
- </div>
- <span class="type-mono" style="color: var(--ad-color-text-secondary); font-size: var(--ad-step--2); white-space:nowrap;">in 3 months</span>
- </div>
- <div class="ad-card-row">
- <button type="button" aria-label="Complete" style="all:unset; width:18px; height:18px; border-radius:4px; flex-shrink:0; cursor:pointer; border: 1.5px solid var(--ad-color-text-secondary);"></button>
- <div style="flex:1; min-width:0;">
- <div style="font-weight:500;">Dentist checkup</div>
- <div class="type-mono" style="color: var(--ad-color-text-tertiary); font-size: var(--ad-step--2);">every 6 months</div>
- </div>
- <span class="type-mono" style="color: var(--ad-color-text-secondary); font-size: var(--ad-step--2); white-space:nowrap;">in 2 weeks</span>
- </div>
- <div class="ad-card-row">
- <button type="button" aria-label="Complete" style="all:unset; width:18px; height:18px; border-radius:4px; flex-shrink:0; cursor:pointer; border: 1.5px solid var(--ad-color-text-secondary);"></button>
- <div style="flex:1; min-width:0;">
- <div style="font-weight:500;">Rotate mattress</div>
- <div class="type-mono" style="color: var(--ad-color-text-tertiary); font-size: var(--ad-step--2);">every 6 months</div>
- </div>
- <span class="type-mono" style="color: var(--ad-color-text-secondary); font-size: var(--ad-step--2); white-space:nowrap;">in 5 weeks</span>
- </div>
+ <ul class="card" style="border:none; border-radius:0; background:none;">
+ <li style="border-top: 1px solid var(--ad-color-border-hairline);">
+ <button type="button" aria-label="Complete" style="all:unset; width:18px; height:18px; border-radius:4px; flex-shrink:0; cursor:pointer; border: 1.5px solid var(--ad-color-text-secondary);"></button>
+ <div style="flex:1; min-width:0;">
+ <div style="font-weight:500;">Renew passport</div>
+ <div class="type-mono" style="color: var(--ad-color-text-tertiary); font-size: var(--ad-step--2);">every 10 years</div>
+ </div>
+ <span class="type-mono" style="color: var(--ad-color-text-secondary); font-size: var(--ad-step--2); white-space:nowrap;">in 3 months</span>
+ </li>
+ <li>
+ <button type="button" aria-label="Complete" style="all:unset; width:18px; height:18px; border-radius:4px; flex-shrink:0; cursor:pointer; border: 1.5px solid var(--ad-color-text-secondary);"></button>
+ <div style="flex:1; min-width:0;">
+ <div style="font-weight:500;">Dentist checkup</div>
+ <div class="type-mono" style="color: var(--ad-color-text-tertiary); font-size: var(--ad-step--2);">every 6 months</div>
+ </div>
+ <span class="type-mono" style="color: var(--ad-color-text-secondary); font-size: var(--ad-step--2); white-space:nowrap;">in 2 weeks</span>
+ </li>
+ <li>
+ <button type="button" aria-label="Complete" style="all:unset; width:18px; height:18px; border-radius:4px; flex-shrink:0; cursor:pointer; border: 1.5px solid var(--ad-color-text-secondary);"></button>
+ <div style="flex:1; min-width:0;">
+ <div style="font-weight:500;">Rotate mattress</div>
+ <div class="type-mono" style="color: var(--ad-color-text-tertiary); font-size: var(--ad-step--2);">every 6 months</div>
+ </div>
+ <span class="type-mono" style="color: var(--ad-color-text-secondary); font-size: var(--ad-step--2); white-space:nowrap;">in 5 weeks</span>
+ </li>
+ </ul>
</section>
</div>
<aside style="width: 15rem; flex-shrink:0; background: var(--ad-color-surface-fill); border-left: 1px solid var(--ad-color-border-hairline); padding: var(--ad-space-l);">
@@ -126,35 +130,33 @@ beyond a couple of inline widths.
<figure class="example">
<figcaption>New series</figcaption>
<div data-layout="stack">
-<div class="ad-dialog-demo" style="min-height: 18rem;">
- <div class="ad-dialog__overlay">
- <div class="ad-dialog" style="width: 22rem;">
- <div class="ad-dialog__title">New series</div>
- <div class="ad-dialog__body">
- <div style="display:flex; flex-direction:column; gap: var(--ad-space-s);">
- <div class="ad-field">
- <label class="ad-field__label" for="loop-note">Note</label>
- <input class="ad-input" id="loop-note" placeholder="Call Mom" />
- <span class="ad-field__hint">First line becomes the display name.</span>
- </div>
- <div class="ad-form__row">
- <div class="ad-field">
- <label class="ad-field__label" for="loop-every">Every</label>
- <input class="ad-input" id="loop-every" type="number" placeholder="2" />
- </div>
- <div class="ad-field">
- <label class="ad-field__label" for="loop-unit">Unit</label>
- <select class="ad-select" id="loop-unit"><option>Day</option><option selected="">Week</option><option>Month</option><option>Year</option></select>
- </div>
- </div>
+<div class="dialog-demo" style="min-height: 36rem;">
+ <dialog open style="width: 22rem;">
+ <header>New series</header>
+ <div>
+ <div style="display:flex; flex-direction:column; gap: var(--ad-space-s);">
+ <label class="field">
+ Note
+ <input placeholder="Call Mom" />
+ <small>First line becomes the display name.</small>
+ </label>
+ <div class="form-row">
+ <label class="field">
+ Every
+ <input type="number" placeholder="2" />
+ </label>
+ <label class="field">
+ Unit
+ <select><option>Day</option><option selected="">Week</option><option>Month</option><option>Year</option></select>
+ </label>
</div>
</div>
- <div class="ad-dialog__footer">
- <button type="button" class="ad-btn">Cancel</button>
- <button type="button" class="ad-btn ad-btn--primary">Create</button>
- </div>
</div>
- </div>
+ <footer>
+ <button type="button">Cancel</button>
+ <button type="button" data-variant="primary">Create</button>
+ </footer>
+ </dialog>
</div>
</div>
</figure>
@@ -164,9 +166,9 @@ beyond a couple of inline widths.
<figure class="example">
<figcaption>Toast</figcaption>
<div>
-<div class="ad-toast">
- <span class="ad-toast__message">Completed "Call Mom"</span>
- <button type="button" class="ad-toast__dismiss" aria-label="Dismiss">×</button>
+<div role="status">
+ <span>Completed "Call Mom"</span>
+ <button type="button" aria-label="Dismiss">×</button>
</div>
</div>
</figure>
diff --git a/_sass/components/badge.css b/_sass/components/badge.css
index b48309d..d7e0d30 100644
--- a/_sass/components/badge.css
+++ b/_sass/components/badge.css
@@ -1,8 +1,12 @@
/* Badge
Mono, uppercase, tracked — the catalog-label convention for status and
- meta values (SAVED, OVERDUE, RECOMMENDED). A label, never a button. */
+ meta values (SAVED, OVERDUE, RECOMMENDED). A label, never a button.
-.ad-badge {
+ A <span> is too generic to style globally — it is used all over running
+ prose for unrelated reasons — so Badge gets the one sanctioned class this
+ shape needs. Tone is an attribute, not a modifier class. */
+
+.badge {
--_bg: var(--ad-color-surface-fill);
--_fg: var(--ad-color-text-secondary);
--_border: var(--ad-color-border-hairline);
@@ -23,11 +27,11 @@
white-space: nowrap;
}
-.ad-badge--accent { --_bg: var(--ad-color-accent-soft); --_fg: var(--ad-color-accent-ink); --_border: transparent; }
-.ad-badge--success { --_bg: var(--ad-color-success-bg); --_fg: var(--ad-color-success); --_border: transparent; }
-.ad-badge--danger { --_bg: var(--ad-color-danger-bg); --_fg: var(--ad-color-danger); --_border: transparent; }
+.badge[data-tone="accent"] { --_bg: var(--ad-color-accent-soft); --_fg: var(--ad-color-accent-ink); --_border: transparent; }
+.badge[data-tone="success"] { --_bg: var(--ad-color-success-bg); --_fg: var(--ad-color-success); --_border: transparent; }
+.badge[data-tone="danger"] { --_bg: var(--ad-color-danger-bg); --_fg: var(--ad-color-danger); --_border: transparent; }
-.ad-badge-set {
+.badge-set {
display: flex;
flex-wrap: wrap;
gap: var(--ad-space-2xs);
@@ -35,9 +39,12 @@
/* Tag
A removable mono chip for freeform tags — locations, categories.
- `--dashed` renders an "add tag" affordance instead of a value. */
+ `[data-dashed]` renders an "add tag" affordance instead of a value. Same
+ reasoning as Badge: <span> is too generic to select bare, so Tag gets one
+ class. The remove control is a real <button>, styled through the
+ descendant selector rather than a class of its own. */
-.ad-tag {
+.tag {
display: inline-flex;
align-items: center;
gap: 0.375rem;
@@ -51,23 +58,23 @@
line-height: 1;
}
-.ad-tag--dashed {
+.tag[data-dashed] {
background: transparent;
border-style: dashed;
color: var(--ad-color-text-tertiary);
cursor: pointer;
}
-.ad-tag__remove {
+.tag button {
all: unset;
cursor: pointer;
color: var(--ad-color-text-tertiary);
line-height: 1;
font-size: 0.75rem;
}
-.ad-tag__remove:hover { color: var(--ad-color-text-primary); }
+.tag button:hover { color: var(--ad-color-text-primary); }
-.ad-tag-set {
+.tag-set {
display: flex;
flex-wrap: wrap;
gap: 0.375rem;
diff --git a/_sass/components/button.css b/_sass/components/button.css
index 57f8ec2..4922d22 100644
--- a/_sass/components/button.css
+++ b/_sass/components/button.css
@@ -1,9 +1,18 @@
/* Button
A flat, quiet button that recedes rather than performs — no gradients, no
shine. Variants: default, primary, dark, ghost, danger. Sizes: sm, md
- (default), lg — padding and font step only, weight and radius stay put. */
+ (default), lg — padding and font step only, weight and radius stay put.
-.ad-btn {
+ The contract is the element itself: a bare <button> is styled directly, no
+ class required. An <a> that acts like a button (navigates rather than
+ changes something — see the Button page's "Links that act like buttons")
+ opts in with a single `.btn` class, since a bare `a` selector would catch
+ every ordinary prose link on the site. `:not(.icon-button)` keeps this
+ block from leaking onto IconButton, which shares the <button> tag but is
+ an entirely different shape. */
+
+button:not(.icon-button),
+a.btn {
--_bg: var(--ad-color-surface-card);
--_fg: var(--ad-color-text-primary);
--_border: var(--ad-color-border-default);
@@ -32,16 +41,16 @@
border-color var(--ad-dur-fast) var(--ad-ease);
}
-.ad-btn:hover:not(:disabled):not([aria-disabled="true"]) { filter: brightness(0.96); }
-.ad-btn:active:not(:disabled):not([aria-disabled="true"]) { filter: brightness(0.92); }
+:is(button:not(.icon-button), a.btn):hover:not(:disabled):not([aria-disabled="true"]) { filter: brightness(0.96); }
+:is(button:not(.icon-button), a.btn):active:not(:disabled):not([aria-disabled="true"]) { filter: brightness(0.92); }
-.ad-btn:disabled,
-.ad-btn[aria-disabled="true"] {
+button:not(.icon-button):disabled,
+:is(button:not(.icon-button), a.btn)[aria-disabled="true"] {
opacity: 0.5;
cursor: default;
}
-.ad-btn--primary {
+:is(button:not(.icon-button), a.btn)[data-variant="primary"] {
--_bg: var(--ad-color-accent);
--_fg: var(--ad-color-accent-fg);
--_border: var(--ad-color-accent);
@@ -49,13 +58,13 @@
/* Reserved for the single most-committing action in a view — a final Save,
never a whole row of them. */
-.ad-btn--dark {
+:is(button:not(.icon-button), a.btn)[data-variant="dark"] {
--_bg: var(--ad-color-text-primary);
--_fg: var(--ad-color-surface-page);
--_border: var(--ad-color-text-primary);
}
-.ad-btn--ghost {
+:is(button:not(.icon-button), a.btn)[data-variant="ghost"] {
--_bg: transparent;
--_fg: var(--ad-color-text-primary);
--_border: transparent;
@@ -65,31 +74,35 @@
is not "on a filled accent surface", so --ad-color-accent-fg is reused
here only because its resolved value is the same literal white, not
because the two roles are the same thing. */
-.ad-btn--danger {
+:is(button:not(.icon-button), a.btn)[data-variant="danger"] {
--_bg: var(--ad-color-danger);
--_fg: var(--ad-color-accent-fg);
--_border: var(--ad-color-danger);
}
-.ad-btn--sm { --_pad: 0.375rem 0.75rem; --_font: 0.8125rem; }
-.ad-btn--lg { --_pad: 0.75rem 1.375rem; --_font: var(--ad-step-0); }
+:is(button:not(.icon-button), a.btn)[data-size="sm"] { --_pad: 0.375rem 0.75rem; --_font: 0.8125rem; }
+:is(button:not(.icon-button), a.btn)[data-size="lg"] { --_pad: 0.75rem 1.375rem; --_font: var(--ad-step-0); }
-.ad-btn--block { width: 100%; }
+:is(button:not(.icon-button), a.btn)[data-block] { width: 100%; }
-.ad-btn-group {
+/* Button group — [role="group"] is already the correct ARIA role for a
+ toolbar of related buttons, so it doubles as the styling hook for free. */
+[role="group"] {
display: inline-flex;
isolation: isolate;
}
-.ad-btn-group > .ad-btn { border-radius: 0; }
-.ad-btn-group > .ad-btn:first-child { border-start-start-radius: var(--ad-radius-md); border-end-start-radius: var(--ad-radius-md); }
-.ad-btn-group > .ad-btn:last-child { border-start-end-radius: var(--ad-radius-md); border-end-end-radius: var(--ad-radius-md); }
-.ad-btn-group > .ad-btn + .ad-btn { margin-inline-start: -1px; }
-.ad-btn-group > .ad-btn:hover,
-.ad-btn-group > .ad-btn:focus-visible { z-index: 1; }
+[role="group"] > button { border-radius: 0; }
+[role="group"] > button:first-child { border-start-start-radius: var(--ad-radius-md); border-end-start-radius: var(--ad-radius-md); }
+[role="group"] > button:last-child { border-start-end-radius: var(--ad-radius-md); border-end-end-radius: var(--ad-radius-md); }
+[role="group"] > button + button { margin-inline-start: -1px; }
+[role="group"] > button:hover,
+[role="group"] > button:focus-visible { z-index: 1; }
/* IconButton — a square icon-only button. Always needs an aria-label; it
- becomes both the accessible name and the native tooltip. */
-.ad-icon-btn {
+ becomes both the accessible name and the native tooltip. There is no
+ attribute that distinguishes "a button that is only an icon" from a plain
+ Button, so this is the one sanctioned class in this file. */
+.icon-button {
--_size: 2.125rem;
--_bg: transparent;
--_border: transparent;
@@ -100,6 +113,7 @@
justify-content: center;
width: var(--_size);
height: var(--_size);
+ padding: 0;
flex: none;
border: 1px solid var(--_border);
border-radius: var(--ad-radius-md);
@@ -109,11 +123,11 @@
transition: background-color var(--ad-dur-fast) var(--ad-ease),
color var(--ad-dur-fast) var(--ad-ease);
}
-.ad-icon-btn:hover:not(:disabled) { background: var(--ad-color-surface-fill-2); }
-.ad-icon-btn:disabled { opacity: 0.5; cursor: default; }
+.icon-button:hover:not(:disabled) { background: var(--ad-color-surface-fill-2); }
+.icon-button:disabled { opacity: 0.5; cursor: default; }
-.ad-icon-btn--sm { --_size: 1.75rem; }
-.ad-icon-btn--lg { --_size: 2.5rem; }
+.icon-button[data-size="sm"] { --_size: 1.75rem; }
+.icon-button[data-size="lg"] { --_size: 2.5rem; }
-.ad-icon-btn--outline { --_bg: var(--ad-color-surface-card); --_border: var(--ad-color-border-default); --_fg: var(--ad-color-text-primary); }
-.ad-icon-btn--accent { --_bg: var(--ad-color-accent-soft); --_fg: var(--ad-color-accent-ink); }
+.icon-button[data-variant="outline"] { --_bg: var(--ad-color-surface-card); --_border: var(--ad-color-border-default); --_fg: var(--ad-color-text-primary); }
+.icon-button[data-variant="accent"] { --_bg: var(--ad-color-accent-soft); --_fg: var(--ad-color-accent-ink); }
diff --git a/_sass/components/card.css b/_sass/components/card.css
index f2b4c32..e82eac9 100644
--- a/_sass/components/card.css
+++ b/_sass/components/card.css
@@ -1,11 +1,22 @@
/* Card
- Flat surface, hairline border, radius-lg. `--elevated` is opt-in and
+ Flat surface, hairline border, radius-lg. `[data-elevated]` is opt-in and
reserved for the one focused/primary card in a view — a dialog, the
- primary capture card — flat is the default everywhere else. */
+ primary capture card — flat is the default everywhere else.
-.ad-card {
+ A <div>/<article> is too generic to select bare, so Card keeps one
+ sanctioned class. When Card's job is holding a list of rows — the common
+ case, entry lists, task lists, settings rows — it is a real <ul class="
+ card">, and CardRow is a bare <li> underneath it: no second class needed,
+ the row look comes from the `.card > li` descendant selector alone. The
+ first row's top border is removed automatically so callers never have to
+ override it by hand. */
+
+.card {
display: flex;
flex-direction: column;
+ margin: 0;
+ padding: 0;
+ list-style: none;
background: var(--ad-color-surface-card);
border: 1px solid var(--ad-color-border-hairline);
border-radius: var(--ad-radius-lg);
@@ -13,12 +24,9 @@
overflow: hidden;
}
-.ad-card--elevated { box-shadow: var(--ad-shadow-float); }
+.card[data-elevated] { box-shadow: var(--ad-shadow-float); }
-/* CardRow — a hairline-divided list row for use inside Card: entry lists,
- task lists, settings rows. The first row's top border is removed
- automatically so callers never have to override it by hand. */
-.ad-card-row {
+.card > li {
display: flex;
align-items: center;
gap: var(--ad-space-xs);
@@ -27,38 +35,49 @@
color: var(--ad-color-text-primary);
font-family: var(--ad-font-sans);
font-size: var(--ad-step--1);
- transition: background-color var(--ad-dur-fast) var(--ad-ease);
}
-.ad-card > .ad-card-row:first-child { border-top: none; }
+.card > li:first-child { border-top: none; }
-.ad-card-row--clickable {
- cursor: pointer;
- text-align: left;
+/* A row whose ENTIRE content is wrapped in a real <button> or <a> — the
+ accessible way to make a row clickable — fills the row edge-to-edge and
+ picks up a hover fill and pointer cursor. `:only-child` is what tells this
+ apart from a row that merely contains a small button among other content
+ (a leading checkbox next to a title and a trailing value, say): only a
+ button/anchor that IS the row's one and only child means "the whole row
+ is the control." */
+.card > li:has(> button:only-child),
+.card > li:has(> a:only-child) {
+ padding: 0;
+}
+.card > li > button:only-child,
+.card > li > a:only-child {
+ all: unset;
+ display: flex;
+ align-items: center;
+ gap: var(--ad-space-xs);
width: 100%;
- border-left: 0;
- border-right: 0;
- background: none;
+ padding: var(--ad-space-xs) var(--ad-space-s);
+ box-sizing: border-box;
+ color: inherit;
+ font: inherit;
+ cursor: pointer;
+ transition: background-color var(--ad-dur-fast) var(--ad-ease);
}
-.ad-card-row--clickable:hover { background: var(--ad-color-surface-fill); }
+.card > li > button:only-child:hover,
+.card > li > a:only-child:hover { background: var(--ad-color-surface-fill); }
-/* Documentation-only extras: a body/title/text/footer quartet and a
- responsive grid, used by the prose pages (rationale.md, index.md) to show
- a Card as a plain content tile rather than a data row. */
-.ad-card__body {
+/* Documentation-only extras: a body/footer pair and a responsive grid, used
+ by the prose pages (rationale.md, index.md) to show a Card as a plain
+ content tile rather than a data row. Neither is part of Card's props —
+ they exist because a content tile's inner layout has no element of its
+ own to hang a selector on. */
+.card-body {
display: flex;
flex-direction: column;
gap: var(--ad-space-2xs);
padding: var(--ad-space-m);
}
-.ad-card__title {
- font: 600 var(--ad-step-0)/1.3 var(--ad-font-sans);
-}
-.ad-card__text {
- margin: 0;
- color: var(--ad-color-text-secondary);
- font-size: var(--ad-step--1);
-}
-.ad-card__footer {
+.card-footer {
display: flex;
flex-wrap: wrap;
align-items: center;
@@ -69,24 +88,27 @@
background: var(--ad-color-surface-fill);
}
-.ad-card--link { transition: border-color var(--ad-dur-fast) var(--ad-ease), box-shadow var(--ad-dur-fast) var(--ad-ease); }
-.ad-card--link:hover { border-color: var(--ad-color-border-default); box-shadow: var(--ad-shadow-sm); }
-.ad-card--link:focus-within { outline: 2px solid var(--ad-color-accent); outline-offset: 2px; }
+.card[data-link] { transition: border-color var(--ad-dur-fast) var(--ad-ease), box-shadow var(--ad-dur-fast) var(--ad-ease); }
+.card[data-link]:hover { border-color: var(--ad-color-border-default); box-shadow: var(--ad-shadow-sm); }
+.card[data-link]:focus-within { outline: 2px solid var(--ad-color-accent); outline-offset: 2px; }
-.ad-card-grid {
+.card-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(15rem, 1fr));
gap: var(--ad-space-s);
}
/* Avatar — lives here because it almost never appears outside a card or a
- row (the Loop dashboard's initials chip, Archive's file-type chip). */
-.ad-avatar {
+ row (the Loop dashboard's initials chip, Archive's file-type chip). A
+ <span> is too generic for a bare selector, so it keeps one class. */
+.avatar {
+ --_size: 1.875rem;
+
display: inline-flex;
align-items: center;
justify-content: center;
- width: 1.875rem;
- height: 1.875rem;
+ width: var(--_size);
+ height: var(--_size);
flex: none;
border-radius: var(--ad-radius-full);
background: var(--ad-color-accent-soft);
@@ -96,6 +118,6 @@
font-weight: 600;
overflow: hidden;
}
-.ad-avatar--square { border-radius: var(--ad-radius-md); background: var(--ad-color-surface-fill); color: var(--ad-color-text-secondary); border: 1px solid var(--ad-color-border-hairline); }
-.ad-avatar--sm { width: 1.5rem; height: 1.5rem; }
-.ad-avatar--lg { width: 2.25rem; height: 2.25rem; }
+.avatar[data-square] { border-radius: var(--ad-radius-md); background: var(--ad-color-surface-fill); color: var(--ad-color-text-secondary); border: 1px solid var(--ad-color-border-hairline); }
+.avatar[data-size="sm"] { --_size: 1.5rem; }
+.avatar[data-size="lg"] { --_size: 2.25rem; }
diff --git a/_sass/components/feedback.css b/_sass/components/feedback.css
index 496ca62..3510e4b 100644
--- a/_sass/components/feedback.css
+++ b/_sass/components/feedback.css
@@ -1,12 +1,18 @@
/* Tooltip — a dark mono label on hover, positioned above the child. Pure CSS:
the trigger and bubble are siblings inside one relatively-positioned span,
- shown on :hover/:focus-within so it works from the keyboard too. */
-.ad-tooltip {
+ shown on :hover/:focus-within so it works from the keyboard too.
+
+ `[role="tooltip"]` is the correct ARIA role for the bubble itself, so it
+ doubles as the styling hook — zero classes there. The wrapping span has no
+ attribute of its own, but it does not need one: `:has()` picks out "a span
+ that contains a [role=tooltip] bubble" precisely, without reaching for a
+ class on an otherwise-generic element. */
+span:has(> [role="tooltip"]) {
position: relative;
display: inline-flex;
}
-.ad-tooltip__bubble {
+[role="tooltip"] {
position: absolute;
bottom: 100%;
left: 50%;
@@ -29,16 +35,21 @@
pointer-events: none;
transition: opacity var(--ad-dur-fast) var(--ad-ease);
}
-.ad-tooltip:hover .ad-tooltip__bubble,
-.ad-tooltip:focus-within .ad-tooltip__bubble {
+span:has(> [role="tooltip"]):hover > [role="tooltip"],
+span:has(> [role="tooltip"]):focus-within > [role="tooltip"] {
opacity: 1;
visibility: visible;
}
/* Toast — the real pattern used for delete confirmations: a flat bar with an
- optional destructive action and a dismiss. `--fixed` pins it bottom-right
- for real use; the bare class stays in document flow for documentation. */
-.ad-toast {
+ optional destructive action and a dismiss. `[data-fixed]` pins it
+ bottom-right for real use; without it the toast stays in document flow for
+ documentation. `role="status"` is the correct live-region role for a
+ transient status message, so it is both the accessibility hook and the
+ style selector — no base class needed. The action and dismiss buttons are
+ plain <button>s told apart by `aria-label="Dismiss"`, an attribute they
+ need anyway for the icon-only close control. */
+[role="status"] {
display: flex;
align-items: center;
gap: var(--ad-space-xs);
@@ -52,62 +63,68 @@
font-family: var(--ad-font-sans);
font-size: var(--ad-step--2);
}
-.ad-toast--fixed {
+[role="status"][data-fixed] {
position: fixed;
inset-block-end: var(--ad-space-s);
inset-inline-end: var(--ad-space-s);
z-index: 60;
}
-.ad-toast__message { flex: 1; }
-.ad-toast__action {
+[role="status"] > span { flex: 1; }
+[role="status"] button {
all: unset;
cursor: pointer;
- color: var(--ad-color-danger);
font-weight: 600;
+ color: var(--ad-color-danger);
}
-.ad-toast__dismiss {
- all: unset;
- cursor: pointer;
+[role="status"] button[aria-label="Dismiss"] {
color: var(--ad-color-text-tertiary);
font-size: 1rem;
+ font-weight: 400;
line-height: 1;
}
-.ad-toast__dismiss:hover { color: var(--ad-color-text-primary); }
+[role="status"] button[aria-label="Dismiss"]:hover { color: var(--ad-color-text-primary); }
+
+/* Dialog — the native <dialog> element: a centered modal, flat surface, no
+ colored header, hairline dividers between title/body/footer. A <header>
+ holds the title and a <footer> holds the actions, matching the elements
+ the tag already wants; everything else in between is the body, styled by
+ excluding those two rather than by a class of its own.
-/* Dialog — a centered modal: flat surface, no colored header, hairline
- dividers between title/body/footer. `.ad-dialog-demo` scopes the overlay
- to its own preview container instead of the viewport, so a static,
- always-open example can sit safely inside a documentation page; wire real
- open/close state (and a viewport-fixed overlay) in the consuming app. */
-.ad-dialog-demo {
+ `.dialog-demo` scopes a static, always-open example to its own preview
+ container instead of the viewport, so it can sit safely inside a
+ documentation page — this is a doc-only affordance, not part of Dialog's
+ contract. It relies on the browser's own dialog centering: the UA
+ stylesheet already gives an open <dialog> `position: absolute; inset: 0;
+ margin: auto`, which centers it inside the nearest positioned ancestor —
+ here, `.dialog-demo` itself. A real `.showModal()` call gets the genuine
+ viewport-fixed overlay and `::backdrop` for free; a plain `open` attribute
+ (used here so the demo renders without JavaScript) does not paint a
+ backdrop at all, since `::backdrop` only exists for a dialog shown
+ modally — wire the real open/close state and call `.showModal()` in the
+ consuming app. */
+.dialog-demo {
position: relative;
- display: flex;
- align-items: center;
- justify-content: center;
min-height: 20rem;
background: var(--ad-color-surface-fill);
border-radius: var(--ad-radius-lg);
}
-.ad-dialog__overlay {
- position: absolute;
- inset: 0;
- display: flex;
- align-items: center;
- justify-content: center;
- padding: var(--ad-space-s);
- background: rgba(20, 18, 14, 0.35);
- border-radius: inherit;
-}
-.ad-dialog {
+
+dialog {
width: 26rem;
- max-width: 100%;
+ max-width: calc(100% - (2 * var(--ad-space-s)));
+ height: fit-content;
+ padding: 0;
background: var(--ad-color-surface-card);
border: 1px solid var(--ad-color-border-hairline);
border-radius: var(--ad-radius-lg);
box-shadow: var(--ad-shadow-float);
+ color: var(--ad-color-text-primary);
overflow: hidden;
}
-.ad-dialog__title {
+dialog::backdrop {
+ background: rgba(20, 18, 14, 0.35);
+}
+dialog > header {
padding: var(--ad-space-s) var(--ad-space-m);
border-bottom: 1px solid var(--ad-color-border-hairline);
font-family: var(--ad-font-sans);
@@ -115,15 +132,16 @@
font-size: var(--ad-step-0);
color: var(--ad-color-text-primary);
}
-.ad-dialog__body {
- padding: var(--ad-space-m);
- color: var(--ad-color-text-primary);
- font-size: var(--ad-step--1);
-}
-.ad-dialog__footer {
+dialog > footer {
display: flex;
justify-content: flex-end;
gap: var(--ad-space-2xs);
padding: var(--ad-space-s) var(--ad-space-m);
border-top: 1px solid var(--ad-color-border-hairline);
}
+dialog > *:not(header):not(footer) {
+ padding: var(--ad-space-m);
+ margin: 0;
+ color: var(--ad-color-text-primary);
+ font-size: var(--ad-step--1);
+}
diff --git a/_sass/components/field.css b/_sass/components/field.css
index 26fba87..b92908b 100644
--- a/_sass/components/field.css
+++ b/_sass/components/field.css
@@ -1,15 +1,19 @@
/* Field
- The label / control / hint trio. The label is always mono, uppercase, and
- tracked — the same catalog-label convention as Badge and type-label. */
+ The labeled field wrapper — a <label> that wraps its own caption text
+ together with the control, so the association is native and needs no
+ `for`/`id` pair. The caption is mono, uppercase, tracked — the same
+ catalog-label convention as Badge and type-label.
-.ad-field {
+ A <label> is reused for three different shapes across this system (this
+ wrapper, Checkbox/Radio's row, Switch's row), so it cannot be styled bare
+ without one shape leaking into another. Field is the one of the three with
+ no distinguishing attribute of its own, so it gets the sanctioned class;
+ Checkbox/Radio tell themselves apart structurally instead (see below). */
+.field {
display: flex;
flex-direction: column;
gap: var(--ad-space-2xs);
min-width: 0;
-}
-
-.ad-field__label {
font-family: var(--ad-font-mono);
font-size: var(--ad-step--2);
font-weight: 500;
@@ -18,14 +22,24 @@
color: var(--ad-color-text-secondary);
}
-.ad-field__hint {
+/* Hint and error read the native fine-print element. `role="alert"` is a
+ real live-region role for validation text, so it doubles as the selector
+ that turns a hint red. */
+.field > small {
+ font-family: var(--ad-font-sans);
font-size: var(--ad-step--2);
+ text-transform: none;
+ letter-spacing: normal;
+ font-weight: 400;
color: var(--ad-color-text-tertiary);
}
+.field > small[role="alert"] { color: var(--ad-color-danger); }
-/* Input / Select — bordered, matched to each other exactly. */
-.ad-input,
-.ad-select {
+/* Input / Select — bordered, matched to each other exactly. Both tags ARE
+ the component, so they are styled bare; nothing else on this site uses a
+ text input or a select. */
+input:not([type="checkbox"]):not([type="radio"]),
+select {
width: 100%;
padding: var(--ad-space-xs) var(--ad-space-s);
background: var(--ad-color-surface-card);
@@ -34,20 +48,23 @@
color: var(--ad-color-text-primary);
font-family: var(--ad-font-sans);
font-size: var(--ad-step--1);
+ text-transform: none;
+ letter-spacing: normal;
+ font-weight: 400;
outline: none;
transition: border-color var(--ad-dur-fast) var(--ad-ease),
box-shadow var(--ad-dur-fast) var(--ad-ease);
}
-.ad-input::placeholder { color: var(--ad-color-text-tertiary); }
+input::placeholder { color: var(--ad-color-text-tertiary); }
-.ad-input:focus,
-.ad-select:focus {
+input:not([type="checkbox"]):not([type="radio"]):focus,
+select:focus {
border-color: var(--ad-color-accent);
box-shadow: 0 0 0 2px var(--ad-color-accent-soft);
}
-.ad-input:disabled,
-.ad-select:disabled {
+input:not([type="checkbox"]):not([type="radio"]):disabled,
+select:disabled {
opacity: 0.5;
cursor: default;
}
@@ -57,7 +74,7 @@
stroke is a literal hex rather than a var(--ad-*) reference. It is picked
to match --ad-color-text-secondary and commented here so it is not
mistaken for a stray color. */
-.ad-select {
+select {
appearance: none;
padding-inline-end: var(--ad-space-l);
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='8' viewBox='0 0 12 8'%3E%3Cpath d='M1 1.5 6 6.5l5-5' fill='none' stroke='%235f5d59' stroke-width='1.75' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
@@ -65,68 +82,57 @@
background-position: right var(--ad-space-xs) center;
}
-.ad-input[aria-invalid="true"],
-.ad-select[aria-invalid="true"] {
+input[aria-invalid="true"],
+select[aria-invalid="true"] {
border-color: var(--ad-color-danger);
background-color: var(--ad-color-danger-bg);
}
-.ad-field__error {
- font-size: var(--ad-step--2);
- color: var(--ad-color-danger);
+
+/* Checkbox / Radio — the real native controls, tinted with accent-color
+ instead of a hand-drawn box. This is not just a rename: it deletes the
+ custom SVG-checkmark markup entirely and lets the browser draw its own
+ (platform-appropriate) check and dot. `role="switch"` is excluded here so
+ Switch — a checkbox playing a different role — can style itself below
+ without this rule fighting it. A <label> wrapping a checkbox/radio is told
+ apart from Field's <label> structurally: it :has() one as a direct child,
+ no class required. */
+input[type="checkbox"]:not([role="switch"]),
+input[type="radio"] {
+ width: 1.125rem;
+ height: 1.125rem;
+ margin: 0;
+ accent-color: var(--ad-color-accent);
+ cursor: pointer;
+}
+input[type="checkbox"]:not([role="switch"]):focus-visible,
+input[type="radio"]:focus-visible {
+ outline: 2px solid var(--ad-color-accent);
+ outline-offset: 2px;
}
-/* Checkbox / Radio — one shared visual language; only the radius differs. */
-.ad-choice {
+label:has(> input[type="checkbox"]:not([role="switch"])),
+label:has(> input[type="radio"]) {
display: inline-flex;
align-items: center;
gap: var(--ad-space-2xs);
cursor: pointer;
font-family: var(--ad-font-sans);
font-size: var(--ad-step--1);
+ font-weight: 400;
+ text-transform: none;
+ letter-spacing: normal;
color: var(--ad-color-text-primary);
}
-.ad-choice input { position: absolute; opacity: 0; width: 1.125rem; height: 1.125rem; margin: 0; }
-
-.ad-choice__box {
- display: flex;
- align-items: center;
- justify-content: center;
- width: 1.125rem;
- height: 1.125rem;
- flex: none;
- border: 1.5px solid var(--ad-color-border-default);
- background: var(--ad-color-surface-card);
- color: transparent;
- transition: background-color var(--ad-dur-fast) var(--ad-ease),
- border-color var(--ad-dur-fast) var(--ad-ease);
-}
-.ad-choice--checkbox .ad-choice__box { border-radius: var(--ad-radius-sm); }
-.ad-choice--radio .ad-choice__box { border-radius: var(--ad-radius-full); }
-
-.ad-choice input:checked + .ad-choice__box {
- border-color: var(--ad-color-accent);
-}
-.ad-choice--checkbox input:checked + .ad-choice__box {
- background: var(--ad-color-accent);
- color: var(--ad-color-accent-fg);
-}
-.ad-choice--radio input:checked + .ad-choice__box::after {
- content: "";
- width: 0.5625rem;
- height: 0.5625rem;
- border-radius: var(--ad-radius-full);
- background: var(--ad-color-accent);
-}
-.ad-choice__box svg { width: 0.6875rem; height: 0.6875rem; }
-
-.ad-choice input:focus-visible + .ad-choice__box {
- outline: 2px solid var(--ad-color-accent);
- outline-offset: 2px;
-}
-.ad-choice:has(input:disabled) { cursor: default; opacity: 0.5; }
+label:has(> input:disabled) { cursor: default; opacity: 0.5; }
-/* Switch — for on/off settings only, never a substitute for Checkbox. */
-.ad-switch {
+/* Switch — for on/off settings only, never a substitute for Checkbox.
+ `input[type="checkbox"][role="switch"]` is real ARIA (the correct role for
+ a toggle), not an invented hook, but the visual track+thumb needs a second
+ element for the thumb's independent position and transition — more than
+ `appearance: none` plus pseudo-elements on the input itself can cleanly
+ carry — so `.switch` is a deliberate, documented fallback class rather
+ than a first choice. */
+.switch {
display: inline-flex;
align-items: center;
gap: var(--ad-space-2xs);
@@ -135,9 +141,9 @@
font-size: var(--ad-step--1);
color: var(--ad-color-text-primary);
}
-.ad-switch input { position: absolute; opacity: 0; width: 2.25rem; height: 1.25rem; margin: 0; }
+.switch input[role="switch"] { position: absolute; opacity: 0; width: 2.25rem; height: 1.25rem; margin: 0; }
-.ad-switch__track {
+.switch span {
position: relative;
flex: none;
width: 2.25rem;
@@ -146,7 +152,7 @@
background: var(--ad-color-border-default);
transition: background-color var(--ad-dur-base) var(--ad-ease);
}
-.ad-switch__track::after {
+.switch span::after {
content: "";
position: absolute;
inset-block-start: 2px;
@@ -158,24 +164,27 @@
box-shadow: var(--ad-shadow-sm);
transition: translate var(--ad-dur-base) var(--ad-ease);
}
-.ad-switch input:checked + .ad-switch__track { background: var(--ad-color-accent); }
-.ad-switch input:checked + .ad-switch__track::after { translate: 1rem 0; }
-.ad-switch input:focus-visible + .ad-switch__track { outline: 2px solid var(--ad-color-accent); outline-offset: 2px; }
-.ad-switch:has(input:disabled) { cursor: default; opacity: 0.5; }
+.switch input[role="switch"]:checked + span { background: var(--ad-color-accent); }
+.switch input[role="switch"]:checked + span::after { translate: 1rem 0; }
+.switch input[role="switch"]:focus-visible + span { outline: 2px solid var(--ad-color-accent); outline-offset: 2px; }
+.switch:has(input:disabled) { cursor: default; opacity: 0.5; }
-/* Rows of fields — used by the settings-form pattern. */
-.ad-form {
+/* Rows of fields — used by the settings-form pattern. `<form>`/`<fieldset>`
+ are the elements that already mean this, so they are styled bare. A row
+ grouping two fields side by side has no element of its own, so it keeps
+ one plain class. */
+form {
display: flex;
flex-direction: column;
gap: var(--ad-space-s);
max-width: 28rem;
}
-.ad-form__row {
+.form-row {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(10rem, 1fr));
gap: var(--ad-space-xs);
}
-.ad-fieldset {
+fieldset {
display: flex;
flex-direction: column;
gap: var(--ad-space-2xs);
@@ -183,7 +192,7 @@
padding: 0;
border: 0;
}
-.ad-fieldset > legend {
+fieldset > legend {
padding: 0 0 var(--ad-space-2xs);
font-family: var(--ad-font-mono);
font-size: var(--ad-step--2);
diff --git a/_sass/components/tabs.css b/_sass/components/tabs.css
index 7fd39c7..8c415ef 100644
--- a/_sass/components/tabs.css
+++ b/_sass/components/tabs.css
@@ -1,19 +1,29 @@
/* Tabs
- Markup follows the WAI-ARIA tabs pattern: a `tablist` of `tab` buttons
- controlling `tabpanel`s. The keyboard wiring lives in assets/js/system.js
- and is variant-agnostic — it only cares about `[data-tabs]` / `[role=tab]`.
+ Follows the real WAI-ARIA tabs pattern: a `[role="tablist"]` of
+ `[role="tab"]` buttons controlling `[role="tabpanel"]`s. Those roles are
+ required for the pattern to be accessible in the first place, so they
+ double as the entire styling contract — no classes at all for the base
+ shape. The keyboard wiring lives in assets/js/system.js and is
+ variant-agnostic — it only cares about `[data-tabs]` / `[role=tab]`.
Two real variants: `underline` (mono, accent underline — primary section
- nav) and `segmented` (pill track, active segment lifts to the surface —
- filters like All · Overdue · Upcoming, never primary navigation). */
+ nav, the default) and `segmented` (pill track, active segment lifts to the
+ surface — filters like All · Overdue · Upcoming, never primary
+ navigation), chosen with `data-variant="segmented"` on the tablist since
+ neither has a natural attribute of its own. */
-.ad-tabs__list { display: flex; }
+/* Every rule below the list container pairs [role="tablist"] with
+ [role="tab"] rather than styling [role="tab"] alone — Tab buttons are
+ still literal <button> elements, so the compound selector's extra
+ specificity is what keeps Button's base styling (button.css) from
+ winning the cascade on shared properties like padding and background. */
-.ad-tabs--underline .ad-tabs__list {
+[role="tablist"] {
+ display: flex;
gap: var(--ad-space-m);
border-bottom: 1px solid var(--ad-color-border-hairline);
}
-.ad-tabs--underline .ad-tabs__tab {
+[role="tablist"] [role="tab"] {
padding: 0.5rem 0;
margin-bottom: -1px;
background: none;
@@ -26,20 +36,21 @@
cursor: pointer;
transition: color var(--ad-dur-fast) var(--ad-ease), border-color var(--ad-dur-fast) var(--ad-ease);
}
-.ad-tabs--underline .ad-tabs__tab:hover { color: var(--ad-color-text-primary); }
-.ad-tabs--underline .ad-tabs__tab[aria-selected="true"] {
+[role="tablist"] [role="tab"]:hover { color: var(--ad-color-text-primary); }
+[role="tablist"] [role="tab"][aria-selected="true"] {
color: var(--ad-color-text-primary);
border-bottom-color: var(--ad-color-accent);
}
-.ad-tabs--segmented .ad-tabs__list {
+[role="tablist"][data-variant="segmented"] {
display: inline-flex;
gap: 0.125rem;
padding: 0.1875rem;
+ border-bottom: 0;
background: var(--ad-color-surface-fill);
border-radius: var(--ad-radius-md);
}
-.ad-tabs--segmented .ad-tabs__tab {
+[role="tablist"][data-variant="segmented"] [role="tab"] {
all: unset;
cursor: pointer;
padding: 0.375rem 0.875rem;
@@ -50,12 +61,12 @@
color: var(--ad-color-text-secondary);
transition: background-color var(--ad-dur-fast) var(--ad-ease), color var(--ad-dur-fast) var(--ad-ease);
}
-.ad-tabs--segmented .ad-tabs__tab[aria-selected="true"] {
+[role="tablist"][data-variant="segmented"] [role="tab"][aria-selected="true"] {
color: var(--ad-color-text-primary);
background: var(--ad-color-surface-card);
box-shadow: var(--ad-shadow-sm);
}
-.ad-tabs__panel { padding-top: var(--ad-space-s); }
-.ad-tabs__panel[hidden] { display: none; }
-.ad-tabs__panel:focus-visible { outline-offset: 4px; }
+[role="tabpanel"] { padding-top: var(--ad-space-s); }
+[role="tabpanel"][hidden] { display: none; }
+[role="tabpanel"]:focus-visible { outline-offset: 4px; }
diff --git a/_sass/site/tokens-docs.css b/_sass/site/tokens-docs.css
index 7d23b27..f03fee4 100644
--- a/_sass/site/tokens-docs.css
+++ b/_sass/site/tokens-docs.css
@@ -138,14 +138,21 @@
margin-bottom: var(--ad-space-2xs);
}
-.ad-token-name {
- font-family: var(--ad-font-mono);
+/* Token names read `<code>` sitewide, which already carries a mono chip
+ look for running prose (see :not(pre) > code in base/typography.css).
+ Inside these specimen/swatch/ruler rows the old `.ad-token-name` class was
+ plain mono text with no chip, so `<code>` is flattened back to that look
+ here the same way table cells are. */
+.ad-swatch code, .ad-specimen code, .ad-ruler code, .ad-alias-row code {
+ background: none;
+ border: 0;
+ padding: 0;
font-size: var(--ad-step--2);
color: var(--ad-color-text-primary);
white-space: nowrap;
}
-/* A documentation aside. Like `.ad-table`, this is site chrome for the prose
+/* A documentation aside. Like `<table>`, this is site chrome for the prose
itself, not a system component — the real system has no Callout. */
.ad-callout {
margin-block: var(--ad-space-s);
@@ -158,21 +165,28 @@
.ad-callout--danger { border-left-color: var(--ad-color-danger); }
.ad-callout--success { border-left-color: var(--ad-color-success); }
-/* Generic data-table utility for reference tables (component APIs, alias
- tables, the contrast contract). Site chrome, not a system component. */
-.ad-table-wrap { overflow-x: auto; margin-block: var(--ad-space-xs); }
-.ad-table { width: 100%; border-collapse: collapse; font-size: var(--ad-step--1); }
-.ad-table th, .ad-table td {
- padding: var(--ad-space-2xs) var(--ad-space-xs);
+/* Generic reference tables (component APIs, alias tables, the contrast
+ contract). `<table>` IS a table — no class needed to say so. Every table
+ on this site uses the one compact density, so there is no separate
+ modifier for it. `display: block` plus `overflow-x: auto` gives the table
+ its own horizontal scrollbar on narrow viewports without a wrapper div;
+ the anonymous table box the browser generates around `<thead>`/`<tbody>`
+ still does the row/column layout. `<code>` replaces the old
+ `.ad-token-name` / `.ad-table__code` cells — it is already styled
+ sitewide, just re-flattened here to a plain mono run instead of the
+ chip-with-border look prose code gets, to match this table's original
+ look. `.num` is the one sanctioned class left, for right-aligned numeric
+ columns — there is no attribute that means "this column is a number". */
+table { width: 100%; border-collapse: collapse; font-size: var(--ad-step--1); display: block; overflow-x: auto; }
+table th, table td {
+ padding: var(--ad-space-3xs) var(--ad-space-xs);
border-bottom: 1px solid var(--ad-color-border-hairline);
text-align: left;
vertical-align: top;
}
-.ad-table th { font-family: var(--ad-font-mono); font-size: 11px; text-transform: uppercase; letter-spacing: 0.06em; color: var(--ad-color-text-secondary); font-weight: 500; }
-.ad-table--compact th, .ad-table--compact td { padding: var(--ad-space-3xs) var(--ad-space-xs); }
-.ad-table--hover tbody tr:hover { background: var(--ad-color-surface-fill); }
-.ad-table__code { font-family: var(--ad-font-mono); font-size: var(--ad-step--2); white-space: nowrap; }
-.ad-table__num { text-align: right; font-variant-numeric: tabular-nums; }
+table th { font-family: var(--ad-font-mono); font-size: 11px; text-transform: uppercase; letter-spacing: 0.06em; color: var(--ad-color-text-secondary); font-weight: 500; }
+table code { background: none; border: 0; padding: 0; font-size: var(--ad-step--2); white-space: nowrap; }
+.num { text-align: right; font-variant-numeric: tabular-nums; }
/* Home page. */
.ad-hero { padding-block: var(--ad-space-m) var(--ad-space-l); }
diff --git a/index.md b/index.md
index 88a78c6..1e90fde 100644
--- a/index.md
+++ b/index.md
@@ -17,8 +17,8 @@ description: >-
typographic mood; this system is what they all share underneath.
</p>
<div class="ad-hero__actions">
- <a class="ad-btn ad-btn--primary ad-btn--lg" href="<%= relative_url('/foundations/color/') %>">Start with color</a>
- <a class="ad-btn ad-btn--lg" href="<%= relative_url('/components/button/') %>">Jump to components</a>
+ <a class="btn" data-variant="primary" data-size="lg" href="<%= relative_url('/foundations/color/') %>">Start with color</a>
+ <a class="btn" data-size="lg" href="<%= relative_url('/components/button/') %>">Jump to components</a>
</div>
</div>
@@ -67,29 +67,29 @@ from the token values, not recomputed on every build. If a hex changes in
`tokens.css`, the swatch and the button are updated by hand together, the
same afternoon.
-<div class="ad-card-grid">
- <article class="ad-card ad-card--link">
- <div class="ad-card__body">
- <h3 class="ad-card__title"><a href="<%= relative_url('/foundations/color/') %>">Foundations</a></h3>
- <p class="ad-card__text">
+<div class="card-grid">
+ <article class="card" data-link>
+ <div class="card-body">
+ <h3 style="margin:0; font: 600 var(--ad-step-0)/1.3 var(--ad-font-sans);"><a href="<%= relative_url('/foundations/color/') %>">Foundations</a></h3>
+ <p style="margin:0; color: var(--ad-color-text-secondary); font-size: var(--ad-step--1);">
Color, type, space, elevation, and motion — with contrast ratios
computed from the tokens and checked by hand, not typed by guess.
</p>
</div>
</article>
- <article class="ad-card ad-card--link">
- <div class="ad-card__body">
- <h3 class="ad-card__title"><a href="<%= relative_url('/components/button/') %>">Components</a></h3>
- <p class="ad-card__text">
+ <article class="card" data-link>
+ <div class="card-body">
+ <h3 style="margin:0; font: 600 var(--ad-step-0)/1.3 var(--ad-font-sans);"><a href="<%= relative_url('/components/button/') %>">Components</a></h3>
+ <p style="margin:0; color: var(--ad-color-text-secondary); font-size: var(--ad-step--1);">
Sixteen components across core, forms, feedback, and navigation, each
rendered live next to the exact markup that produced it.
</p>
</div>
</article>
- <article class="ad-card ad-card--link">
- <div class="ad-card__body">
- <h3 class="ad-card__title"><a href="<%= relative_url('/patterns/loop/') %>">Patterns</a></h3>
- <p class="ad-card__text">
+ <article class="card" data-link>
+ <div class="card-body">
+ <h3 style="margin:0; font: 600 var(--ad-step-0)/1.3 var(--ad-font-sans);"><a href="<%= relative_url('/patterns/loop/') %>">Patterns</a></h3>
+ <p style="margin:0; color: var(--ad-color-text-secondary); font-size: var(--ad-step--1);">
Loop and Archive — a task dashboard and a capture app — put every
component family in one screen at once.
</p>
@@ -102,42 +102,42 @@ same afternoon.
<figure class="example">
<figcaption>Everything on one card</figcaption>
<div data-layout="stack">
-<article class="ad-card ad-card--elevated" style="max-width: 26rem;">
- <div class="ad-card__body">
- <div class="ad-badge-set">
- <span class="ad-badge ad-badge--accent">Recommended</span>
- <span class="ad-badge">Unassigned</span>
+<article class="card" data-elevated style="max-width: 26rem;">
+ <div class="card-body">
+ <div class="badge-set">
+ <span class="badge" data-tone="accent">Recommended</span>
+ <span class="badge">Unassigned</span>
</div>
- <h3 class="ad-card__title">Rename the caution color</h3>
- <p class="ad-card__text">
+ <h3 style="margin:0; font: 600 var(--ad-step-0)/1.3 var(--ad-font-sans);">Rename the caution color</h3>
+ <p style="margin:0; color: var(--ad-color-text-secondary); font-size: var(--ad-step--1);">
Three people have called it "yellow" in review this month. There is no
caution token in this system — only <code>--ad-color-success</code> and
<code>--ad-color-danger</code>, fixed across every project.
</p>
</div>
- <div class="ad-card__footer">
- <button type="button" class="ad-btn ad-btn--primary ad-btn--sm">Approve</button>
- <button type="button" class="ad-btn ad-btn--ghost ad-btn--sm">Dismiss</button>
+ <div class="card-footer">
+ <button type="button" data-variant="primary" data-size="sm">Approve</button>
+ <button type="button" data-variant="ghost" data-size="sm">Dismiss</button>
</div>
</article>
</div>
<details><summary>Markup</summary>
-<pre><code><article class="ad-card ad-card--elevated" style="max-width: 26rem;">
- <div class="ad-card__body">
- <div class="ad-badge-set">
- <span class="ad-badge ad-badge--accent">Recommended</span>
- <span class="ad-badge">Unassigned</span>
+<pre><code><article class="card" data-elevated style="max-width: 26rem;">
+ <div class="card-body">
+ <div class="badge-set">
+ <span class="badge" data-tone="accent">Recommended</span>
+ <span class="badge">Unassigned</span>
</div>
- <h3 class="ad-card__title">Rename the caution color</h3>
- <p class="ad-card__text">
+ <h3>Rename the caution color</h3>
+ <p>
Three people have called it "yellow" in review this month. There is no
caution token in this system — only <code>--ad-color-success</code> and
<code>--ad-color-danger</code>, fixed across every project.
</p>
</div>
- <div class="ad-card__footer">
- <button type="button" class="ad-btn ad-btn--primary ad-btn--sm">Approve</button>
- <button type="button" class="ad-btn ad-btn--ghost ad-btn--sm">Dismiss</button>
+ <div class="card-footer">
+ <button type="button" data-variant="primary" data-size="sm">Approve</button>
+ <button type="button" data-variant="ghost" data-size="sm">Dismiss</button>
</div>
</article></code></pre>
</details>
diff --git a/install.md b/install.md
index ec5382f..b7046b0 100644
--- a/install.md
+++ b/install.md
@@ -99,18 +99,16 @@ $ bundle exec rake serve # http://127.0.0.1:4000
## What is where
-<div class="ad-table-wrap">
- <table class="ad-table ad-table--compact">
+ <table>
<thead><tr><th scope="col">Path</th><th scope="col">What it is</th></tr></thead>
<tbody>
- <tr><td class="ad-table__code">assets/css/tokens.css</td><td>Every value in the system, hand-maintained.</td></tr>
- <tr><td class="ad-table__code">_sass/</td><td>One plain CSS file per component group, concatenated into <code>style.css</code> at build time. No hexes, no pixels, no durations.</td></tr>
- <tr><td class="ad-table__code">lib/arbitrary_definitions/</td><td>The whole site builder — front matter, ERB, and Kramdown, wrapped in a layout.</td></tr>
- <tr><td class="ad-table__code">_layouts/, _includes/</td><td>Plain ERB templates for the page chrome.</td></tr>
- <tr><td class="ad-table__code">_components/, _foundations/, _patterns/</td><td>Static Markdown and HTML — the content itself, hand-written.</td></tr>
+ <tr><td><code>assets/css/tokens.css</code></td><td>Every value in the system, hand-maintained.</td></tr>
+ <tr><td><code>_sass/</code></td><td>One plain CSS file per component group, concatenated into <code>style.css</code> at build time. No hexes, no pixels, no durations.</td></tr>
+ <tr><td><code>lib/arbitrary_definitions/</code></td><td>The whole site builder — front matter, ERB, and Kramdown, wrapped in a layout.</td></tr>
+ <tr><td><code>_layouts/, _includes/</code></td><td>Plain ERB templates for the page chrome.</td></tr>
+ <tr><td><code>_components/, _foundations/, _patterns/</code></td><td>Static Markdown and HTML — the content itself, hand-written.</td></tr>
</tbody>
</table>
-</div>
## How the example demos work
@@ -124,12 +122,12 @@ the page is how the source panel gets written, so there is nothing to drift.
<figure class="example">
<figcaption>Variants</figcaption>
<div>
- <button type="button" class="ad-btn">Cancel</button>
- <button type="button" class="ad-btn ad-btn--primary">Save</button>
+ <button type="button">Cancel</button>
+ <button type="button" data-variant="primary">Save</button>
</div>
<details><summary>Markup</summary>
-<pre><code><button type="button" class="ad-btn">Cancel</button>
-<button type="button" class="ad-btn ad-btn--primary">Save</button></code></pre>
+<pre><code><button type="button">Cancel</button>
+<button type="button" data-variant="primary">Save</button></code></pre>
</details>
</figure>
```
diff --git a/rationale.md b/rationale.md
index b4cbd79..59e0cd6 100644
--- a/rationale.md
+++ b/rationale.md
@@ -23,23 +23,23 @@ are therefore changeable.
This system was not designed from a blank page. It was built by reading three
real, running personal projects and finding what they actually share:
-<div class="ad-card-grid">
- <article class="ad-card">
- <div class="ad-card__body">
- <h3 class="ad-card__title"><a href="https://github.com/kejadlen/domus">domus</a></h3>
- <p class="ad-card__text">Household inventory & document archive. Warm paper surfaces, a Georgia/Helvetica/mono mix, a swappable "Clay" accent, a bottom capture dock.</p>
+<div class="card-grid">
+ <article class="card">
+ <div class="card-body">
+ <h3 style="margin:0; font: 600 var(--ad-step-0)/1.3 var(--ad-font-sans);"><a href="https://github.com/kejadlen/domus">domus</a></h3>
+ <p style="margin:0; color: var(--ad-color-text-secondary); font-size: var(--ad-step--1);">Household inventory & document archive. Warm paper surfaces, a Georgia/Helvetica/mono mix, a swappable "Clay" accent, a bottom capture dock.</p>
</div>
</article>
- <article class="ad-card">
- <div class="ad-card__body">
- <h3 class="ad-card__title"><a href="https://github.com/kejadlen/ketchup">ketchup</a></h3>
- <p class="ad-card__text">Recurring-task tracker. All-mono interface, plain grays, red/green semantics, a flash-bar toast.</p>
+ <article class="card">
+ <div class="card-body">
+ <h3 style="margin:0; font: 600 var(--ad-step-0)/1.3 var(--ad-font-sans);"><a href="https://github.com/kejadlen/ketchup">ketchup</a></h3>
+ <p style="margin:0; color: var(--ad-color-text-secondary); font-size: var(--ad-step--1);">Recurring-task tracker. All-mono interface, plain grays, red/green semantics, a flash-bar toast.</p>
</div>
</article>
- <article class="ad-card">
- <div class="ad-card__body">
- <h3 class="ad-card__title"><a href="https://github.com/kejadlen/quire">quire</a></h3>
- <p class="ad-card__text">Personal git forge / CI server. Humanist sans + mono, a warm cream surface, a full dark palette, underline tabs.</p>
+ <article class="card">
+ <div class="card-body">
+ <h3 style="margin:0; font: 600 var(--ad-step-0)/1.3 var(--ad-font-sans);"><a href="https://github.com/kejadlen/quire">quire</a></h3>
+ <p style="margin:0; color: var(--ad-color-text-secondary); font-size: var(--ad-step--1);">Personal git forge / CI server. Humanist sans + mono, a warm cream surface, a full dark palette, underline tabs.</p>
</div>
</article>
</div>
@@ -54,39 +54,39 @@ ground, deliberately not lifted wholesale from any one of the three.
Not the quality of the individual choices. The properties below:
-<div class="ad-card-grid">
- <article class="ad-card">
- <div class="ad-card__body">
- <h3 class="ad-card__title">It is written down once</h3>
- <p class="ad-card__text">
+<div class="card-grid">
+ <article class="card">
+ <div class="card-body">
+ <h3 style="margin:0; font: 600 var(--ad-step-0)/1.3 var(--ad-font-sans);">It is written down once</h3>
+ <p style="margin:0; color: var(--ad-color-text-secondary); font-size: var(--ad-step--1);">
One hand-maintained stylesheet of custom properties. Not a Figma
library that disagrees with the CSS, and not a spreadsheet somebody
exports by hand.
</p>
</div>
</article>
- <article class="ad-card">
- <div class="ad-card__body">
- <h3 class="ad-card__title">It has names</h3>
- <p class="ad-card__text">
+ <article class="card">
+ <div class="card-body">
+ <h3 style="margin:0; font: 600 var(--ad-step-0)/1.3 var(--ad-font-sans);">It has names</h3>
+ <p style="margin:0; color: var(--ad-color-text-secondary); font-size: var(--ad-step--1);">
<code>--ad-color-danger</code> survives a change of mind about which
red. <code>#ad3a2c</code> does not.
</p>
</div>
</article>
- <article class="ad-card">
- <div class="ad-card__body">
- <h3 class="ad-card__title">It is actually used</h3>
- <p class="ad-card__text">
+ <article class="card">
+ <div class="card-body">
+ <h3 style="margin:0; font: 600 var(--ad-step-0)/1.3 var(--ad-font-sans);">It is actually used</h3>
+ <p style="margin:0; color: var(--ad-color-text-secondary); font-size: var(--ad-step--1);">
Every color, type, spacing, radius, elevation, and motion value in
the components is a <code>var(--ad-*)</code>. Not one is a literal.
</p>
</div>
</article>
- <article class="ad-card">
- <div class="ad-card__body">
- <h3 class="ad-card__title">It can be changed</h3>
- <p class="ad-card__text">
+ <article class="card">
+ <div class="card-body">
+ <h3 style="margin:0; font: 600 var(--ad-step-0)/1.3 var(--ad-font-sans);">It can be changed</h3>
+ <p style="margin:0; color: var(--ad-color-text-secondary); font-size: var(--ad-step--1);">
Because of the three above, swapping the accent for Clay, Ochre, Ink
Blue, or Plum is a one-line diff.
</p>