Theming

Table of contents

The convention uses a simple background and foreground colors. Take a look at the following CSS variables:

--z-primary: oklch(27.4% 0.006 286.033);
--z-primary-f: oklch(98.5% 0 0);

The background color of the following component will be oklch(27.4% 0.006 286.033) and the foreground color will be oklch(98.5% 0 0).

<div
  class="bg color"
  style="--bg: var(--z-primary); --color: var(--z-primary-f);"
></div>

List of variables

Here’s the list of variables available for customization:

/* Background */
--z-bg: #fff;
--z-bg-f: oklch(21% 0.006 285.885);

/* Card */
--z-card: #fff;
--z-card-f: oklch(21% 0.006 285.885);

/* Popover/Dropdown */
--z-drop: #fff;
--z-drop-f: oklch(21% 0.006 285.885);

/* Primary */
--z-primary: oklch(27.4% 0.006 286.033);
--z-primary-f: oklch(98.5% 0 0);

/* Secondary */
--z-secondary: #000;
--z-secondary-o: 4%;
--z-secondary-f: oklch(21% 0.006 285.885);

/* Muted */
--z-muted: #000;
--z-muted-o: 4%;
--z-muted-f: oklch(44.2% 0.017 285.786);

/* Accent */
--z-accent: #000;
--z-accent-o: 4%;
--z-accent-f: oklch(21% 0.006 285.885);

/* Destructive/Danger */
--z-danger: oklch(63.7% 0.237 25.331);
--z-danger-f: oklch(50.5% 0.213 27.518);

/* Info */
--z-info: oklch(62.3% 0.214 259.815);
--z-info-f: oklch(48.8% 0.243 264.376);

/* Success */
--z-success: oklch(69.6% 0.17 162.48);
--z-success-f: oklch(50.8% 0.118 165.612);

/* Warning */
--z-warning: oklch(76.9% 0.188 70.08);
--z-warning-f: oklch(55.5% 0.163 48.998);

/* Border & Input */
--z-border: #000;
--z-border-o: 10%;
--z-input: #000;
--z-input-o: 10%;

/* Focus Ring */
--z-focus: oklch(70.5% 0.015 286.067);

/* Charts */
--z-chart-1: oklch(64.6% 0.222 41.116);
--z-chart-2: oklch(60% 0.118 184.704);
--z-chart-3: oklch(30.2% 0.056 229.695);
--z-chart-4: oklch(82.8% 0.189 84.429);
--z-chart-5: oklch(76.9% 0.188 70.08);

Adding new colors

To add new colors, simply add them to your main CSS file.

:root {
  --z-custom: oklch(0.84 0.16 84);
  --z-custom-f: oklch(0.28 0.07 46);
}

.dark {
  --z-custom: oklch(0.41 0.11 46);
  --z-custom-f: oklch(0.99 0.02 95);
}

@layer theme {
  --color-custom: var(--z-custom);
  --color-custom-f: var(--z-custom-f);
}

You can now use it via utility class in your components.

<!-- Direct -->
<div
  class="bg color"
  style="--bg: var(--z-custom); --color: var(--z-custom-f);"
></div>

<!-- Proxy -->
<div
  class="bg color"
  style="--bg: var(--color-custom); --color: var(--color-custom-f);"
></div>

Custom palette

1. Start by picking your desired color and use that to assign values to the --z-primary, --z-primary-f, and --z-focus tokens. These tokens represent your main theme color, its contrasting foreground color, and the focus color.

2. Use the CSS snippet below as your starting point and replace the * with your theme name (e.g. indigo, cyan, fuchsia, etc.). You only need to update the values for --z-primary, --z-primary-f, and --z-focus for both light and dark modes, but you’re free to customize everything else if needed.

.z-theme-emerald {
  --z-primary: /* oklch(...) */;
  --z-primary-f: /* oklch(...) */;
  --z-focus: /* oklch(...) */;
}

.dark.z-theme-emerald {
  --z-primary: /* oklch(...) */;
  --z-primary-f: /* oklch(...) */;
  --z-focus: /* oklch(...) */;
}

Setting the default palette

To set your newly added palette as the default, simply update the script in your <head> tag to reference the new theme name, like so:

<script>
  const htmlElement = document.documentElement;

  const __Z_THEME__ = JSON.parse(localStorage.getItem("__Z_THEME__") || "{}");

  if (
    __Z_THEME__.mode === "dark" ||
    (!__Z_THEME__.mode &&
      window.matchMedia("(prefers-color-scheme: dark)").matches)
  ) {
    htmlElement.classList.add("dark");
  } else {
    htmlElement.classList.remove("dark");
  }

  htmlElement.classList.add(__Z_THEME__.layout || "z-layout-small");
  htmlElement.classList.add(__Z_THEME__.ff || "z-ff-a");
</script>

Controls

Beyond colors, the API handles structural properties to maintain a consistent physical appearance across components.

So, instead of hardcoding static pixel values, components dynamically calculate their size. For example, a button’s height is determined by multiplying --z-global-font-size by the --z-interactive-height-multiplier. The button’s block padding and inline padding are calculated similarly using their respective multipliers. This keeps the entire system proportionally synced whenever you change the base font size.

Let’s look at a common, real-world example: readability.

In text-heavy interfaces—like story-sharing platforms, blogs, or reading apps—giving users control over readability is crucial. While users can rely on native browser zooming, this often leads to broken layouts, horizontal scrolling, or misaligned elements.

Because this framework uses multipliers mathematically synced to --z-global-font-size, your entire UI remains perfectly proportional. This setup allows you to easily implement a custom “Text Size” toggle directly in your application. When a user increases the text size, you only need to update that single base variable, and the rest seamlessly follows:

  • Buttons scale flawlessly: Interactive elements grow proportionally without the text feeling cramped.
  • Spacing remains balanced: Padding and margins adjust automatically based on their multipliers.
  • Layouts stay intact: You avoid the structural chaos and overlapping elements that manual browser zooming sometimes triggers.

Readability is just the beginning; there are countless other use cases where dynamic scaling shines. Build on top of 0build’s API, and the framework will work for you, rather than you fighting against it.

Radii

You can adjust the roundness of your components globally using the following tokens:

VariableDefault ValueRole
--z-global-radius0.375remThe primary global radius used for standard components.
--z-global-radius-small0.125remA tighter radius intended for inner, smaller or compact elements.

Shadows

Depth and elevation are standardized using global shadow tokens:

VariableDefault ValueRole
--z-global-shadow0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1)The default, layered shadow for standard elevation.
--z-global-shadow-small0 1px 2px 0 rgb(0 0 0 / 0.05)A subtler shadow intended for flatter elevations.

Interactives

These multiplier variables act as a base scale shared by interactive components like buttons, forms, and pagination:

VariableDefault ValueRole
--z-interactive-height-multiplier2.5Scales the overall height of an interactive element.
--z-interactive-padding-block-multiplier0.625Scales the top and bottom padding.
--z-interactive-padding-inline-multiplier1.25Scales the left and right padding.

We extensively use multipliers to control certain elements and keep them in sync, so you only need to update a few variables. However, you're free to leave them as they are and implement your own theme values.

Utilities

To make applying these structural properties as seamless as possible, the framework provides utility classes that map directly to your global variables.

Layout & Typography

You can apply this globally to the <html> tag to instantly control layout density of your app.

ClassCSS Variables AdjustedEffect
.z-layout-small--z-global-font-size, --z-global-line-heightScales the layout down to a compact size (base 0.875rem).
.z-layout-medium--z-global-font-size, --z-global-line-heightScales the layout up for a more spacious, readable UI (base 1.125rem).

Border Radius

Use these classes to quickly apply your predefined corner tokens to elements like cards, dialogs, or custom containers.

ClassMaps ToEffect
.z-rounded--z-global-radiusApplies the standard default corner radius.
.z-rounded-small--z-global-radius-smallApplies the tighter, compact corner radius.

Box Shadows

These classes apply your global elevation tokens to create consistent depth across your application.

ClassMaps ToEffect
.z-shadow--z-global-shadowApplies the standard layered shadow for primary elevation.
.z-shadow-small--z-global-shadow-smallApplies a subtle, minimal shadow—ideal for flatter UI elements.

LSH Integration

The true power of this multiplier and variable system unlocks when you give control directly to your users. Please see the LSH component documentation for step-by-step instructions on how to build interactive, state-synced preference toggles—like layout density switchers, text scalers, and custom theme selectors—that automatically save to local storage and update your UI instantly.