Colors
Color in Zazz is role-first. Pick a role like primary or muted-foreground and light/dark swap for free; drop to a numeric scale only when a role can't express it.
Color in Zazz is chosen by role, not by shade. Instead of "neutral-600," you reach for muted-foreground; instead of "indigo-600," you reach for primary. Because roles resolve through light-dark(), a role-based design is correct in both themes with no extra work, and recoloring the brand is a one-line change.
<div class="bg-card text-card-foreground border border-border p-md rounded-lg">
<p class="text-muted-foreground">Role classes carry their own light/dark values.</p>
</div>Every role ships text-{role}, bg-{role}, and border-{role} utilities.
The roles
Surfaces and text, paired
Each surface has a matching foreground tuned for contrast on it:
| Surface | Foreground | Use for |
|---|---|---|
background | foreground | The page itself |
card | card-foreground | Cards, raised panels |
input | input-foreground | Form control fills |
muted | muted-foreground | Dimmed surfaces and secondary text |
faded | faded-foreground | Faded surfaces and tertiary text |
border | border-foreground | Hairlines and dividers |
muted dims (it's darker than its surface in light mode); faded lightens. Pair a surface with its foreground and contrast takes care of itself.
The popover surface (--popover / --popover-foreground) is the one exception. It's a token that menu, tooltip, and dialog components read directly, with no text-/bg- utility of its own.
Brand
primary, secondary, tertiary, each with a -foreground for text and icons placed on it.
Status
info, success, warning, destructive, same -foreground pairing. Use destructive for dangerous actions and error states.
When a role won't do: scales and overlays
Reach past roles only when you need a specific shade: a fixed tint, a backdrop, a precise accent.
- Brand & neutral scales:
--primary-50…--primary-950(and--secondary-*,--tertiary-*,--neutral-*), plus--whiteand--black. These are tokens only, no utility classes. Reach them in custom CSS or inline:style="color: var(--primary-600)". - Overlays:
--shade-*darkens (from neutral-950, ideal for modal backdrops like--shade-800) and--tint-*lightens (from white).*-noneis transparent. Overlays do shipbg-shade-*andbg-tint-*utilities, since dimming a surface is common. - Absolute:
text-white,text-black,bg-white,bg-black,bg-transparent.
A fixed scale step stays the same in both themes, which is exactly why roles, not scales, are the default.
Built on oklch
Zazz colors are authored in oklch(), a perceptually uniform color space. That's what lets the system derive consistent variant tints and overlays with oklch(from … ) and color-mix(): equal lightness steps actually look equal, and a single hue shift re-skins a whole ramp cleanly. oklch() is Baseline and widely available.
Color alpha vs element opacity
Two different fades, and it's worth not confusing them:
- Element opacity (
opacity-{0,25,50,75,100}) fades the whole element and its children. - Color alpha (
bg-opacity-*,text-opacity-*,border-opacity-*) dims only that color channel, leaving children untouched. It's backed by@property-registered variables that don't inherit, so a faded background never bleeds into the text on top of it.
<!-- the surface is translucent; the text stays fully opaque -->
<div class="bg-primary bg-opacity-25 text-foreground">…</div>Recoloring the brand
Because everything reads tokens, changing your brand color is a single override. See Theme variables for the three scopes:
:root {
--primary: oklch(0.6 0.2 145); /* every primary surface, ring, and accent follows */
}To shift the entire ramp (so derived tints match), override the numeric scale instead, then point the role at it. For where to put these overrides, see Adding custom styles.
Dark mode
Dark mode is on from the first line. Use a role color and both themes are correct for free, with no .dark overrides and no duplicated styles.
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.