Changelog
What shipped
A version-by-version record of new components, changes, and fixes in Vuance UI.
Spring motion, one selection family and fields that notch without a patch
Shape morphs move on springs, MDropdown becomes the base of the selection family with MAutocomplete built on it, and outlined fields cut a real gap in their border instead of painting a patch behind the label — while every field state becomes a BEM modifier.
useShapeMorphtakestransitioninstead ofeasingand returnsrotatealongside the resolved transition.MShapegainstransition;durationnow only overrides the transition's own.TypeScript// before: useShapeMorph(shape, { easing: easeM3Standard, duration: 600 }) const { d, rotate } = useShapeMorph(shape, { transition: 'standard' })- The default morph is
expressive— a spring with a 60° peak roll — instead of the 600ms quartic ease. Passtransition="standard"for the letter of the M3 spec. MLoading type="expressive"no longer spins through a CSS animation: the transition carries the rotation, andtransition="calm"does not rotate at all.MOtpInputhas no defaultlabel. The kit does not invent user-facing copy in a language it cannot know; a field with neitherlabelnor a#labelslot warns in development.MOtpInputseparatordefaults to empty — groups are separated by the gap — and the separator element is not rendered when there is nothing to draw.- The otp-input fragments (
Field,Group,Separator) are removed; cells render from the component itself and are restyled through the#field,#maskand#groupslots. MDropdowntakesitemsinstead ofoptions, and every item carries anid— its identity, used to compare values. Arrays of primitives are no longer accepted by the dropdown or the autocomplete.Vue<!-- before: <m-dropdown :options="[{ label: 'One', value: 1 }]" /> --> <m-dropdown :items="[{ id: 1, label: 'One' }]" item-value="id" />menuOriginis renamed tomenuPlacement.- The dropdown's own variant union is gone:
MDropdownandMAutocompletetakevariant: 'filled' | 'outlined'from the field family, andunderlineis not valid for either. itemKeyis removed fromMAutocomplete— identity is always the item'sid.itemTitlenow defaults to'label'anditemValueto the whole item.- Slots are regrouped:
#defaultis the whole panel,#itemthe body of one row,#selectionthe whole chip block and#chipa single chip. OnMAutocomplete,#selectionchanges meaning from one chip to the whole block. role="combobox"moved from the wrapperdivto the real<input>. Selectors and tests that queried the wrapper for it must query the input.- Validation through
pathnow binds the selected value, not the displayed text: the adapter receives2where it used to receive"Two". useChipSelectionis removed. It duplicated the selection model without a comparator,maxormandatory; keyboard handling over chips lives inuseDropdownChips.MTextFieldcarries its state as BEM modifiers instead ofdata-*attributes:ui-text-field--focused,--populated,--error,--disabled,--prependand--append.SCSS// before: .ui-text-field[data-error] { … } .ui-text-field--error { … }- Outlined fields render a
<fieldset class="…__outline">inside the control, andMTextFieldwrapsleading-contentinui-text-field__leading. Styles that reached into the old markup must follow. - Component tokens that only served the removed label patch or the old geometry are gone:
outlined.label.*,filled.radius,filled.input.padding,filled.active,outlined.border.radius,container.height,label.activeand the focus border widths onMTextField;outlined.label.*onMNumberInput;label.notch.*onMTextarea.
- Motion is stated as maths in
utils/motion/: a spring is damping and stiffness, and its duration is derived rather than declared. Four named transitions —calm,standard,expressiveandbouncy— split intostandard/(the M3 spec) andexpressive/. - Morphs preserve contour area (the shoelace formula, corrected to exactly 1 at both ends), and rotation is a windup driven by the curve's own velocity, so a shape always settles at 0° instead of accumulating an angle across morphs.
MDropdownis the base of the selection family and takes the whole field surface —labelPlacement,density,rounded,error,helperText,required— plusmultiple,mandatory,max,clearable,valueComparatorandmaxHeight.MAutocompleteis the same component with a query: filtering, a draft that is put back on blur, Escape or Tab, and IME-safe Enter.- Selection is model-first: a value that is not in
itemsyet — a form that loads before its options — stays in the model and renders from a fallback row instead of being dropped. MListandMListItemgaindensity. The list provides it down the tree and a row can override it; heights follow the field family (44 / 56 / 64rem for one-line rows).- Outlined fields cut a real gap in their border for the raised label, drawn by a
fieldsetand a hiddenlegend. Nothing is painted behind the label, so the field reads right on any surface, and the gap is correct in the server-rendered HTML. - On outlined fields,
largeandpillcorners move the content and the notch past the curve instead of cutting the arc. MTextFieldexposescontrolandinput(MFieldParts) so composite fields can anchor a popover to the drawn box and move focus to the input.MOtpInputgainslabelPlacement: 'top' | 'hidden'.
- Error wins over focus: a focused invalid field keeps the error colour on its boundary and label in
MTextField,MNumberInputandMTextarea. - A filled
MTextFieldlabel turns primary on focus, like the other shapes; the rule that should have done it never matched. - With a prepend, the raised label of an outlined
MTextFieldor a splitMNumberInputreturns to the start edge, so the notch never depends on what the prepend holds. densityandroundedset private--ui-<component>-*values on the field, and every rule reads them once — no more density × placement × variant selector matrix.- Every
g()path uses the canonical dot form. The dash form warns and resolves tonullon keys that contain a dash. - Default copy lives in
shared/constants/messages.tsand icon names go throughICONS, so a message source can be plugged in later.
- The otp-input state layer rendered nothing in any of its four cell states, because a dash path cannot address
field-active-outline. - The otp-input caret tracks a held arrow key instead of jumping, and the first ArrowLeft on a full code is no longer swallowed.
- Keyboard order follows the
itemsarray, so a row inserted in the middle of a mounted list is reached where it stands. - Rows blocked by
maxleave the arrow-key order and carryaria-disabled; selected rows stay removable at the limit. MAutocompleteno longer shows[object Object]for a value missing fromitems.
- Named transitions never reference the bezier solver, so a project that uses springs only does not ship it.
easeM3Standardis kept as a deprecated export; usetransition: 'standard'.
- The kit's constitution — principles, craft, behaviour, axes and decisions — is written down in
docs/system/in en-US and ru-RU.
Plugin-owned runtime and a leaner client bundle
The state layer drops Pinia for a single plugin-provided runtime, the Material Color Utilities leave the eager client bundle, components stop registering globally, and misconfiguration now fails the build instead of rendering a colourless application.
- Theme options moved under a
themegroup.themes,defaultTheme,defaultPalette,defaultDefinition,defaultContrast,defaultVariant,defaultNeutralChroma,semanticColors, andsemanticBlendare no longer read at the top level. Because Nuxt merges module defaults with your configuration, a stale key would not error — it would leave the application with only the built-in fallback palette — so the module refuses to build and prints the migration map instead.TypeScriptmaterialKit: { theme: { themes: THEME_COLORS, default: { palette: 'forest', definition: 'dark', contrast: 'medium' }, semanticColors: { success: '#2e7d32' }, }, } - Removed
defineMaterialKitand the@pr0s1k/primetime-kit/defineKitentry point. The helper performed no transformation, and Nuxt already types thematerialKitkey from the module itself — declare the object directly.TypeScript// before: materialKit: defineMaterialKit({ … }) materialKit: { theme: { /* … */ } } setColorFromImage()is now asynchronous and returnsPromise<string | null>. The Material Color Utilities quantizer is loaded on demand, which is what keeps it out of every page load; a lazily loaded module cannot be awaited synchronously.- Components are no longer registered globally.
<m-button>and friends still resolve through auto-import in templates, but a page now bundles only the components it actually uses. Applications that render kit components dynamically by name — runtime-compiled templates,<component :is="'MButton'">— must import those components explicitly. - Pinia is no longer a dependency of the kit and is no longer installed for you.
useThemeStore()survives as a deprecated alias ofuseMaterialTheme(), but it returns a plain reactive object:storeToRefs(),$patch,$subscribe, and passing a Pinia instance no longer work.useWindowSizeStore()is removed — useuseSSRWindowSize().
- The module now fails the build on a
theme.default.palettethat is not declared intheme.themes, listing the declared keys. Previously a typo resolved to no theme at all and the application rendered without a single colour token, with no error and no warning. - Added
restrict.customPalette, an application-level lock on runtime palettes. Under the lock only palettes declared intheme.themesmay be used: a cookie claiming a custom seed, or naming an undeclared key, resolves to the default palette.setCustomColor()andsetColorFromImage()become no-ops, and the chroma and variant setters write through the sanitized state so the cookie cannot accumulate flags the runtime must ignore. Readtheme.canCustomizePaletteto hide a colour picker accordingly. - Consumer-defined breakpoints are now first-class in the runtime: any key added to
materialKit.breakpointsappears in theis,more, andlessmaps under its camelCased name. Declare it inKitBreakpointRegistryfor typed access.TypeScriptdeclare module '@pr0s1k/primetime-kit/types' { interface KitBreakpointRegistry { ultrawide: true } }
- The theme controller and breakpoint state are created once by a
materialplugin and injected as$material.useMaterialTheme()anduseBreakpoint()are thin readers over it, so every caller shares one instance and a write in one component is visible synchronously in every other. Cookie-backed state alone could not carry that guarantee — Nuxt's cross-instance cookie synchronisation is asynchronous in the browser and absent on the server. - The palette CSS is rendered during SSR and travels to the client in the payload. The generator is re-imported only when the theme actually changes.
- Split the colour mathematics out of the configuration helper into
utils/theme/generateScheme, and moved the theme, viewport, and image-colour internals intoruntime/utils/. They are no longer exposed as auto-imports, socreateMaterialTheme,createThemeController,generateScheme, andseedViewportno longer occupy names in the application namespace. - The generated
$material-kit-breakpointsmap now carries the fully resolved configuration with explicitpxunits, rather than only the keys an application overrode. The SCSS defaults become a fallback instead of a second source of truth, and numeric configuration values no longer produce unitless media queries that browsers silently ignore. - Viewport size is a single shared state updated by one resize listener, seeded on the server from the request device class. The first client measurement is deferred until after hydration.
- A malformed palette cookie no longer takes down server rendering. Any seed that is not three, six, or eight hexadecimal digits — or a non-string value — reached the scheme generator and threw, answering the request with a 500. Seeds and scheme variants are now validated, and an unusable value degrades to "palette not applied".
data-paletteno longer changes before the stylesheet for that palette exists. On the first theme switch of a page the attribute moved immediately while the generated CSS was still loading, leaving the interface with no colour tokens for the duration of the fetch.- Kit plugins are appended rather than prepended, so they cannot run ahead of
@nuxtjs/devicewhen an application lists that module first. Previously the viewport seed read an undefined$deviceand rendered every mobile visitor at desktop width. isandmore/lessare computed from the same resolved breakpoint list. A custom key previously appeared inisbut not in the other two maps.- Restored two unit specifications that had stopped resolving after a file move and were silently not running.
- The Material Color Utilities are no longer part of the eager client graph. The generator sits in an on-demand chunk of roughly 20 KB gzipped, fetched only when a visitor changes the theme, and the scheme is no longer rebuilt during hydration to reproduce markup the server already sent.
- Dropping global component registration removes the whole library from the entry graph. On the documentation site this cut eagerly loaded resources from 119 to 88 and module preloads from 73 to 52.
- Breakpoint sorting and name conversion happen once per application instead of inside the width-dependent computed, and the bands are shared rather than rebuilt per
useBreakpoint()call. - The resize listener skips writes that do not change the measured size, and
orientationchangeis read on the next frame, where the browser has recomputed the window dimensions.
useThemeStore()is a deprecated alias ofuseMaterialTheme()and will be removed in a future release.
- Rewrote the README around installation, configuration, and first use, and split the reference material into dedicated configuration, architecture, layout, and contributing documents.
Runtime theming and validation architecture
A foundational release that replaces build-time dynamic theme output with an SSR-safe runtime palette engine, expands the Material 3 colour system, and makes validation an opt-in, engine-agnostic integration.
- Dynamic configured palettes are no longer emitted as build-time SCSS for every theme.
<MApp>now emits server-rendered, reactive CSS for the single active palette; applications that relied on selectors for inactive generated palettes must select that palette at runtime or retain an explicit static SCSS theme. - Validation is no longer installed or registered automatically.
vee-validateandyupare optional peer dependencies, and@vee-validate/nuxtis no longer added by the module. Inputs withpathstay inert until an application provides an adapter.TypeScript// app.vue or a layout above the forms import { provideValidationAdapter, veeValidateAdapter, } from '@pr0s1k/primetime-kit/validation' provideValidationAdapter(veeValidateAdapter()) useFormSchemano longer returns a Yupschemaor a Vee Validate form context. It now returns an adapter-backedform; consumers that read those implementation-specific values must migrate to the adapter API.- The validation engine is deliberately outside the core bundle. Consumers of validation must install the optional peer dependencies for their chosen adapter; applications that do not use validation no longer ship an engine by default.
- Added runtime MD3 Material Color Utilities scheme generation for configured and custom palettes. The active palette's light and dark token sets are rendered on the server and updated reactively in the browser, preventing a theme flash on first paint.
- Added named MCU variants:
tonalSpot,neutral,vibrant,expressive,monochrome,fidelity,content,rainbow, andfruitSalad. Themes can also opt into the 2021 or 2025 Material specification. - Added configuration and runtime controls for contrast, neutral chroma, overall chroma, and the active MCU variant.
setContrast,setNeutralChroma, andsetVariantpersist the selected runtime overrides with the palette.TypeScriptmaterialKit: defineMaterialKit({ defaultVariant: 'expressive', defaultContrast: 'high', defaultNeutralChroma: 8, semanticColors: { success: '#2e7d32', warning: { color: '#ed6c02', blend: true }, }, themes: [{ key: 'violet', name: 'Violet', color: '#7c4dff', variant: 'vibrant', neutralChroma: 8, contrast: 0.5, }], }) - Added harmonized semantic
success,warning, andinfocolor roles: each produces its colour, on-colour, container, and on-container tokens for both light and dark schemes. Applications can replace or add semantic seeds globally or per theme, and choose whether each seed is harmonized with the palette. - Added
setColorFromImage()to the theme store. It extracts a representative seed from browser image pixels through the MCU quantizer and applies it as a custom palette. - Added the public
@pr0s1k/primetime-kit/validationentry point. It exposes adapter registration, engine-neutral field and form contracts, a prebuilt Vee Validate + Yup adapter, schema generation, and form-builder helpers. - Added a
ValidationAdapterseam withbindFieldandcreateForm. Custom adapters can now connect another validation engine without changing input components or the declarative form API. - Added a global MD3-styled scrollbar with a rounded, inset thumb that respects component shape boundaries.
<MApp>now owns document theme output:data-definition,data-palette, anddata-contrastattributes plus the generated style element. Changing palette, light/dark/system definition, or contrast updates the active scheme reactively while retaining SSR output.- The runtime emits only the active palette's light and dark selectors. This reduces generated CSS from a configuration-wide matrix to the CSS actually used by the document.
- Theme contrast accepts
standard,medium,high, or a clamped numeric level from-1to1. Contrast is resolved into the generated token values rather than selected by an additional CSS rule. - Palette cookies retain support for legacy string values and now store optional runtime
variantandneutralChromaoverrides. A null neutral-chroma override means automatic variant-derived neutrals. - Custom palettes and configured palettes now follow one generation path. SCSS-defined themes remain supported through
definedInScssand do not invoke the dynamic generator. MAlertnow uses dedicated semantic roles for info, success, and warning instead of reusing primary, secondary, and tertiary roles.- Deprecated the
surface-varianttoken mapping in favour ofsurface-container-highest; the old key remains as a compatibility alias for existing component tokens. - Reworked scrollbar styling for Firefox and WebKit: tracks stay transparent, the thumb uses outline roles, and its inset avoids touching rounded container edges.
- Moved runtime utilities into focused
setup,theme,context,guards,props,viewport, andcolormodules. The public@pr0s1k/primetime-kit/defineKitimport remains unchanged. - Added explicit internal imports for distributed runtime code that cannot rely on Nuxt auto-import transformation after installation in
node_modules. - Package contents now exclude test and specification files.
- Added 0.3.0 migration guidance for adapter installation, the new runtime theme configuration, and the active-palette rendering model.
Critical layout hotfix
A focused hotfix that restores correct component `uses` resolution and layout behaviour.
- Restored correct
usesresolution so slotted content and default renderers no longer collide. - Fixed a layout regression where sticky zones lost their reserved space without JS.
- Rebuilt
MAlertdocumentation page, examples, and generated manifests.
From Nuxt layer to distributable module
The kit graduates from an in-repo Nuxt layer into a published Nuxt module — and ships the largest component wave yet on shared interaction foundations.
- Vuance UI is now a distributable Nuxt module (
@pr0s1k/primetime-kit), not an in-repo layer. Consumers register it throughmodulesinstead ofextends.TypeScript// nuxt.config.ts (consumer) import { defineMaterialKit } from '@pr0s1k/primetime-kit/defineKit' export default defineNuxtConfig({ modules: ['@pr0s1k/primetime-kit'], materialKit: defineMaterialKit({ themes: THEME_COLORS, breakpoints: { 'mobile-xs': '320px', 'tablet-xs': '905px', 'desktop-xs': '1440px' }, }), }) - The package is renamed to
@pr0s1k/primetime-kitand exposes explicitexportssubpaths; deep relative imports into the kit no longer resolve.JSON{ "name": "@pr0s1k/primetime-kit", "exports": { ".": { "types": "./dist/module.d.mts", "import": "./dist/module.mjs" }, "./defineKit": { "import": "./dist/runtime/shared/utils/defineKit.js" }, "./types": { "import": "./dist/runtime/shared/types/props.js" } }, "files": ["dist", "README.md", "LICENSE"] } - Kit source moved from
app/**tosrc/runtime/**; internal runtime imports now go through the#kitalias instead of~/~~. - Removed the legacy
v-click-outsideplugin — use theuseClickOutsidecomposable instead.
- New module definer
src/module.tsregisters the component libraries (publicmprefix + internalcore), deep auto-imports, and the theme pipeline.TypeScriptasync setup(options, nuxt) { const resolver = createResolver(globalThis._importMeta_.url) const runtime = (path: string) => resolver.resolve('./runtime', path) nuxt.options.alias['#kit'] = runtime('.') addComponentsDir({ path: runtime('components/ui'), prefix: 'm', pathPrefix: true, global: true }) addComponentsDir({ path: runtime('components/core'), prefix: 'core' }) // `/**` forces a deep scan so nested composables auto-import. addImportsDir(runtime('composables/**')) addImportsDir(runtime('store/**')) addImportsDir(runtime('shared/**')) registerThemePipeline(options, nuxt) } - Builds with
nuxt-module-build+ abuild.config.tsthat ships SCSS/CSS as raw source for the consumer's Vite to compile.TypeScript// build.config.ts export default { hooks: { // Only the 'js' loader: .ts is transpiled (+ .d.ts), while .vue and // .scss/.css fall through as raw copies for the consumer's build. 'mkdist:entry:options'(_ctx, _entry, options) { options.loaders = ['js'] }, }, } - The module auto-registers its peer modules, so consumers install one package.TypeScript
moduleDependencies() { return { '@vee-validate/nuxt': {}, '@pinia/nuxt': {}, '@nuxtjs/device': {}, '@nuxt/icon': { defaults: { provider: 'server', serverBundle: { collections: ['ic'] } } }, } } - Dynamic theme store:
systemcolor mode plus runtime custom HEX palettes generated on the client.TypeScriptconst theme = useThemeStore() theme.definition = 'system' // light | dark | system theme.setCustomColor('#7c4dff') // seeds an MD3 scheme at runtime - Added the shell and foundation primitives
MApp,MOverlay,MSurface,MSelectionGroup,MHotkey, andMLazy.Vue<template> <m-app> <NuxtPage /> </m-app> </template> - Added typed inputs:
MAutocomplete,MOtpInput,MRating,MFileUpload,MConfirmEdit,MTextarea,MNumberInput, andMFileInput. - Added the
MColorPickerfamily andMColorInputover a pure RGBA color codec. - Added content and collection components:
MAvatar(+getAvatarInitials),MBanner,MBreadcrumbs,MChipGroup, andMListSubheader. - Added
MPagination,MTimeline, and the headlessuseVirtualScrollcomposable.
- Migrated dialogs and sheets from
vue-final-modalonto the sharedMOverlayprimitive; VFM remains only an internal mounting adapter. - The theme pipeline emits
~material-kit-configand~material-kit-themesas build templates derived from the configthemes/breakpoints. - Moved
roadmap.mdtodocs/ROADMAP.mdand removed theaudit.ps1maintenance script.
MAlertseverity maps to explicit MD3 roles so success and warning read correctly against the generated scheme.TypeScript// info → secondary, success → tertiary, warning → primary, error → error- Async
MAutocompletesuggestions stay stable through hydration with server-safe initial state.
- Shipped docs v2: build-time API generation, token manifests, and interactive playgrounds.
Zero-Runtime M3 foundations
The first foundational release — the build-time token system, the adaptive layout engine, and the core component set.
- Introduced the Zero-Runtime M3 token system: co-located
$tokensmaps resolved at build time throughg(). - Added the carving auto-layout engine with self-registering zones,
useLayoutZone(), and an SCSS-first column system (m-container/m-row/m-col). - Added headless primitives: registry/selection chain,
usePopover,useDrag,useTimer, and form builders. - Shipped the Dropdown and Menu family with teleported surfaces and native CSS anchor positioning.
- Migrated the theme system to a Pinia store with build-time SCSS generation.
- Renamed the component prefix from
ui-tom-.
- Fixed SSR/no-JS sticky positioning by moving layout rules into generated CSS.