Zazz Design Framework

Changelog

Release history for @zazz-ui/core — scoped per primitive, breaking changes flagged. Single source is the kit's CHANGELOG.md, embedded below.

Notable changes to @zazz-ui/core, grouped by primitive or base scope under each version. The grouping is load-bearing: the zazz-ui CLI's update and diff print only the slice that touches the files you've vendored. Breaking entries are flagged BREAKING with a one-line migration note. During 0.x, a minor bump means at least one breaking entry (ADR-0010 has the full definition of "breaking").

0.4.1 (2026-09-04)

Housekeeping on top of 0.4.0: the vestigial anchor-size() @supports gates come out, and the anchor-positioning support notes in the CSS headers and ADR-0011 are corrected. No rendered output changes in any browser — see the reasoning on the gate entry below.

base

  • The four @supports (inline-size: anchor-size(width)) gates are removed (autocomplete panel, combobox panel, select ::picker(select), multiselect panel); min-inline-size: anchor-size(width) now applies unconditionally. Not a browser-floor raise, and not breaking: in a browser without anchor-size() the function is unknown, so the declaration is dropped at parse time and the inline-size: max-content above it still stands — exactly what the gate produced. anchor-size() is also Baseline 2026 (Chrome 125, Firefox 147, Safari 26.0), inside the support floor either way. Migration: none.
  • The anchor-name gates in popover, tooltip, select, and tabs are deliberately kept. Unlike anchor-size(), dropping those would change rendering: full anchor-name support starts at Safari 27, and on Safari 26.x the gate is what keeps a popover UA-centered instead of an unpositioned top-left box. tabs.css also keeps its @supports not branch, which is a real designed fallback (indicator hidden, filled label instead). popover.css records when to retire them.
  • Corrected support notes in popover.css, autocomplete.css, combobox.css, and select.css: anchor positioning reached Baseline in January 2026 (Chrome 151, Firefox 147, Safari 27), so the headers no longer claim Firefox lacks it. ADR-0011's support matrix is corrected the same way, and it now records why no anchor-positioning polyfill is adopted (@oddbird/css-anchor-positioning implements no anchor-size(), no dynamic anchors, and wraps the target in a way that disturbs custom-element lifecycles).

0.4.0 (2026-09-03)

One theme: the polyfill set is corrected and cut to one. The Popover API and Invoker Commands are native across the kit's browser floor and are no longer polyfilled; interestfor — which is Chromium-only and drives every tooltip — was never actually polyfilled despite appearing to be, and now is. Net effect: tooltips and hover-open menus start working in Firefox and Safari, and the head's polyfill payload drops from ~228 KB to 15 KB. See ADR-0011.

base

  • BREAKING The head contract ships one polyfill instead of two: invokers@2.2.2/dist/esm/production/interest.js (Interest Invokers). @oddbird/popover-polyfill is removed — the Popover API is native in Chrome 114+, Firefox 125+, Safari 17+, iOS Safari 18.3+, all below the support floor — and the invokers compatible build (Invoker Commands: Chrome 135+, Firefox 144+, Safari 26.2+) is replaced by the interest build from the same package. Migration: re-run zazz-ui update (vendored) or re-render buildHead / regenerate your head.html; if you hand-maintain your head, replace both old script tags with the single interest.js tag and its sha384-hR2BVNtsS7fIIMwOm+f7MY0JZfuByGhQQfevCBB6evFATKzBsTw0JzH/RxD94z2T hash.
  • BREAKING Browser floor raised to match: the Popover API and Invoker Commands are now assumed native rather than polyfilled. Browsers below the floor no longer get popover behavior at all (a closed [popover] keeps the UA's display: none) instead of degrading through the polyfill. Migration: none if you were already on the documented floor (latest Chrome/Firefox/Safari, two versions back).
  • BREAKING The .\:popover-open class hack is gone from every selector and matches() guard — 18 sites across popover, tooltip, menu, navigation-menu, command, select, combobox, autocomplete, toaster, and base/typeahead. Selectors are plain :popover-open now. command.css keeps :where(:popover-open, [open]); that branch is the dialog form of the panel, not the polyfill. Migration: if your own CSS matched .\:popover-open to hook Zazz popovers, switch to :popover-open.

tooltip

  • Fix: tooltips now work in Firefox and Safari. The trigger is interestfor, and the previously loaded polyfill (invokers/compatible) contained no interestfor implementation at all, so tooltips were silently Chromium-only. The invokers/interest build supplies it.
  • Header comment corrected: the Popover API is native across the floor, and interestfor's implicit anchors still are not polyfilled (explicit anchor-name is why the styling holds).
  • Fix: the optional hover/focus-open path on menu, menubar, and navigation-menu triggers (interestfor alongside popovertarget) now works outside Chromium, for the same reason as tooltip.

0.3.0 (2026-09-02)

Three themes: (1) theming is single-source — every theme role is declared once as a light-dark() pair, and .dark/.light/[data-theme] are pure color-scheme pins that re-resolve the same tokens (the inverted-popover feature is removed); (2) utility classes go fully logical — every physical-side declaration becomes its logical equivalent, every side-named class gains a logical-name alias, and border widths and dividers arrive; (3) the cascade layer contract is reworked around vendors, a grouped legacy, zazz.plugins, and overrides. In LTR horizontal writing with system theming, default rendering is unchanged except where flagged below.

base

  • Theme roles are now declared exactly once as light-dark() pairs on :root; .dark/.light and [data-theme="dark"|"light"] are pure color-scheme pins that also re-assert color. Scopes nest — a .light island inside a .dark section re-lightens its subtree. Override a role once (--primary: light-dark(…, …)) and every mode picks it up. Visible change: the manual/toggled dark palette had drifted from the system-dark arms and is now reconciled to them — --border (tint-100), --popover (neutral-950), --primary/--secondary (800), --tertiary (800) replace the stale tint-200/shade-950/500/400 copies, so toggled dark now matches system dark exactly.
  • BREAKING The inverted-popover feature is removed: the --use-inverted-popovers property and the data-use-inverted-menu attribute no longer exist. Popovers follow the page's color scheme. Migration: delete both from your markup/CSS; to force a dark popover, set color-scheme: dark on it (tokens re-resolve automatically).
  • New text-decoration utilities: underline, overline, line-through, no-underline.
  • New border width utilities: border-1 to border-4 (all sides), per-side border-{t|b}-{1-4}, border-{l|s}-{1-4}, border-{r|e}-{1-4}. Compose with border color classes. Not responsive.
  • New divider utilities: divide-x / divide-y draw a 1px border on the trailing logical edge of every direct child except the last. Children inherit --_border-color, so border color classes on the container recolor the dividers.
  • New logical-name aliases (identical values to their physical-name twins): inline-*/block-* for w-*/h-* (plus min-/max- variants), start-*/end-* for left-*/right-*, ps-*/pe-* for pl-*/pr-*, ms-*/me-* for ml-*/mr-* (negative margins included), border-s/border-e for border-l/border-r, rounded-s/e/ss/se/ee/es-* for rounded-l/r/tl/tr/br/bl-*, and text-start/text-end for text-left/text-right (responsive variants included).
  • Positioning, border, radius, and text-align utilities converted from physical to logical properties/values: left:inset-inline-start:, border-right:border-inline-end:, physical corner radii → border-start-start-radius etc., text-align: left|rightstart|end. Rendering only changes in RTL/vertical writing modes, where utilities now mirror with the text direction.
  • BREAKING Internal radius composition variables renamed to logical corners: --_radius-top-left|top-right|bottom-right|bottom-left--_radius-start-start|start-end|end-end|end-start. Migration: rename in any custom CSS that reads or sets the --_radius-* vars (they are internal, so most consumers are unaffected).
  • Fix: z-isolate declared invalid z-index: isolate (a no-op); now isolation: isolate as documented.
  • Fix: --ui-prose-figcaption-gap was consumed by .ui-prose figure but never declared; now declared (0px, preserving current spacing) so the hook works.
  • Fix: the global-view-transition--old keyframes read --view-transition-opacity--new; they now read --view-transition-opacity--old, making that documented hook functional (defaults are identical, so default rendering is unchanged).
  • Fix: article containers' data-container="full"|"bleed" referenced undeclared --article-full/--article-bleed tokens (the rules were invalid and fell back to auto width); they now implement band-container semantics — full spans the available width keeping gutters, bleed runs edge to edge.
  • BREAKING Cascade layer contract reworked: top-level order is now variables, reset, vendors, legacy, zazz, overrides. New vendors layer for third-party CSS that does not build on Zazz (above reset so the reset can't clobber library widgets, below legacy). The migrations top-level layer moved to a legacy.migrations sublayer — legacy now nests imports, components, utilities, migrations, so deleting a finished migration is dropping one layer; shims still beat all other legacy CSS but no longer beat Zazz (use overrides for that). New zazz.plugins sublayer between components and utilities for Zazz-dependent extensions (added variants can restyle the primitives they extend; utilities still win). New overrides top-level layer as the structured app-override slot — only unlayered CSS outranks it. Migration: import whole legacy files with layer(legacy.imports) (never bare layer(legacy) — un-sublayered rules form an implicit final sublayer that beats the shims), move migrations.css to layer(legacy.migrations), and move any shim that had to override Zazz into @layer overrides.

button

  • Buttons paint with background-clip: padding-box, and filled variants (primary, secondary, muted, …) now default their border color to the variant background (--ui-button-border-color: var(--ui-button-background) and the --hover/--active pairs) instead of transparent. Same rendered look with cleaner edges on translucent backgrounds; if you overrode a variant background, its border now follows automatically.

badge

  • Same treatment as buttons: background-clip: padding-box, and variant border colors default to the variant background instead of transparent.
  • Example markup: dropped a redundant font-heading class from the example heading (text-h5 already applies the heading weight).

tooltip

  • Example markup no longer carries data-use-inverted-menu="false" — the attribute was removed with the inverted-popover feature (see base).

0.2.0 (2026-08-31)

Token naming consistency pass + shared field-family inheritance. Two themes: (1) every token is now named after the logical CSS property it feeds (-block-size, never -height), and interactive controls decompose borders into -border-width / -border-style / -border-color parts; (2) buttons, toggles, tabs, checkboxes, radios, and badge borders now default to the shared --ui-field-* family, so one --ui-field-radius or --ui-field-block-size override retunes every control that sits on a line together. Default rendering is unchanged — the new aliases resolve to the same values.

base

  • BREAKING fields.css now registers first among primitives in index.css (it owns the shared --ui-field-* family); CSS_CASCADE_ORDER and the button/badge/tabs/checkbox manifest entries gained a fields dependency. Migration: re-run zazz-ui update (vendored) or re-emit your import order from the manifest; hand-maintained subsets must load fields.css before its consumers.
  • CONVENTIONS.styles.md §5/§6 now document the token-naming rules: logical property names, full property names (no abbreviations), the border width/style/color decomposition (composed at the usage site, never into a :root token), -foreground vs -{part}-color, and sanctioned cross-component token defaults.
  • Physical top/left/width/height declarations converted to logical equivalents in dialog, lightbox, mobile-menu, tooltip, carousel, and badge; remaining physical uses are commented exceptions (anchor() side keywords, centering idioms).
  • BREAKING group-hover:scale-* now matches the regular scale-* set (0, 25, 50, 75, 90, 98, 99, 100, 101, 102, 110, 125, 150). Dropped 95/105. Migration: group-hover:scale-95group-hover:scale-98 (or 90); group-hover:scale-105group-hover:scale-102 (or 110).

fields

  • BREAKING --ui-field-height--ui-field-block-size. Migration: rename the token in your overrides.
  • BREAKING --ui-field-border (color-valued) → --ui-field-border-color; --ui-field-border--hover/--focus--ui-field-border-color--hover/--focus. New --ui-field-border-width (1px) and --ui-field-border-style (solid) parts. Migration: rename color overrides; width/style are now their own hooks.

input

  • BREAKING --ui-input-border-radius--ui-input-radius; --ui-input-border (shorthand) replaced by --ui-input-border-width/-style/-color aliases of the field parts. Migration: rename, or override the --ui-field-border-* parts to move the whole family.
  • --ui-input-calendar-picker-radius now defaults to --ui-field-inset-child-radius (same value).

textarea

  • BREAKING --ui-textarea-border-radius--ui-textarea-radius; --ui-textarea-border replaced by --ui-textarea-border-width/-style/-color. --ui-textarea-font-size now defaults to --ui-field-font-size (same value).

select

  • BREAKING --ui-select-border-radius--ui-select-radius; --ui-select-border replaced by --ui-select-border-width/-style/-color; --ui-option-height--ui-option-block-size; --ui-option-border(--hover/--active) replaced by --ui-option-border-width/-style/-color (+ -color--hover/--active).

otp

  • BREAKING --ui-otp-slot-border replaced by --ui-otp-slot-border-width/-style/-color.

combobox

  • BREAKING --ui-combobox-border-radius--ui-combobox-radius; --ui-combobox-border replaced by --ui-combobox-border-width/-style/-color (aliasing the select parts); --ui-combobox-control-align--ui-combobox-control-align-items; --ui-combobox-control-wrap--ui-combobox-control-flex-wrap; removed the dead --ui-combobox-display token (nothing consumed it).

input-group

  • BREAKING --ui-input-group-text-size/-text-weight/-text-color--ui-input-group-font-size/-font-weight/-foreground; --ui-input-group-align-align-items; --ui-input-group-wrap-flex-wrap.

password-group

  • BREAKING same renames as input-group (-text-*-font-*/-foreground, -align-align-items, -wrap-flex-wrap).

button

  • Button metrics now default to the shared field family: --ui-button-block-size/-font-size/-line-height/-padding/-radius/-icon-size/-ring-color alias --ui-field-*, and --ui-button-border-width/-style alias the field border parts (same values as before; retune --ui-field-* and buttons follow, remap --ui-button-* to diverge).
  • BREAKING --ui-button-height--ui-button-block-size; --ui-button-border(--hover/--active) (shorthands) replaced by --ui-button-border-color(--hover/--active) color parts. Migration: rename; to restyle the whole border override the width/style/color parts.

toggle

  • BREAKING --ui-toggle-height--ui-toggle-block-size; --ui-toggle-border* shorthands replaced by --ui-toggle-border-width/-style/-color (+ -color--hover/--active/--checked/--checked-hover/--checked-active), defaulting to the button parts.

badge

  • BREAKING --ui-badge-height--ui-badge-block-size; --ui-badge-border(--hover/--active) replaced by --ui-badge-border-width/-style/-color parts. Border + ring now default to --ui-field-* (same values); metrics stay an independent smaller scale.

tabs

  • BREAKING --ui-tabs-label-min-height--ui-tabs-label-min-block-size.
  • Track/label metrics now default to the field family: --ui-tabs-track-radius (--ui-field-radius), --ui-tabs-indicator-radius (--ui-field-inset-child-radius), label font-size/line-height/padding/ring-color (same values as before).

checkbox

  • BREAKING --ui-checkbox-border(--hover/--checked)--ui-checkbox-border-color(--hover/--checked), plus new width/style parts. Tokens moved from the element onto :root in @layer variables (they are now overridable from :root like every other hook); surface + border default to --ui-field-*.

radio

  • BREAKING --ui-radio-border(--hover/--checked)--ui-radio-border-color(--hover/--checked), plus new width/style parts; surface + border default to --ui-field-*.

switch

  • BREAKING --ui-switch-track-width/-track-height--ui-switch-track-inline-size/-track-block-size; --ui-switch-thumb--ui-switch-thumb-background.

dialog

  • BREAKING --ui-dialog-width/-height--ui-dialog-inline-size/-block-size; removed the dead --ui-dialog-display token (nothing consumed it).

alert-dialog

  • BREAKING --ui-alert-dialog-width--ui-alert-dialog-inline-size.

progress

  • BREAKING --ui-progress-height--ui-progress-block-size.

meter

  • BREAKING --ui-meter-height--ui-meter-block-size.

slider

  • BREAKING --ui-slider-track-height--ui-slider-track-block-size.

table

  • BREAKING --ui-table-head-height--ui-table-head-block-size.

toaster

  • BREAKING --ui-toaster-width--ui-toaster-inline-size; --ui-toaster-description-color--ui-toaster-description-foreground; --ui-toaster-border--ui-toaster-border-color (it held a color).
  • BREAKING --ui-carousel-slide-min-width--ui-carousel-slide-min-inline-size.

kbd

  • BREAKING --ui-kbd-color--ui-kbd-foreground.

mobile-menu

  • BREAKING --ui-mobile-menu-backdrop--ui-mobile-menu-backdrop-color.

accordion

  • BREAKING --ui-accordion-icon-transform-open--ui-accordion-icon-transform--open (double-dash state convention).

popover

  • BREAKING --ui-popover-border--ui-popover-border-color (it held a color).

tooltip

  • BREAKING --ui-tooltip-border--ui-tooltip-border-color (it held a color).
  • BREAKING Removed dead tokens nothing consumed: --ui-lightbox-thumb-border(--active) (the thumb border comes from --ui-lightbox-img-border; active/focus feedback is the inset ring + opacity), --ui-lightbox-radius (slides/thumbs use --ui-lightbox-slide-radius / --ui-lightbox-thumb-radius), and --ui-lightbox-thumb-aspect-ratio. Migration: delete overrides of these; retune the named replacements instead.

0.1.0 (2026-08-28)

First public release. One package, consumed three ways: the dist/zazz.css + dist/zazz.js bundles for a two-tag CDN drop-in, per-file CDN URLs into the readable src/ tree, or copying the code into your project and owning it.

base

  • Cascade-layer architecture: _layers, _variables, _reset, _typography, _view-transitions, then primitives, then _utilities and _layout. A layer(legacy) slot lets a migrating stylesheet ride below the kit.
  • Core runtime (utils, signals, zazz-element, dialog-lifecycle), opt-in page behaviors (reveal, navigation), and the shared engines behind the typeahead family (typeahead, command-score, hotkeys) and the Embla carousel adapter.
  • head.ts: the canonical <head> contract (fonts, stylesheet, pinned and SRI-checked import map, polyfills, theme persistence), in a local mode for vendored copies and a CDN mode (cdn: { version, primitives?, sri? }).
  • manifest.ts: the distribution manifest. PRIMITIVES (files, dependencies, and examples for every primitive), CSS_CASCADE_ORDER, resolveClosure(), and MANIFEST_VERSION.
  • dist/sri.json: sha384 hashes of every published css/js file, regenerated each release.

primitives

  • 43 primitives: accordion, alert-dialog, autocomplete, avatar, badge, breadcrumbs, button, button-group, card, carousel, checkbox, combobox, command, dialog, fields, input, input-group, kbd, lightbox, menu, menubar, meter, mobile-menu, navigation-menu, otp, password-group, popover, progress, prose, radio, reveal, select, separator, slider, switch, table, tabs, textarea, toaster, toggle, toggle-group, toolbar, tooltip, utilities.

On this page