Changelog
Release notes for the @getvitops/* packages — what changed, what broke, and how to migrate.
On this page
Release notes for the @getvitops/* packages — what changed, what broke, and how to migrate.
@getvitops/core, generator, utils, cli, vite and astro share one version: they are
released together and are only supported in matching versions (the generator embeds a snapshot of
core’s CSS and web-component bundles, and the Astro integration copies the installed core’s
bundles into your public/ — mixing versions can leave the CSS and the components disagreeing).
@getvitops/emdash and @getvitops/create version independently.
Per-package detail — including every release before 0.7.0 — ships with each package:
node_modules/@getvitops/<pkg>/CHANGELOG.md.
2.0.0 — 2026-08-03
Also: @getvitops/emdash 0.3.0, @getvitops/create 0.4.0.
Three things that were quietly not working now work, and each one needed a breaking change to fix. Utilities never actually beat patterns; the framework assumed a border-box reset it didn’t ship; and the site config had nowhere to put a system-wide fact. Alongside them: an icon system with one semantic vocabulary across icon sets, webfont loading through Astro’s Fonts API, analytics with a consent gate, and every config-taking surface now accepting the larger site config that embeds a design system.
If you are upgrading a real site, read Breaking in full — two of the three change how existing markup renders, and neither announces itself.
Breaking
-
Cascade layers are assigned by what a rule is, not which file it lives in. The three-layer order (
vitops.base → components → utilities) has existed for a while and its entire purpose is to letclass="card bg-danger-muted"work without!important. But layers were chosen per partial, andlayout.cssheld both structural patterns and roughly three quarters of the framework’s utilities — so.m-*,.flex-*,.items-*,.justify-*,.text-*and the split ratios were shelved below the patterns they exist to override and silently did nothing.utilities.csshad the mirror-image problem, holding the.revealcomponent family.Both are split, and a shared
LAYER_CONTRACTasserts the classification across formats so the halves cannot drift. Migration: combinations likeclass="table text-center",class="banner items-start"andclass="media items-center"now take effect. If you wrote one, never noticed it did nothing, and preferred the old result — remove the utility. To keep a pattern winning, write the rule in your own stylesheet: unlayered CSS still beats all three layers.Removed: the bare
<bp>-splitclasses (sm-split…xl-split).@utilitycannot live in a cascade layer, so@md:splitwas impossible and the css/bricks counterpart went with it. Usemd-flex-row, which says the same thing in every format. -
The framework ships a border-box reset. In
vitops.base, so unlayered consumer CSS still wins, and the opt-out is one rule:*, *::before, *::after { box-sizing: content-box }. Thetailwindformat is unaffected — preflight already did this. The reason it stopped being optional:.split’s ratio is a flex basis, which sizes the border box, so under content-box a padded column came out wider than its sibling by exactly its padding. Stating the assumption once beats every pattern re-asserting it and the ones that forget being quietly wrong. -
designSystemin a site config is now an object. The map moves underthemes, anddefaultTheme+defaultColorSchememove inside it;respectSystemPreferenceis gone, sincedefaultColorScheme: "system"says the same thing and the incoherent combination is no longer expressible. Migration is automatic at runtime — all three spellings are accepted — butsite.schema.jsonis published to a stable URL, so an editor pinned to$schemawill flag the old shape.This is what made
<color-scheme-toggle>’s System position work. It had always shipped three segments and one did nothing: it removes the theme attribute, and with noprefers-color-schemeblock in the emitted CSS the page fell through to light on every machine. Opt in withdefaultColorScheme: "system"(+303 B gzipped); it is opt-in because switching it on visibly flips an existing site dark for dark-OS visitors. -
@getvitops/emdashnow depends on the toolchain. Its blocks render from the generated SVG sprite, so against an older toolchainvitops.actionLinkrendered an empty box — no error, nothing to grep. It hard-depends on@getvitops/utils(the editor’s icon list is now derived fromiconMap, not copied) and peers on@getvitops/astro>=2.0.0. An unmet-peer warning on upgrade is this change working.
Added
- An icon system. Icons are named by meaning and resolved per configured set —
menubecomesfa7-solid:bars,lucide:menuorph:list— so swapping sets is a config edit. A name containing:passes through untouched. Three delivery paths:astro-icon,astro-iconset, and a build-time SVG sprite (icons.sprite) for Bricks, EmDash and plain HTML —<use href>, no JS, no icon-API call. Theincludemap that keeps an SSR bundle from shipping an entire icon set is derived by scanning your source, not hand-maintained. getvitops({ fonts })loads webfonts through Astro’s Fonts API instead of hand-rolled tags, andfontsindesign-system.jsonis now documented as what it always was: stacks only, loading nothing.<Analytics />and a general-purpose consent gate. GA4, Clarity, Matomo and Plausible, every tag off the critical path (strategydefaults to'idle') and, when it sets cookies, only after consent. The gate is a sibling of analytics, not part of it — anything markeddata-consent="<category>"waits on the same choice.- Any config-taking surface accepts a site config.
generate(), the Vite plugin, the Astro integration and every CLI command take adesign-system.jsonor the largercompany.jsonthat embeds one, told apart by shape rather than filename. A site config also supplies the site-level facts — colour scheme, legal documents, icon sprite, fonts — so the path is declared once. Newtheme/siteEnvoptions select a theme and apply an A/B variant. vitops lintcatches reinvention, not just typos: hand-written CSS that re-implements a framework primitive. Findings gained a severity, so an advisory rule can’t break your CI on first run.gap-*utilities over the fluid space scale,text-{wrap,nowrap,balance,pretty},.splitstacking +.split-reverse, andtext-wrapas a stated decision on every type role.
Fixed
- A filled CTA’s text turned dark on hover.
:where()zeroes the element, but a pseudo appended outside it does not — so:where(a, .link):hoverwas 0-1-0, tied with.cta-<role>, and won on source order. Every<a class="cta">flipped to the link colour mid-hover. - JS and scroll-timeline detection moved into CSS, so no-JS visitors stop getting invisible content, and animation families that never animated now do.
validate()rejects what the published JSON Schema already rejected — it was lenient where the schema was strict, so a config could passvitops validateand fail in an editor.vitops validatealso routes on the file’s shape: pointed at a site config it used to report oneunrecognized_keysand nothing about the file’s real contents.- A chroma-0 seed produces an actual neutral instead of a pink one, and non-monotonic ramps are now rejected rather than silently producing a scale that doesn’t darken.
- Maskable favicons are composited opaque, so they stop rendering as a logo in a black box.
- Three tailwind-only divergences:
container-name: bodywas missing (so the TOC patterns were stuck narrow),grid-autolost its list-margin reset, and.stickywas being deleted outright because its name collides with a Tailwind utility. @getvitops/createtemplates tracklatest. They pinned@getvitops/astro: ^0.7.0and scaffolded projects a full major behind through the whole 1.0 release;vite/vite-pluswerecatalog:, which cannot resolve outside this monorepo at all, so the scaffold succeeded and the first install in it failed.
1.0.0 — 2026-08-02
The colour system is rebuilt. Every colour token and utility class is renamed; this is the
breaking change the 1.0 is for. Alongside it: a fourth output format that emits an agent-facing
DESIGN.md, generated legal documents, and a base-typography binding that also fixes the theme
editor’s dead Body controls.
Breaking
-
Colour moves to a target-prefixed grammar over one shared lightness ladder.
--color-<target>-<role>[-<variant>], target ∈bgtexticonborder, and the class name is the token name minus--color-.What it replaces: two axes shared one namespace — functional planes (
--<role>-bg-muted) and appearance-relative stops (--color-<role>-muted) — arbitrated by a “plane wins” rule. The result wasn’t a scale. On the shipped palettebg-ui-accent-x-mutedandbg-ui-accent-mutedboth resolved to step 100, barebg-ui-accentwas lighter than both, and--color-<role>-mutedwas unreachable through anybg-class. With the target inside the token name there is nothing left to arbitrate.Ramps now sit on a fixed lightness ladder (50 → L 0.98 … 950 → L 0.21); only chroma and hue vary, so a step means the same lightness in every hue. Authored colours — a
seed, ananchorsentry, atonesvalue — are still pinned verbatim at their nearest step, the only steps allowed off the ladder, and warn past ~0.03 L.Migration: the full before/after table is in
node_modules/@getvitops/generator/CHANGELOG.md. The two that catch people:bg-<role>on a chromatic role becomesbg-<role>-x-muted(a chromatic role has no bare background — say how loud you mean), and thesurfacenames rotate value-preservingly — the page is nowbg-surface-muted, the card isbg-surface.vitops lintreports role classes that no longer resolve, with suggestions derived from what the generator actually emits. -
Contrast is enforced at build time, not only in tests. Text ≥ APCA Lc 75 on its primary background, ≥ 60 on secondary planes, icons and surface boundaries ≥ 45, in both appearances. A violation now throws out of
generate— a palette that used to build and read badly now fails loudly.text-<role>-x-muted(placeholder) and-xx-muted(disabled) are exempt. -
Two
tonesclaiming the same step is now an error rather than one silently overwriting the other. Use the record form (tones: { "600": "…", "700": "…" }) to resolve it.
Added
-
--format design— a fourth format emitting oneDESIGN.mdand no CSS: the brief in google-labs-code/design.md format, YAML token front matter plus a prose body. It’s what you hand a coding agent or a Figma import that has never heard of the toolchain;vitops docsstays the richer reference for those who have it. Run it with--out .— the file conventionally sits at a repo root besideAGENTS.md.Role tokens are emitted as
{colors.<hue>-<step>}references rather than flattened hexes, so the role → ramp lineage survives the export; flattening them is exactly what breaks dark mode downstream.StylesheetFormat(Exclude<Format, 'design'>) is new on the public API, and is what@getvitops/astro’scss.formatandvitops lint --formatnow take — passingdesignwhere a stylesheet is expected is a type error rather than a missing-file build failure. -
vitops legal— privacy policy, terms of service and cookie notice rendered from a site config, as markdown, an HTML fragment or EmDash Portable Text. The documents are derived: the analytics vendor they name is the one whose ID you set, the personal information they list is what your forms collect. A provider swap updates the policy on the next build. Delivered per stack — the CLI anywhere,dist/legal/*.htmlplus a[vitops_legal]shortcode on Bricks, a content collection on Astro. It is not legal advice, and it is only as true as your config; every document opens with a review banner saying so. -
typography.headingscan bind base page typography to a type role — map"body"to your prose role and the generator emitsbody { font-family: var(--body-ff, …); … }, so prose inherits the role instead of a hand-writtenbody { line-height: … }block each consumer had to author. If you keep such a block, drop the properties the role now owns: restating them shadows the role’s tokens, and unlayered CSS wins. -
icon-<role>— a non-text colour tier, so a glyph can run more vivid than text.iconjoinsbg/text/borderas a default utility family. Plus--color-border-focus, the focus-ring tone. -
A pattern’s fill can be undone.
background/background-colorjoinBASE_HOOK, so.cardemitsbackground: var(--bg-card, …)and a flat border-only card isstyle="--bg-card: transparent; --ds-card: none"rather than an inline override. Thecssandbricksformats also gainbg-transparent/bg-inherit(Tailwind ships both itself). -
<Seo />for non-EmDash Astro sites —<title>, description, canonical, Open Graph, Twitter cards, robots,article:*,hreflang, verification tokens. Site defaults in the integration, per-page overrides as props. It owns<title>and the description meta, so remove yours when you adopt it. On an EmDash site use<EmDashHead>instead. -
An opt-in
sitemapoption on the Astro integration, registering@astrojs/sitemap(an optional peer) and linking the result from<Head />. It needs thesiteconfig option and lists prerendered routes only. On EmDash, leave it off — EmDash serves its own. -
An optional
metakey ({ name, description }) indesign-system.json, supplying the brand name and Overview paragraph toDESIGN.md. No other format reads it.
Fixed
-
Theme-editor typography edits that previewed live and vanished on save. The design manifest’s
reverseIndexonly mapped hooks a role explicitly declared, while the editor renders a control for every hook — so editing--body-lsor--body-tton a role that omitted it updated the page and then silently dropped out of thedesign-system.jsonpatch. Every hook of every role is now indexed, and pattern backgrounds (--bg-card,--bg-btn,--bg-status) are tunable in the browser alongside their geometry. -
validate()warns when a shadow value can’t survivedrop-shadow(). A--shadow-<name>token feeds bothbox-shadowandfilter: drop-shadow(…). A spread radius, a second layer orinsetinvalidates the whole filter, so.drop-shadow-<name>rendered nothing while the token still looked correct everywhere it was authored. -
The
virtual:getvitops/headtype declaration was missing theeditorfield<Head />already reads — a type error in consumer projects that don’t setskipLibCheck. -
validateSiterejects a privacy policy with no contact ordomains.canonical, both of which are interpolated into sentences that would otherwise render blank.
Docs
Every Astro example now binds the integration as vitops (import vitops from '@getvitops/astro'). The default export is unchanged — configs binding it as getvitops keep
working. The generator and CLI reference pages now document the design format, and the
scaffolded emdash template moves onto the new colour tokens.
0.9.0 — 2026-07-31
Format parity. The three outputs had quietly drifted apart — the same markup meant different
things depending on which one you built. Most of this release is closing that, plus the dead
references and unrendered components the investigation turned up. Prompted by a report from a
14-page consumer site built on the tailwind format.
One change can surprise you: the css/bricks bundle is now layered, so your own CSS
overrides the framework by default. If you ship a reset, read the migration under Changed.
Added
-
vitops lint— reports framework classes in your source that resolve to nothing. An unknown utility class is indistinguishable from a working one: nothing errors, the element just never gets the style. Format-aware (md-flex-rowis real incss/bricksand inert intailwind), and it only judges classes anchored to your own config — a palette hue, a role, a type role, a shadow — so it stays silent on Tailwind’s utilities and your own class names.vitops lint --format tailwind --src src -
validate()warns when a required role is missing.colors.rolesis an open map, but the shipped component CSS referencesbrand-primary,danger,neutral,surface,ui-primaryandwarningwith no fallback. Omitting one leaves those components uncoloured, silently. -
<wc-theme-editor>no longer dims the page it is editing, so you can judge colour changes against the real design rather than through a scrim.
Changed
-
The
css/bricksbundle now ships cascade layers. Previously every colour utility was emitted before every pattern and both sat at0-1-0, so the pattern won on source order:class="card bg-danger-muted"left the card on--surface-bghere while tinting it correctly intailwind. The bundle now emits@layer vitops.base, vitops.components, vitops.utilities;, so a utility overrides a pattern in every format. No rule changed — 1482 rules before, 1482 after — only precedence.What this changes for you: unlayered CSS beats every cascade layer regardless of specificity, so your own stylesheet, an Astro scoped
<style>, or a Bricks-authored class now overrides the framework with no!important. That is the intended override story.Migration, only if you ship a reset. An unlayered reset will now beat the framework component rules it used to lose to — a bare
p { margin: 0 }defeats.rhythm. Put it in a layer and declare the order before the stylesheet loads:<style> @layer my.reset, vitops.base, vitops.components, vitops.utilities; </style> <link rel="stylesheet" href="/styles.css" />Declaring it after the link makes
my.reseta new name introduced later — which sorts last, i.e. highest priority — and the reset wins anyway. This repo’sindex.htmlshows the change.The
tailwindformat is byte-identical: its utilities are@utilitydefinitions, which Tailwind already layers correctly and which are what makehover:/@md:variants work.Known gap:
layout.cssmixes structural rules with utilities in one partial, so it sits invitops.componentswhole and its utility half (.m-*,.flex,.split-*) still can’t override a pattern.
Fixed
- The
tailwindformat was missing 87 role colour classes.bg-<role>-x-muted,bg-<role>-bold,bg-<role>-x-bold,text-<role>-bold,text-<role>-x-boldandborder-<role>-{muted,x-muted,x-bold}existed in thecss/bricksoutputs and silently did nothing intailwind. No test built the tailwind format, so nothing caught it. All three formats now render from one emitter and a parity test holds them to the same vocabulary, permitting only four documented differences. No class changes meaning;css/bricksoutput is unchanged. colors.utilitiesis now honoured in thetailwindformat (it was hardcoded to bg/text/border). For raw hue scales it stays a floor rather than a ceiling — those are@themecolours and Tailwind derives every colour family from them on demand.- The
tailwindformat stripped component container queries. The pass that drops the framework’s pre-expandedmd-*utilities matched every@container (min-width: …)block, including component behaviour — so.sitenav--bp-{sm,md,lg,xl}were removed and the nav never left its mobile layout. vitops initandvp createscaffolded broken configs. Both still referenced--color-surface-xl/--color-surface-xxl, aliases from the named-step scale removed in 0.6, giving acardwith no background and an invalid default border. The EmDash template also carried thepatterns.radii.cardcollision thatvalidate()warns about..text-revealrendered invisible text. Its gradient read two custom properties with no defaults; when they were unset thevar()substitution failed,backgroundbecame invalid at computed-value time, and the pairedcolor: transparentleft nothing to see..borderedsilently fell back tocurrentColorthrough a reference to a token the generator never emitted.vitops docs/vitops agentsnow surface config warnings (on stderr, so pipingdocsis unaffected). They discarded them, unlikegenerateandvalidate.- Contrast is checked against every background plane a role emits (
bg,bg-muted,bg-bold), not onlybg— body text on acardwas previously unguaranteed. SubgridandCardsrendered as unstyled lists, in every format. They drew their geometry with Tailwind utilities that no framework CSS layer defines, so undercss/bricksthey had no layout at all — and undertailwindtoo, because Tailwind v4 is JIT and does not scannode_modules: a class only a shipped component references is never generated. Cards laid out atgrid-row: auto— visually plausible, quietly wrong. Now drawn with framework CSS.<details>disclosures never opened. The.detailspattern animatedblock-sizefrom a collapsed state that<details>itself controls, so the content stayed at zero height.tailwindcssand@tailwindcss/viteare optional peer dependencies of@getvitops/astro, not dependencies. Installing the integration no longer pulls Tailwind into projects using thecssorbricksformat.
Docs
- Raw scale classes are frozen and do not remap in dark mode — now stated, with a migration
table to the role equivalents. The dark-mode guarantee only ever covered functional role
tokens, and nothing said so; a consumer site hardcoded
data-brx-theme="dark"and filled up with latent light-mode bugs. - Roles are extensible over a required core — the schema description and class reference read as a closed enumeration, which is why that consumer forked their own colour layer instead of adding a role.
md:vs@md:vsmd-in the tailwind format —@md:uses the framework’s breakpoints,md:works but uses Tailwind’s (which differ:sm:is 40rem,@sm:is 30rem),md-is silently inert. Plus: registering--container-*also re-points Tailwind’smax-w-*scale.- The
css/bricksbundles carry a/*!banner pointing atnpx vitops docs classes. The previous plain comment was stripped by the minifier and never reached the file.
0.8.0 — 2026-07-27
One new feature — a live theme editor — plus repairs to things shipped in 0.7.0 that didn’t work outside this repo.
Added
-
<wc-theme-editor>— tune the whole design system in the browser, with no rebuild. Palette, semantic roles, type roles, spacing, layout, pattern geometry, radii and shadows, layered as:rootcustom-property overrides and exportable as CSS or as adesign-system.jsonpatch. On a dev server running@getvitops/vite, Save to source writes the patch back through validate → write → regenerate; on a static build the probe fails and the button isn’t rendered.It ships as a separate, opt-in bundle —
@getvitops/core/editor, ~13 kB, no Lit — and is never registered inelements.js, so a page that doesn’t ask for it pays nothing. Enable withgetvitops({ editor: true }).This is a deliberate exception to the framework’s rule that web components must progressively enhance accessible no-JS markup: it’s tooling, not a page pattern, and a live editor has no no-JS fallback to enhance. It’s quarantined rather than excused — don’t read it as precedent for behaviour JS in a
<wc-*>element. -
validate()returnswarnings: string[]for configs that parse and generate but won’t behave as authored, andvitops validateprints them. First case: apatterns.radiikey named after a pattern collides on--br-<name>(the example config hits this withradii.card).
Breaking
-
body { margin: 0 }is now part of the framework. The UA’s 8px margin offset every full-bleed surface — sticky headers andbg-*bands rendered inset, with a sliver of canvas around them, because the framework owns page gutters through.centered’s--gutter. This is the only UA reset the framework makes; it still deliberately ships no general reset (no globalbox-sizingchange), which would silently reflow existing layouts. Migration: drop anybody { margin: 0 }you added to compensate; add your own padding if you relied on the inset. -
.ctadefaults to theui-primaryrole instead ofbrand-primary. The three tiers of one interaction family had split colour lineage —:where(button, .btn)and:where(a, .link)resolved toui-primarywhile.ctaalone usedbrand-primary, so the focus ring changed colour depending on which tier you tabbed onto. Migration: none if the two roles share a hue (true of the example config). If they differ and you want the old colour, use the new.cta-brand-primaryvariant —brand-primarywas added tocta.roles, so a brand-coloured CTA is reachable rather than unavailable.
Fixed
- Dark mode worked only under Bricks. The dark block was emitted under
:root[data-brx-theme="dark"]alone — Bricks’ own attribute, which nothing else sets — while the shipped<color-scheme-toggle>writesdata-theme. Clicking “Dark” changed an attribute no rule matched. Both are now matched. (“System” still resolves to light; there is deliberately noprefers-color-schemeblock, since adding one would flip every existing site dark for dark-OS users.) - The colour scheme now persists across navigations, via
localStorage, and<Head />applies it before first paint so pages don’t render light and flip. Previously the choice was per-page state and the toggle even cleared it on unmount. @getvitops/emdash@0.2.1reported version0.2.0from its plugin descriptor — a hand-maintained literal thatchangeset versiondoesn’t touch. It’s now derived from package.json, andvp run releaseruns the test suite that catches this before publishing.generateIconInclude()is reachable. The semantic icon mapping (declare names + sets, get the build-timeincludemap) lived in a package path that was never exported, so nothing could call it. It moved to@getvitops/utils, which@getvitops/astrore-exports. Unresolvable semantic names now throw at build time naming every offender, where they were skipped silently.design-manifest.jsonreverse-index paths. Numeric colour steps mapped to the hue’sseed(which regenerates the whole ramp, collapsing every step onto one path); they now map toanchors.<n>.--br-<name>resolved topatterns.radii.<name>even when a pattern owned the variable.@getvitops/create’s emdash template pinned@getvitops/astro: ^0.4.0, a range that stopped resolving when astro joined the fixed group at 0.7.0.
0.7.0 — 2026-07-27
Breaking
-
A bare
<button>is no longer a filled brand-primary button. Actions now split into two tiers named by intent:.ctais persuasion (filled, bolder, roomier, lifts on hover) and bare<button>/.btnis affordance — it signals only that something is interactive, with no fill, nofont-weight: 600and no shadow.Migration: add
class="cta"to any button that should stay prominent — submit buttons, hero actions, anything driving a conversion. Dialog closes, toolbar buttons, icon buttons and toggles should keep the new default. To restore the old look globally, pointpatterns.items.btnin yourdesign-system.jsonback at the previous filled base.Why:
<button>the element means “interactive control”, not “primary action” — and a CTA is usually an<a>, because it navigates. Making the CTA a class is what finally lets it go on a link. The framework was also fighting its old default: fourteen component partials existed partly to undo the fill. -
chipis retired as vocabulary. The two small-label patterns now split by behaviour, not size:badgeis a static label (status, count, category),tagis an editable and/or dismissable one (e.g. entries in a filter list).Migration:
.chip-list→.tag-list. Its__chip/__chip-removesub-parts are removed — replace<span class="chip-list__chip">x <button class="chip-list__chip-remove">with<span class="tag">x <button class="tag__remove">, since a tag list is a list of tags. Items change appearance:.tagis outlined where the old chip was filled with--color-surface-muted. Tokens--*-chip-list→--*-tag-list; the redundantradii.chipprimitive is gone (use--br-tag). -
The small-label pattern group is renamed
tag→label, so group tokens are now--{p,br,b,ds,fs}-label. Migration: if you set any--*-tagexpecting the group value, switch to--*-label. Why: thetagpattern and thetaggroup compiled to the same variables, so the pattern’s override hook shadowed the group token and its-groupalias was unreachable.--*-tagis now free as thetagpattern’s own hook. -
@getvitops/astrojumps 0.4.2 → 0.7.0. It now shares the toolchain version instead of tracking its own line. The number changed; the package did not — 0.7.0 is the direct successor to 0.4.2, with no API change implied by the jump.Migration: update the version range, nothing else. Install
@getvitops/astroat the same version as your@getvitops/cli/@getvitops/generator.Why: astro depends on core, generator, utils and vite, and was already being bumped on every single toolchain release by its dependency updates — so its separate version line cost the same churn while making “which astro works with cli 0.6?” a question you had to answer yourself. Now the versions match by construction.
Added
.cta— the persuasion tier, with.cta-{success,danger,warning,info}role variants. A class, so it works on any element.:where(button, .btn)and:where(a, .link)— a pattern may now set bothelementandclass, emitting one zero-specificity rule. The element gets the styling with no class needed, the class carries it to any other tag, and any explicit class overrides it without!important.fill: true|falseon a pattern — states whether states and role variants drivebackground-color(pluson-solidtext) orcolor, instead of inferring it from the pattern’s name and base declarations. Existing configs are unaffected; the old inference is the fallback.- Every published package now ships its
CHANGELOG.mdin the npm tarball, so per-package history is readable atnode_modules/@getvitops/<pkg>/CHANGELOG.md(and on unpkg/jsdelivr) without needing repository access. - This file: curated, toolchain-level release notes covering all packages at once.
Fixed
- Role variants on element patterns were emitted at specificity 0-1-1 (
button.danger), outranking any plain class. They now emit as:where(button, .btn).danger, .btn-danger— both at class specificity, and reachable from a non-<button>host. - Pattern geometry now resolves through the group alias layer (
--br-btn-group: var(--br-control)) instead of hard-codingvar(--br-control, …)into each rule, so the whole cascade —--p-btn→--p-btn-group→--p-control→--p-default— is live custom properties you can inspect and edit in the browser. Applies tobtn,cta,badge,tag,cardandstatus. Computed values are unchanged. - The
linkpattern declareddefault_role: "brand-primary"while hard-coding aui-primarybase colour, so hovering shifted hue instead of intensifying. Itsdefault_roleis nowui-primary. @getvitops/astro’sFormRendererdefaulted its submit button toclass="btn btn-primary"— a class that never existed and a role that is not emitted. It now defaults to.cta.- The Tailwind bundle is no longer assembled during
css/bricksbuilds, where it was computed and discarded (it also read every framework partial off disk).