Getting started

How to add Canon to a page, how its markup is read, what needs JavaScript and which browsers it supports. For what Canon is and why, start with the Introduction.

Quick start

Link Canon's stylesheet (it brings the core with it) and, for behavior, canon.js. There's no build step, so a page can take both from a CDN:

index.html
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/canon-ui@0.2.1/css/canon/canon.css">
<script src="https://cdn.jsdelivr.net/npm/canon-ui@0.2.1/js/canon.js" defer></script>

Or install it from npm:

Terminal
npm i canon-ui

Then link the two files from where the package put them, or from wherever your app serves them. Keep the css folder's files together, since a stylesheet finds its icons and fonts next to it:

index.html
<link rel="stylesheet" href="node_modules/canon-ui/css/canon/canon.css">
<script src="node_modules/canon-ui/js/canon.js" defer></script>

Then write the markup each component's page shows. A primary action:

Button
<button type="button" class="canon-button" data-emphasis="high">
  <span class="canon-icon" data-icon="plus" aria-hidden="true"></span>
  <span class="canon-button-label">New invoice</span>
</button>

A field, its label tied to its input:

Field
<div class="canon-field">
  <div class="canon-label">
    <label class="canon-label-text" for="email">Billing email</label>
  </div>
  <div class="canon-text-input">
    <input id="email" type="email" placeholder="billing@globex.example">
  </div>
</div>

Principles

HTML first

Native elements wherever they exist: a real <button>, <dialog>, <details>. Meaning lives in attributes, not in wrappers.

The platform first

Popovers, dialogs, invoker commands and anchor positioning do the work; canon.js fills only the gaps, and steps back as browsers catch up.

Accessible by contract

Names, roles, focus and keyboard are part of each component's markup, so a theme can't lose them and an app doesn't have to remember them.

The author beats the theme

Hints like data-size and data-density sit above the theme in the cascade. The author is never required to say anything; the theme decides the rest.

Every direction, every language

Logical properties throughout, so right-to-left works as well as left-to-right; every word Canon writes comes from CSS, ready to translate.

Works without JavaScript

Each component says what it does without canon.js and what it adds with it. When a script doesn't load, the page still works.

How the markup speaks

Every attribute has one job. Knowing which channel says what makes any component's page quick to read.

Markup channels
ChannelSaysFor example
classWhat it is, and its partscanon-button, canon-button-label
data-*What it means: its variantdata-emphasis="high", data-intent="danger"
ARIA and native attributesIts statedisabled, aria-pressed, aria-current
HintsWhat only the author knows about its place: its room, its density, icons alone. The theme decides how that looksdata-size, data-density, data-display
data-stateState canon.js keeps (rare)data-state="changed"

Hints are inherited: put one on a container and everything inside follows, unless it says otherwise.

A hint on a container
<div data-size="sm">
  …every control in here is small, unless one says otherwise…
</div>

What needs JavaScript

Each component's specification gives its tier, so you know what to expect before you build:

T0
HTML and CSS only. Buttons, tags, tables, cards.
T1
The platform's own features: popovers, dialogs, invoker commands, anchor positioning (filled in where a browser lacks them).
T2
Plain controls, more with canon.js: a combobox, a date field, a number input.
T3
Needs JavaScript: a command palette's search.

Browser support

Canon supports current versions of Chrome, Edge, Firefox and Safari. It needs cascade layers, :has(), light-dark(), container queries, popovers and dialogs, which every current browser has (Chrome and Edge 123, Firefox 125 and Safari 17.5 onwards). Newer platform features are used where a browser has them; elsewhere canon.js fills them in, or the page does without:

Newer platform features and what happens without them
FeatureCanon uses it forWithout it
Anchor positioningPlacing menus, popovers, lists and tooltips next to what opened themcanon.js places them (in browsers older than Chrome 125, Safari 26 or Firefox 147)
Invoker commands (commandfor)Buttons that open dialogs and run Canon's commandscanon.js fills them in
Interest invokers (interestfor)Tooltips that show on hover and focuscanon.js fills them in
closedby on dialogsClosing a dialog with a click beside itcanon.js fills it in
field-sizingFields that grow with what's typedcanon.js sizes them
Typed attr() and if()Colors from data-seed, sizes from attributescanon.js and the stylesheet cover them
Customizable selectA Select's list in Canon's lookThe browser's own list, which works the same
hidden="until-found"Find in page reaching text in a Tab's hidden panel, and choosing its tabFind in page only searches the panel that shows
Scroll-state container queriesA line under a stuck Page Header or over a stuck Save BarNo line; they stick the same

These docs say so at the top of a page when your browser is missing one of them.

Reading these docs

Each component has a page in three parts:

Reference
Every variant, state and edge case, as working markup: what a theme must render, and what you can copy.
Playground
The component's options as controls, the result, and its exact HTML to copy or edit in place.
Specification
The contract in full (docs/components/…): elements, attributes, behavior, accessibility, and what a theme must do.

Patterns put the components together into whole screens: finding, opening, editing and saving records. Search everything with the top bar's search, or ⌘K.

The contract check

For developers and theme authors: css/canon.debug.css outlines, in a dashed amber line, any markup on the page that breaks a component's contract: a missing name, a part in the wrong place, a second primary action. Add js/canon.debug.js and each outlined element gets a warning right below it: the contract it breaks, why, and the exact condition it fails. Load both while you build, never in production.

In these docs, turn it on from the search (⌘K, "Turn contract check on") or add ?debug to a page's address.

Making it yours

Canon's styles sit in cascade layers, so your own unlayered CSS always wins, with no specificity fights. To go further, write a theme: one stylesheet that declares Canon's layers, imports css/canon/canon.core.css and draws every component, in place of css/canon/canon.css. Each component's specification lists what a theme must do. The markup never changes, so pages move to a new theme by changing one line. Themes compares the ones that come with Canon.