Skip to content

Type to search the docs, API, and blog.

↑↓ navigate↵ openesc close
Zazz 0.7 alpha
Docs navigation

Docs

Quick start

A page in five steps: one head snippet, a layout, a header, a hero, and a theme.

You need a text editor and a browser. You install nothing; the kit loads from a CDN. When you are ready to own the files, the installation page covers the CLI.

1. The head

Create index.html and paste this into <head>. It is the output of the kit's own buildHead() with the fonts block off: the stylesheet, the import map for the kit's bare dependencies (signal-polyfill and the Embla packages), the one polyfill, the script, and a theme script that pins the stored theme-dark / theme-light switch before first paint.

<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<!-- Paint the right theme (background, form controls, scrollbars) before CSS loads -->
<meta name="color-scheme" content="light dark">
<!-- Zazz styles: the whole kit, one request -->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@zazz-ui/core@0.7.0/dist/zazz.css">
<!-- Import map: bare specifiers in Zazz scripts resolve to pinned, SRI-checked files -->
<script type="importmap">
{
  "imports": {
    "signal-polyfill": "https://cdn.jsdelivr.net/npm/signal-polyfill@0.2.2/dist/index.js",
    "embla-carousel": "https://cdn.jsdelivr.net/npm/embla-carousel@8.6.0/esm/embla-carousel.esm.js",
    "embla-carousel-autoplay": "https://cdn.jsdelivr.net/npm/embla-carousel-autoplay@8.6.0/esm/embla-carousel-autoplay.esm.js",
    "embla-carousel-auto-scroll": "https://cdn.jsdelivr.net/npm/embla-carousel-auto-scroll@8.6.0/esm/embla-carousel-auto-scroll.esm.js",
    "embla-carousel-class-names": "https://cdn.jsdelivr.net/npm/embla-carousel-class-names@8.6.0/esm/embla-carousel-class-names.esm.js"
  },
  "integrity": {
    "https://cdn.jsdelivr.net/npm/signal-polyfill@0.2.2/dist/index.js": "sha384-0OqM7OXGT6Oz1z98u/geiFn/FUyz4iWexkXHCvbIGXRRDXl6bp6IKc0j78AQ6DEK",
    "https://cdn.jsdelivr.net/npm/embla-carousel@8.6.0/esm/embla-carousel.esm.js": "sha384-GXjfyU1fSHHeweoYd3rwo7OBKYtkq+JcsDoe8VDRc6znCty2ZTzyVvX1DwqCE+9t",
    "https://cdn.jsdelivr.net/npm/embla-carousel-autoplay@8.6.0/esm/embla-carousel-autoplay.esm.js": "sha384-AX49yBc7zd/CT8ZqNMRiiqqVSV9xfxb27VwEGziPioT3wL4k4C5BW2qwefdWuN4J",
    "https://cdn.jsdelivr.net/npm/embla-carousel-auto-scroll@8.6.0/esm/embla-carousel-auto-scroll.esm.js": "sha384-SPdnL1eic73WfM72zFEf0RJ5PdUC7TL3Yb5jOl1JC3/l4D7EsVxwO8wFnnezlMSu",
    "https://cdn.jsdelivr.net/npm/embla-carousel-class-names@8.6.0/esm/embla-carousel-class-names.esm.js": "sha384-ZWno/FqzYeWE5Ils19GIhep1AZ5bMl0mp9wmm6baTJE4+pjMjOdTnE55SjUQAdVh"
  }
}
</script>
<link rel="modulepreload" href="https://cdn.jsdelivr.net/npm/@zazz-ui/core@0.7.0/dist/zazz.js">
<!-- Polyfill: Interest Invokers (interestfor) — native in Chromium only -->
<script type="module" src="https://cdn.jsdelivr.net/npm/invokers@2.2.2/dist/esm/production/interest.js" integrity="sha384-hR2BVNtsS7fIIMwOm+f7MY0JZfuByGhQQfevCBB6evFATKzBsTw0JzH/RxD94z2T" crossorigin="anonymous"></script>
<!-- Zazz behavior: the whole kit, one module -->
<script type="module" src="https://cdn.jsdelivr.net/npm/@zazz-ui/core@0.7.0/dist/zazz.js"></script>
<!-- Theme: apply the persisted (or preferred) scheme before first paint -->
<script>
  (() => {
    try {
      // A stored choice pins the scheme with the theme-dark / theme-light
      // switch; with no choice the page follows the system scheme.
      const storedTheme = localStorage.getItem("theme");
      if (storedTheme === "dark" || storedTheme === "light") {
        const root = document.documentElement;
        root.dataset.ui = ((root.dataset.ui || "") + " theme-" + storedTheme).trim();
      }
    } catch {
      /* no storage access: follow the system scheme */
    }
  })();
</script>

2. A layout

Add a header and a main region. data-ui="layout" turns an element into the band grid for its children: everything inside is capped and centered at the xl band unless placed elsewhere.

<body>
  <header data-ui="layout" style="--py: 4; --border-b: 1">
    <div style="--display: flex; --items: center; --justify: space-between">
      <a href="/" data-ui="text-lg" style="--font-weight: strong">Northwind</a>
      <nav style="--display: flex; --gap: 1">
        <a href="#menu" data-ui="button" data-button-variant="ghost">Menu</a>
        <a href="#signup" data-ui="button" data-button-variant="primary">Join</a>
      </nav>
    </div>
  </header>
  <main data-ui="layout"></main>
</body>

Three things happened in that header. --py: 4 is four steps on the spacing scale. --border-b: 1 is a one-pixel border in --color-border. And the links became buttons through an identity and a preset, not a class.

3. A hero

Inside <main>:

<section style="--py: 24; --text-align: center">
  <p data-ui="text-eyebrow">Small-batch roasting since 2019</p>
  <h1 data-ui="text-h4" style="--mt: 2; --text-wrap: balance">Coffee worth slowing down for</h1>
  <p data-ui="text-lg" style="--mt: 4; --mx: auto; --max-w: var(--article-sm); --text: var(--color-muted-foreground)">
    Beans roasted every morning, shipped the same day.
  </p>
</section>

text-eyebrow, text-h4, and text-lg are typography roles: each bundles family, size, weight, leading, tracking, and case, and every size is fluid. Write the text in sentence case; the eyebrow's capitals come from the role.

4. Cards in a grid

<section id="menu" style="--py: 8">
  <div style="--display: grid; --grid-cols: 1; --grid-cols--md: 3; --gap: 4">
    <article data-ui="card" style="--p: 6; --display: flex-col; --gap: 2">
      <span data-ui="badge" data-badge-variant="muted">Espresso</span>
      <h2 data-ui="text-lg">The classic</h2>
      <p data-ui="text-sm" style="--text: var(--color-muted-foreground)">
        A Honduran single origin with notes of dark chocolate and orange peel.
      </p>
    </article>
    <!-- two more cards -->
  </div>
</section>

--grid-cols--md: 3 is a breakpoint tier: one column until the container is 65 characters wide, three from there. Breakpoints read the nearest inline-size container, not the window, so the same card grid works in a sidebar.

5. A theme

Open the page. The text uses the system fonts, and the colors and form controls already follow the system color scheme, because every color role is a light-dark() pair. Pin a scheme with data-ui="theme-dark" on <html> (the head's script does that from a stored choice), or change the look everywhere with a few tokens:

<style>
  @layer overrides {
    :root {
      --color-primary: light-dark(oklch(0.45 0.15 30), oklch(0.8 0.12 30));
      --radius-multiplier: 0.5;
      --ui-button-font-weight: 600;
    }
  }
</style>

A token recolors every primary surface; a hook retunes every button. Neither touches your markup. The theming page has the whole model.

Next

Add <ui-debug data-debug-domains="localhost"></ui-debug> to the body while you work: it warns in the console about a misspelled utility, a tier without a base, or a preset on the wrong element. Then read the core concepts, or jump to the API reference.