Adding custom styles
When tokens and utilities don't cover it, extend Zazz without touching its source. Override a token, drop a rule into the right layer, or write a one-off inline.
Zazz's tokens and utilities cover most of what you'll build, but every project has a one-off. When you hit one, you extend the system from the outside, and you never edit files in zazz/styles/. Keep your additions in your own stylesheet, loaded after main.css, and they land in the cascade after Zazz's own layers.
<link rel="stylesheet" href="./zazz/styles/main.css" />
<link rel="stylesheet" href="./your-styles.css" />Start with a token override
Most customizations aren't new CSS at all; they're a different value for an existing token. Reach for this first, at the narrowest scope that works:
/* your-styles.css */
:root {
--primary: oklch(0.6 0.2 145); /* global: re-skin the brand */
--button-radius: var(--radius-full); /* component default: pill buttons */
}<!-- instance: this element only -->
<div class="card" style="--card: var(--muted)">…</div>A token override is the most durable kind of custom style: it composes with dark mode, stays consistent with the rest of the system, and never fights a component rule.
Add your own utilities
When you want a reusable class that isn't in the set, add it to @layer zazz.utilities so it lands in the right layer, winning over components like every other utility. Wrap the selector in :where() to keep it zero-specificity and overridable:
@layer zazz.utilities {
:where(.text-gradient) {
background: linear-gradient(90deg, var(--primary), var(--secondary));
background-clip: text;
color: transparent;
}
}Compose from tokens (var(--primary), var(--gap-md)) rather than literals, and your utility inherits the theme and dark-mode behavior automatically.
Add your own components
For a larger pattern with variants, write it in @layer zazz.components and give it token hooks, mirroring how Zazz components are built. See File anatomy:
@layer zazz.components {
:where(.callout) {
--callout-background: var(--muted);
background: var(--callout-background);
border-inline-start: var(--step-1) solid var(--primary);
padding: var(--gap-md);
border-radius: var(--radius-md);
}
}Because it's in components, your utilities still override it, so a .callout p-xl works as expected.
The escape hatch: unlayered CSS
A rule written outside any layer beats everything in Zazz (every component and every utility) regardless of specificity:
/* No @layer wrapper. This wins over all layered styles. */
.button {
border-radius: 0;
}That makes it powerful and easy to misuse. It's the right tool when you deliberately need to trump the system; the rest of the time, a token override or a properly layered rule keeps you inside it. See layers.css for why unlayered styles win.
Pick the smallest tool
In order of preference:
- Token override: a different value for an existing variable. Composes with everything.
- Layered rule: a new utility or component in
@layer zazz.utilities/@layer zazz.components. - Unlayered rule: only when you intend to override all of Zazz.
The one rule that doesn't bend: don't edit zazz/styles/. Every customization above lives in your own files, so updating Zazz never clobbers your work.