CanonCSS
GitHub
Menu

Components

Seventeen components. Selector is always data-component; modifiers are extra attributes; named children are data-slot.

button

<button data-component="button" data-variant="secondary" data-size="sm">
  Label
</button>

card

With slots
Header, body and footer are all optional.
No slots at all - the card pads itself.
<article data-component="card">
  <div data-slot="header">Title</div>
  <div data-slot="body">Content</div>
  <div data-slot="footer">…</div>
</article>

badge

NeutralBrandSuccessWarningErrorInfo
<span data-component="badge" data-variant="success">Active</span>

input / textarea / select

<input data-component="input" type="email" data-state="error">
<textarea data-component="textarea" rows="4"></textarea>
<select data-component="select">…</select>

topbar

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>

modal

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>

avatar

SMMDLG
<span data-component="avatar">MA</span>
<span data-component="avatar"><img src="…" alt=""></span>

stat

4.8M

Requests this month

99.98%

Uptime

<span data-component="stat">4.8M</span>
<p data-tone="subtle">Requests this month</p>

table

PlanSeatsStatus
Starter3Active
Growth25Trial
<table data-component="table">
  <thead><tr><th>Plan</th></tr></thead>
  <tbody><tr><td>Starter</td></tr></tbody>
</table>

nav

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>

disclosure

What counts as an event?

Any request that reaches your endpoint, successful or not.

Can I change plan mid-cycle?

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>

divider



<hr data-component="divider">
<hr data-component="divider" data-variant="strong">

stepper

  1. Cart
  2. Delivery
  3. Payment

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>

alert

Scheduled maintenance

The API is read-only on Sunday between 02:00 and 04:00 UTC.

Payment received

Your invoice for March is settled.

Trial ends in three days

Add a card to keep your projects running.

We could not process the file

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>

breadcrumb

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>