Skip to content

Type to search the docs, API, and blog.

↑↓ navigate↵ openesc close
Zazz 0.7 alpha

Zazz 0.5 alpha

Zazz 0.5 replaces utility classes with style utilities: inline custom properties that keep the cascade and cascade layers intact.

Zazz 0.5 alpha is a huge milestone that redefines the future of the Zazz design system and primitives library.

Introduction of style utilities

This release removes the Tailwind-like class utility system in favor of an inline CSS custom property syntax we call style utilities. This rework opens up huge wins for performance, file size, and flexibility. It also sets Zazz up to take full advantage of the new if(), style(), and @function CSS features as soon as they're widely available, which will greatly reduce file size and add a huge number of style utilities that would otherwise bloat the system.

0.4.x syntax

<section class="py-xl text-center">
  <div class="container">
    <hgroup class="flex flex-col gap-sm">
      <span class="text-eyebrow">Utilities</span>
      <h1 class="text-h1" tabindex="-1">Responsive utilities</h1>
      <p class="text-lg text-muted-foreground @md:max-w-screen-sm">
        Every utility below carries an <code>@</code> breakpoint prefix. They are
        mobile-first, gated on the global breakpoint flags from the body container, so they
        react to the viewport. Resize to watch them move.
      </p>
      <a href="#" class="ui-button mt-md" data-variant="primary">Get started</a>
    </hgroup>
  </div>
</section>

0.5.0 syntax

<section
  style="
    --py: 24;
    --text-align: center;
  "
>
  <div data-ui="layout">
    <hgroup
      style="
        --display: flex-col;
        --gap: 6;
        --text-align: center;
      "
    >
      <span data-ui="text-eyebrow">Utilities</span>
      <h1 data-ui="text-h1" tabindex="-1">Responsive utilities</h1>
      <p
        data-ui="text-lg"
        style="
          --max-w: 100%;
          --max-w--md: 60ch;
          --text-wrap: balance;
          --text: var(--color-muted-foreground);
        "
      >
        Every utility below carries an <code>@</code> breakpoint prefix. They are
        mobile-first, gated on the global breakpoint flags from the body container, so they
        react to the viewport. Resize to watch them move.
      </p>
      <a
        href="#"
        data-ui="button"
        data-button-variant="primary"
        style="--mt: 24"
      >Get started</a>
    </hgroup>
  </div>
</section>

Why not normal inline styles?

You have likely never seen a syntax quite like this, and your first question might be, "why not use normal inline styles?"

The essential idea is that inline styles break the CSS cascade: they win over every selector, and they ignore cascade layers. If you've ever had to deal with inline styles that you're unable to remove from the markup, you know how much suffering they can cause.

Style utilities, on the other hand, preserve the cascade and correctly use Zazz's cascade layers by using the style attribute and the variable name inside it as the selector itself.

The simplest version

The simplest expression of this idea looks like this:

@property --p { syntax: "*"; inherits: false; }

@layer ui.utilities {
  :where([style*="--p:"]) {
    padding: var(--p);
  }
}

The trick is to keep style utilities from inheriting values from parent elements (that's the inherits: false in the @property registration). Once you do that, each element's styles become scoped to itself. The result is a syntax that feels and reads more like StyleX but uses familiar, Tailwind-like shorthand naming conventions that many developers are already used to.

Design system values and arbitrary values together

Zazz style utilities take this idea even further by allowing design system values and arbitrary values to live together. All the following values are valid:

<div
  style="
    --p: 24; /* Spacing scale: equivalent to `calc(24 * var(--spacing, 0.25rem))` */
    --p: 6rem; /* Fixed value: can be any regular length value */
    --p: var(--space-md); /* Variable: can be any CSS variable of the same value type */
  "
></div>

Zazz uses CSS property types and type invalidation to switch between arbitrary fixed values and spacing scale values without functions or if statements.

The real utility

The real utility looks closer to this:

@property --p { syntax: "*"; inherits: false; }
@property --_p { syntax: "*"; inherits: false; }
@property --_p-resolved { syntax: "*"; inherits: false; }
@property --_p-len { syntax: "<length-percentage>"; inherits: false; initial-value: 0px; }
@property --_p-num { syntax: "<number>"; inherits: false; initial-value: 0; }

@layer ui.utilities {
  :where([style*="--p:"], [style*="--p--"]) {
    --_p-resolved: var(--_p-2xl, var(--_p-xl, var(--_p-lg, var(--_p-md, var(--_p-sm, var(--_p))))));
  }
  :where([style*="--p:"]) {
    --_p: var(--p);
    --_p-len: var(--_p-resolved);
    --_p-num: var(--_p-resolved);
    padding: calc(var(--_p-len) + var(--_p-num) * var(--spacing));
  }
}

The --_p-sm through --_p-2xl values come from breakpoint tiers like --p--md: 12. Much of this code will be simplified once if() CSS conditionals become widely available, but this solution brings type-driven conditional values to Zazz today.

How this affects legacy system migration

There are two core target audiences for Zazz UI. First, Zazz is a no-build, drop-in solution that any technology can use without adopting new infrastructure. But Zazz is also built specifically for legacy applications, which drives the core decision to stay out of the class attribute.

Many legacy applications are stuck with decades of fragile technical debt and rely heavily on classes as their primary target for styles and JavaScript selectors. On top of that, their backend language of choice often manipulates classes or depends on specific classes in ways that are difficult to track down or too fragile to just delete.

Zazz stays out of your classes and can live beside them without worrying about name conflicts or unforeseen downstream bugs. Style utilities themselves never conflict with other JavaScript libraries that use the inline style attribute for things like animations or state changes. When JavaScript is turned on, your style utilities can be automatically preserved, even if a library tries to clear the style attribute completely, with the style guard. The migration guide covers bringing Zazz into an existing app.

Other notable features

The changelog lists every breaking change with a one-line migration.