Seventeen components. Selector is always data-component; modifiers are extra attributes; named children are data-slot.
<button data-component="button" data-variant="secondary" data-size="sm">
Label
</button><article data-component="card">
<div data-slot="header">Title</div>
<div data-slot="body">Content</div>
<div data-slot="footer">…</div>
</article><span data-component="badge" data-variant="success">Active</span><input data-component="input" type="email" data-state="error">
<textarea data-component="textarea" rows="4"></textarea>
<select data-component="select">…</select>The sticky header at the top of this page. Slots: brand, nav, actions.
<header data-component="topbar">
<a data-slot="brand" href="/">Logo</a>
<nav data-slot="nav">…</nav>
<div data-slot="actions">…</div>
</header>Pure markup - Canon ships no JavaScript, so showing and hiding it is yours.
<div data-component="modal" role="dialog" aria-modal="true">
<div data-slot="panel">
<div data-slot="header">Title</div>
<div data-slot="body">…</div>
<div data-slot="footer">…</div>
</div>
</div><span data-component="avatar">MA</span>
<span data-component="avatar"><img src="…" alt=""></span>Requests this month
Uptime
<span data-component="stat">4.8M</span>
<p data-tone="subtle">Requests this month</p>| Plan | Seats | Status |
|---|---|---|
| Starter | 3 | Active |
| Growth | 25 | Trial |
<table data-component="table">
<thead><tr><th>Plan</th></tr></thead>
<tbody><tr><td>Starter</td></tr></tbody>
</table>The current item is aria-current="page", never a data-* value. Marking it correctly is what makes the page accessible, so there is no second state to forget.
<nav data-component="nav">
<a href="/overview">Overview</a>
<a href="/shipments" aria-current="page">Shipments</a>
</nav>Any request that reaches your endpoint, successful or not.
Yes. The difference is prorated to the day.
Zero JavaScript, on <details>. The caret is generated, so the markup carries no icon to get wrong.
<details data-component="disclosure">
<summary>What counts as an event?</summary>
<p>Any request that reaches your endpoint.</p>
</details><hr data-component="divider">
<hr data-component="divider" data-variant="strong">On <ol>, because the order is the meaning. Numbers, ticks and rails are generated. The current step is aria-current="step", done ones are data-state="complete", and upcoming is the absence of both.
<ol data-component="stepper">
<li data-state="complete">Cart</li>
<li aria-current="step">Delivery</li>
<li>Payment</li>
</ol>The API is read-only on Sunday between 02:00 and 04:00 UTC.
Your invoice for March is settled.
Add a card to keep your projects running.
Rows 12 and 40 are missing a customer id.
The block-level message badge cannot be, since a badge is an inline span. It carries no role by itself: add role="status" when it appears in response to something the user did, role="alert" only when it must interrupt.
<div data-component="alert" data-variant="error" role="status">
<h3>We could not process the file</h3>
<p>Rows 12 and 40 are missing a customer id.</p>
</div>On <nav> around an <ol>. The separators are generated, so no slash sits in the markup for a screen reader to announce. The last item is aria-current="page".
<nav data-component="breadcrumb" aria-label="Breadcrumb">
<ol>
<li><a href="/docs">Docs</a></li>
<li><a href="/docs/components" aria-current="page">Components</a></li>
</ol>
</nav>