Changelog

What shipped

A version-by-version record of new components, changes, and fixes in Vuance UI.

v0.5.0

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.

Breaking changes17
  • useShapeMorph takes transition instead of easing and returns rotate alongside the resolved transition. MShape gains transition; duration now 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. Pass transition="standard" for the letter of the M3 spec.
  • MLoading type="expressive" no longer spins through a CSS animation: the transition carries the rotation, and transition="calm" does not rotate at all.
  • MOtpInput has no default label. The kit does not invent user-facing copy in a language it cannot know; a field with neither label nor a #label slot warns in development.
  • MOtpInput separator defaults 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, #mask and #group slots.
  • MDropdown takes items instead of options, and every item carries an id — 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" />
  • menuOrigin is renamed to menuPlacement.
  • The dropdown's own variant union is gone: MDropdown and MAutocomplete take variant: 'filled' | 'outlined' from the field family, and underline is not valid for either.
  • itemKey is removed from MAutocomplete — identity is always the item's id. itemTitle now defaults to 'label' and itemValue to the whole item.
  • Slots are regrouped: #default is the whole panel, #item the body of one row, #selection the whole chip block and #chip a single chip. On MAutocomplete, #selection changes meaning from one chip to the whole block.
  • role="combobox" moved from the wrapper div to the real <input>. Selectors and tests that queried the wrapper for it must query the input.
  • Validation through path now binds the selected value, not the displayed text: the adapter receives 2 where it used to receive "Two".
  • useChipSelection is removed. It duplicated the selection model without a comparator, max or mandatory; keyboard handling over chips lives in useDropdownChips.
  • MTextField carries its state as BEM modifiers instead of data-* attributes: ui-text-field--focused, --populated, --error, --disabled, --prepend and --append.
    SCSS
    // before: .ui-text-field[data-error] { … }
    .ui-text-field--error { … }
  • Outlined fields render a <fieldset class="…__outline"> inside the control, and MTextField wraps leading-content in ui-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.active and the focus border widths on MTextField; outlined.label.* on MNumberInput; label.notch.* on MTextarea.
Features9
  • 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, expressive and bouncy — split into standard/ (the M3 spec) and expressive/.
  • 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.
  • MDropdown is the base of the selection family and takes the whole field surface — labelPlacement, density, rounded, error, helperText, required — plus multiple, mandatory, max, clearable, valueComparator and maxHeight. MAutocomplete is 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 items yet — a form that loads before its options — stays in the model and renders from a fallback row instead of being dropped.
  • MList and MListItem gain density. 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 fieldset and a hidden legend. 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, large and pill corners move the content and the notch past the curve instead of cutting the arc.
  • MTextField exposes control and input (MFieldParts) so composite fields can anchor a popover to the drawn box and move focus to the input.
  • MOtpInput gains labelPlacement: 'top' | 'hidden'.
Changes6
  • Error wins over focus: a focused invalid field keeps the error colour on its boundary and label in MTextField, MNumberInput and MTextarea.
  • A filled MTextField label 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 MTextField or a split MNumberInput returns to the start edge, so the notch never depends on what the prepend holds.
  • density and rounded set 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 to null on keys that contain a dash.
  • Default copy lives in shared/constants/messages.ts and icon names go through ICONS, so a message source can be plugged in later.
Fixes5
  • 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 items array, so a row inserted in the middle of a mounted list is reached where it stands.
  • Rows blocked by max leave the arrow-key order and carry aria-disabled; selected rows stay removable at the limit.
  • MAutocomplete no longer shows [object Object] for a value missing from items.
Performance1
  • Named transitions never reference the bezier solver, so a project that uses springs only does not ship it.
Deprecations1
  • easeM3Standard is kept as a deprecated export; use transition: 'standard'.
Documentation1
  • The kit's constitution — principles, craft, behaviour, axes and decisions — is written down in docs/system/ in en-US and ru-RU.
v0.4.0

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.

Breaking changes5
  • Theme options moved under a theme group. themes, defaultTheme, defaultPalette, defaultDefinition, defaultContrast, defaultVariant, defaultNeutralChroma, semanticColors, and semanticBlend are 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.
    TypeScript
    materialKit: {
      theme: {
        themes: THEME_COLORS,
        default: { palette: 'forest', definition: 'dark', contrast: 'medium' },
        semanticColors: { success: '#2e7d32' },
      },
    }
  • Removed defineMaterialKit and the @pr0s1k/primetime-kit/defineKit entry point. The helper performed no transformation, and Nuxt already types the materialKit key from the module itself — declare the object directly.
    TypeScript
    // before: materialKit: defineMaterialKit({ … })
    materialKit: { theme: { /* … */ } }
  • setColorFromImage() is now asynchronous and returns Promise<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 of useMaterialTheme(), but it returns a plain reactive object: storeToRefs(), $patch, $subscribe, and passing a Pinia instance no longer work. useWindowSizeStore() is removed — use useSSRWindowSize().
Features3
  • The module now fails the build on a theme.default.palette that is not declared in theme.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 in theme.themes may be used: a cookie claiming a custom seed, or naming an undeclared key, resolves to the default palette. setCustomColor() and setColorFromImage() become no-ops, and the chroma and variant setters write through the sanitized state so the cookie cannot accumulate flags the runtime must ignore. Read theme.canCustomizePalette to hide a colour picker accordingly.
  • Consumer-defined breakpoints are now first-class in the runtime: any key added to materialKit.breakpoints appears in the is, more, and less maps under its camelCased name. Declare it in KitBreakpointRegistry for typed access.
    TypeScript
    declare module '@pr0s1k/primetime-kit/types' {
      interface KitBreakpointRegistry { ultrawide: true }
    }
Changes5
  • The theme controller and breakpoint state are created once by a material plugin and injected as $material. useMaterialTheme() and useBreakpoint() 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 into runtime/utils/. They are no longer exposed as auto-imports, so createMaterialTheme, createThemeController, generateScheme, and seedViewport no longer occupy names in the application namespace.
  • The generated $material-kit-breakpoints map now carries the fully resolved configuration with explicit px units, 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.
Fixes5
  • 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-palette no 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/device when an application lists that module first. Previously the viewport seed read an undefined $device and rendered every mobile visitor at desktop width.
  • is and more/less are computed from the same resolved breakpoint list. A custom key previously appeared in is but not in the other two maps.
  • Restored two unit specifications that had stopped resolving after a file move and were silently not running.
Performance4
  • 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 orientationchange is read on the next frame, where the browser has recomputed the window dimensions.
Deprecations1
  • useThemeStore() is a deprecated alias of useMaterialTheme() and will be removed in a future release.
Documentation1
  • Rewrote the README around installation, configuration, and first use, and split the reference material into dedicated configuration, architecture, layout, and contributing documents.
v0.3.0

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.

Breaking changes4
  • 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-validate and yup are optional peer dependencies, and @vee-validate/nuxt is no longer added by the module. Inputs with path stay 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())
  • useFormSchema no longer returns a Yup schema or a Vee Validate form context. It now returns an adapter-backed form; 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.
Features8
  • 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, and fruitSalad. 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, and setVariant persist the selected runtime overrides with the palette.
    TypeScript
    materialKit: 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, and info color 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/validation entry 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 ValidationAdapter seam with bindField and createForm. 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.
Changes11
  • <MApp> now owns document theme output: data-definition, data-palette, and data-contrast attributes 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 -1 to 1. 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 variant and neutralChroma overrides. 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 definedInScss and do not invoke the dynamic generator.
  • MAlert now uses dedicated semantic roles for info, success, and warning instead of reusing primary, secondary, and tertiary roles.
  • Deprecated the surface-variant token mapping in favour of surface-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, and color modules. The public @pr0s1k/primetime-kit/defineKit import 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.
Documentation1
  • Added 0.3.0 migration guidance for adapter installation, the new runtime theme configuration, and the active-palette rendering model.
v0.2.1

Critical layout hotfix

A focused hotfix that restores correct component `uses` resolution and layout behaviour.

Fixes2
  • Restored correct uses resolution so slotted content and default renderers no longer collide.
  • Fixed a layout regression where sticky zones lost their reserved space without JS.
Documentation1
  • Rebuilt MAlert documentation page, examples, and generated manifests.
v0.2.0

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.

Breaking changes4
  • Vuance UI is now a distributable Nuxt module (@pr0s1k/primetime-kit), not an in-repo layer. Consumers register it through modules instead of extends.
    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-kit and exposes explicit exports subpaths; 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/** to src/runtime/**; internal runtime imports now go through the #kit alias instead of ~/~~.
  • Removed the legacy v-click-outside plugin — use the useClickOutside composable instead.
Features9
  • New module definer src/module.ts registers the component libraries (public m prefix + internal core), deep auto-imports, and the theme pipeline.
    TypeScript
    async 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 + a build.config.ts that 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: system color mode plus runtime custom HEX palettes generated on the client.
    TypeScript
    const 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, and MLazy.
    Vue
    <template>
      <m-app>
        <NuxtPage />
      </m-app>
    </template>
  • Added typed inputs: MAutocomplete, MOtpInput, MRating, MFileUpload, MConfirmEdit, MTextarea, MNumberInput, and MFileInput.
  • Added the MColorPicker family and MColorInput over a pure RGBA color codec.
  • Added content and collection components: MAvatar (+ getAvatarInitials), MBanner, MBreadcrumbs, MChipGroup, and MListSubheader.
  • Added MPagination, MTimeline, and the headless useVirtualScroll composable.
Changes3
  • Migrated dialogs and sheets from vue-final-modal onto the shared MOverlay primitive; VFM remains only an internal mounting adapter.
  • The theme pipeline emits ~material-kit-config and ~material-kit-themes as build templates derived from the config themes/breakpoints.
  • Moved roadmap.md to docs/ROADMAP.md and removed the audit.ps1 maintenance script.
Fixes2
  • MAlert severity 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 MAutocomplete suggestions stay stable through hydration with server-safe initial state.
Documentation1
  • Shipped docs v2: build-time API generation, token manifests, and interactive playgrounds.
v0.1.0

Zero-Runtime M3 foundations

The first foundational release — the build-time token system, the adaptive layout engine, and the core component set.

Features4
  • Introduced the Zero-Runtime M3 token system: co-located $tokens maps resolved at build time through g().
  • 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.
Changes2
  • Migrated the theme system to a Pinia store with build-time SCSS generation.
  • Renamed the component prefix from ui- to m-.
Fixes1
  • Fixed SSR/no-JS sticky positioning by moving layout rules into generated CSS.