Zazz Design Framework
Foundations

Theme

Theme tokens for base, overlay, brand, and status. Light and dark modes attached to the variable, not the layer.

Theme is where modes live. It is the surface layer every component consumes, and the only layer that swaps values when the mode switches.

Theme tokens describe roles: --background, --card, --primary, --destructive. They resolve to palette values based on the active mode. No component reaches past theme to grab a palette value directly. Change --primary at the theme layer, and every primary surface in the product follows.

Theme follows shadcn/ui's background-foreground pattern, with a few departures noted at the bottom of this page.

Base

Base tokens cover the bedrock surfaces and the text that sits on them.

TokenLightDarkRole
--background--neutral-50--neutral-950The page surface everything else sits on.
--foreground--neutral-900--whiteDefault text.
--border--neutral-200--neutral-800Default border lines.
--border-foreground--tint-950--tint-100Text color when it sits on a border (outlined buttons, ghost inputs, bordered tags).
--card--white--neutral-900Card surfaces, one step elevated from background.
--card-foreground--neutral-900--whiteText on cards.
--input--neutral-50--tint-50Input field background.
--input-foreground--neutral-900--whiteInput text.
.card {
  background: var(--card);
  color: var(--card-foreground);
  border: 1px solid var(--border);
  border-radius: var(--radius-card);
}

Overlay

Overlay tokens cover dimming, fading, and the muted text that goes with them.

TokenLightDarkRole
--muted--shade-50--tint-50A subtle dim over whatever sits below.
--muted-foreground--shade-600--tint-600Text on muted surfaces. Also used for de-emphasized labels and helper copy.
--faded--tint-100--shade-100A subtle fade over whatever sits below.
--faded-foreground--tint-600--shade-600Text on faded surfaces.

Overlays swap their underlying primitive between modes: --muted uses shade-* in light and tint-* in dark; --faded does the opposite. The semantic stays constant. Muted is always darker than the surface, faded is always lighter, even though the underlying token shifts.

.section-divider {
  background: var(--muted);
  color: var(--muted-foreground);
}

Brand

Brand tokens map the active mode to a step on each corporate scale.

TokenLightDarkRole
--primary--primary-600--primary-500Primary brand.
--primary-foreground--white--whiteText on primary surfaces.
--secondary--secondary-600--secondary-500Secondary brand.
--secondary-foreground--white--whiteText on secondary surfaces.
--tertiary--tertiary-500--tertiary-400Tertiary brand.
--tertiary-foreground--white--whiteText on tertiary surfaces.

Brand colors step lighter in dark mode (600 → 500, 500 → 400) so they stay readable against the darker background. Tertiary takes a slightly lighter step than primary and secondary. Most tertiary brand colors carry more chroma per step, so a lighter base reads cleaner.

Re-target the scale steps at the theme layer to rebrand without touching components. See Corporate for the full scales.

Status

Status tokens cover info, success, warning, and destructive.

TokenLightDarkRole
--infooklch(0.5876 0.1389 241.97)oklch(0.5 0.1193 242.75)Informational, neutral notices.
--successoklch(0.596 0.1274 163.23)oklch(0.5081 0.1049 165.61)Successful outcomes, positive confirmations.
--warningoklch(0.6658 0.1574 58.32)oklch(0.5553 0.1455 49)Cautionary notices, soft alerts.
--destructiveoklch(0.5771 0.2152 27.33)oklch(0.5054 0.1905 27.52)Errors, irreversible actions.
--*-foreground--white--whiteText on status surfaces.

Status colors step darker in dark mode (lightness drops by roughly 0.08–0.11), opposite to brand. The reasoning: a status color is a signal, not an identity. Lightening a destructive red in dark mode makes errors the brightest thing on the page. Darkening keeps the signal present without overshouting the rest of the UI.

Status values are written in raw OKLCH so they sit in the same color space as every other Zazz token. Lightness, chroma, and hue can be tuned independently to hit a contrast target in either mode.

How modes work

Zazz responds to both system preference and an explicit class.

:root {
  /* light by default */
}

@media (prefers-color-scheme: dark) {
  :root {
    /* dark when the OS asks for it */
  }
}

:is(.dark, .dark *) {
  /* dark whenever .dark is on an ancestor */
}

Force a mode by adding .dark (or any class you wire up) anywhere up the tree: for an in-page theme switcher, a preview panel, or a section that overrides the system default.

To add a new mode (high-contrast, brand-takeover, holiday), repeat the pattern: a new selector that re-points the same theme variables. Components never branch on mode; they consume theme tokens and trust them.

Beyond Tailwind and shadcn

The theme layer is compatible with Tailwind and shadcn and borrows the parts that work: background-foreground naming, color-scale conventions, the .dark ancestor pattern. Where Zazz makes different choices, the framework is geared more toward marketing and ecommerce sites than toward web apps. Both work; the defaults are tuned for the former.

The choices worth knowing:

  • A text-on-border role exists. --border-foreground colors text that sits on a border (outlined buttons, ghost inputs, bordered tags) without reaching into a palette directly.
  • Overlays split into muted and faded. Muted dims, faded fades. The split matters when stacking on product photography, building hero overlays, or designing dark-mode surfaces that need a different scrim than the light equivalent.
  • Tertiary brand is a first-class role. Marketing and ecommerce clients often have three identity colors: a primary, an accent, and a "callout" hue used on sale tags, badges, or seasonal moments.
  • Four status roles ship out of the box. Info, success, warning, and destructive in a consistent foreground-paired shape, so checkout flows and form validation work without bespoke wiring.
  • Status darkens in dark mode. Opposite of brand. Alerts should not be the loudest thing on a marketing surface that's already loud with imagery.

Where to next

On this page