Zazz Design Framework
Core concepts

Hover, focus, and other states

Style hover, focus, validation, and disabled states straight from your markup, with prefixed utilities for hover and sensible defaults that handle the rest.

Interactive states in Zazz come from two places: hover: utilities you add yourself, and defaults the framework already handles: focus rings, validation feedback, and disabled styling. Most of the time you write less than you'd expect.

Hover

Prefix a utility with hover: and it applies while the pointer is over the element. The prefix works on color, opacity, and scale:

<a class="button hover:bg-primary hover:scale-105">Hover me</a>

<div class="card hover:opacity-75 transition">…</div>

In your HTML you write hover:bg-primary, with no backslash. The backslash you'll see in the source (.hover\:bg-primary) is only CSS escaping the colon in the selector; the class attribute uses a plain colon.

What hover: covers:

  • Text color: hover:text-primary and every other theme role.
  • Background: hover:bg-primary, plus hover:bg-transparent and hover:bg-none.
  • Border: hover:border-primary, hover:border-destructive, and the other roles.
  • Opacity: hover:opacity-{0,25,50,75,100}.
  • Scale: hover:scale-{50,75,90,95,100,105,110,125,150}.

Pair them with transition so the change eases instead of snapping. Motion respects prefers-reduced-motion automatically, so you don't have to guard it.

Focus

You rarely write focus styles. The reset applies a :focus-visible ring to every focusable element, so keyboard users get a clear, consistent indicator everywhere. And :focus-visible means it shows for keyboard navigation without firing on every mouse click.

/* Already handled globally — shown for reference, not something you write */
:where(:focus-visible) {
  outline: var(--outline-width) var(--outline-style) var(--ring);
}

To recolor the ring in a given context, override --ring-color (not --ring, which recomputes from it):

<form style="--ring-color: var(--destructive)">…</form>

Add the ring utility when you want a persistent ring on an element regardless of focus. The ring renders as a box-shadow from Tailwind/shadcn-compatible tokens, and a matching transparent outline is kept so focus stays visible in forced-colors mode.

Pressed and active

There are no active: utilities. Express pressed feedback in a component's own CSS with :active. That keeps tactile, per-component motion where it belongs rather than scattering it across markup. The built-in components already do this.

Validation states

Form fields validate on :user-invalid, which only matches after the user has interacted, on blur or submit, never while they're still typing. That timing is the difference between helpful and nagging, and you get it for free; no was-touched class to toggle.

<div class="field">
  <label for="email">Email</label>
  <input id="email" type="email" required />
  <p class="field__error">Enter a valid email address.</p>
</div>

The shared field family crossfades label, hint, and error for you. :user-invalid is Baseline and widely available. See Input for the full form markup.

Disabled

Components style their disabled state through :not(:disabled) guards rather than order-dependent overrides, so a disabled attribute is all you add:

<button class="button" data-variant="primary" disabled>Saving…</button>

Component states via data attributes

Open/closed, selected, and side/alignment states are driven by data-* attributes that the framework and native APIs toggle: data-state, data-side, and friends. You set the variant; the component handles the transitions. See any component page for its attribute API.

On this page