Theme
/* default-semantic-tokens.css - the design system */
:root {
  --background:         oklch(1 0 0);
  --foreground:         oklch(0.145 0.005 285);
  --primary:            oklch(0.205 0.005 285);
  --primary-foreground: oklch(0.985 0.002 247);
  --border:             oklch(0.88 0.004 247);
  --radius:             0.625rem;
  /* ...all color pairs, shadows, spacing */
}
.dark { /* same keys, dark values */ }
# Pattern: Button
## Native basis
`<button>` element. Also works on `<a>` for link-style buttons.
## Native Web APIs
- `<button>` - native clickable element with built-in keyboard and form support
- `:focus-visible` - keyboard-only focus ring
- `commandfor` / `command` - declarative button→dialog/popover triggers without JS
  # ...prefers-reduced-motion, prefers-contrast, forced-colors
## Structure
<button class="btn" data-variant="default">Click me</button>
## Variants
| data-variant  | Surface              | Text                     | Hover           |
|---------------|----------------------|--------------------------|-----------------|
| default       | --primary            | --primary-foreground     | opacity: 0.88   |
| secondary     | --secondary          | --secondary-foreground   | opacity: 0.8    |
| outline       | --background + border | --foreground             | --accent bg     |
| ghost         | transparent          | --foreground             | --accent bg     |
| destructive   | --destructive        | --destructive-foreground | opacity: 0.88   |
| link          | transparent          | --primary                | underline       |
  # ...sizes, accessibility, notes
/* button.css - styled with tokens */
@layer components {
  .btn {
    display: inline-flex;
    align-items: center;
    border-radius: var(--radius-md);
    font-weight: 500;
  }
  .btn[data-variant="default"] {
    background: var(--primary);
    color: var(--primary-foreground);
  }
  /* ...secondary, destructive, outline, ghost, link */
}
<button class="btn" data-variant="default"
        data-dialog-trigger="confirm">
  Open Dialog
</button>
<dialog id="confirm" class="dialog">
  <div class="dialog-content">...</div>
</dialog>
// dialog.js - wire interactivity through df$ (query + morph, installed by core)
function init() {
  for (const trigger of df$('[data-dialog-trigger]:not([data-init])')) {
    df$(trigger).attr('data-init', '');           // the double-init guard
    const dialog = df$(`#${trigger.dataset.dialogTrigger}`).get(0);
    if (!dialog) continue;
    df$(trigger).on('click', () => dialog.showModal()); // native protocol stays native
  }
  /* ...close-on-backdrop, [data-dialog-close] buttons, focus restore */
}
init();
new MutationObserver(init).observe(document, { childList: true, subtree: true });

Where to go next

Configure variants and sizes in markup with the Data Attribute API, drive and observe the interactive components' named states through the State API, and read a component's component skill before writing its HTML.

Comments, ideas or improvements? Edit this page's source on GitHub