This guide is for the engineers, architects, and maintainers who build on Vuetify or contribute to it. Where the User Guide explains how to use components, this document explains how the framework is built: its monorepo structure, the reactive composable-injection architecture, the component composition pattern, the multi-stage build pipeline, the code-generation tooling, the testing strategy, and the security and performance characteristics of the codebase.
It is derived directly from the source at version 4.1.0 (commit bbffe05). It is detailed
enough that an engineer — or an AI — could reconstruct the framework's architecture from
this document alone.
Table of Contents
- Executive Summary
- Repository Architecture
- The
createVuetifyPlugin: Initialization Flow - Component Composition Architecture
- The Composables Layer
- Theming Internals
- The Build Pipeline
- The API Generator
- Configuration Reference
- Styling & SASS System
- Testing Strategy
- Security Analysis
- Performance & Bundle Strategy
- Development & Operations
- Contributing
- Known Issues & Improvement Opportunities
- Quick Reference
1. Executive Summary
Vuetify is a Vue 3 Material Design component framework distributed as the vuetify npm
package (v4.1.0, MIT). It comprises 104 stable components, 10 experimental "labs"
components, 8 directives, ~63 composables, 15 icon sets, 43 locales, and a CSS/SASS design
system.
Architectural thesis. The framework is organized around a single idea: one plugin
builds a set of reactive subsystems and provides them through Vue's dependency-injection,
and every component consumes those subsystems through small, composable hooks. There is no
central store and no class hierarchy — state lives in effectScope-bound reactive objects
keyed by InjectionKey symbols.
| Attribute | Value |
|---|---|
| Package | vuetify |
| Version | 4.1.0 |
| License | MIT |
| Required Vue | ^3.5.0 |
| Node (tooling) | >=24.11.1 |
| Module type | ESM ("type": "module") |
| Package manager | pnpm@10.26.1 (workspace monorepo) |
| Language | TypeScript + TSX (JSX render functions) |
| Build outputs | lib/ (per-file ESM), dist/ (bundled UMD/ESM), type declarations |
Workspace packages: the public vuetify library, the @vuetify/api-generator tooling,
and vuetifyjs.com (the documentation site).
2. Repository Architecture
Vuetify is a pnpm workspace monorepo with three packages under packages/:
vuetify/ (root: @vuetify/vuetify-root, private)
├── packages/
│ ├── vuetify/ The published framework (npm: "vuetify")
│ │ ├── src/
│ │ │ ├── framework.ts createVuetify() — the heart
│ │ │ ├── entry-bundler.ts CDN/UMD entry (bundles all components)
│ │ │ ├── components/ 104 components (VBtn, VTextField, …)
│ │ │ ├── labs/ 10 experimental components
│ │ │ ├── composables/ ~63 reactive hooks (theme, display, …)
│ │ │ ├── directives/ 8 directives (ripple, intersect, …)
│ │ │ ├── blueprints/ md1 / md2 / md3 preset bundles
│ │ │ ├── iconsets/ 15 icon-set adapters
│ │ │ ├── locale/ 43 language files + 2 adapters
│ │ │ ├── styles/ SASS: settings, tools, generic, utilities
│ │ │ ├── util/ Helpers (color, box, propsFactory, …)
│ │ │ └── icons.ts, types.ts Public type surface
│ │ ├── build/ Rollup configs + babel plugins
│ │ ├── dev/ Local playground app
│ │ └── test/ Test setup, templates, browser commands
│ ├── api-generator/ Generates the per-component API docs (ts-morph)
│ └── docs/ vuetifyjs.com (Vite + vue-router + i18n + Algolia)
└── scripts/ Repo-wide build/dev/release scripts
Package boundaries
| Package | Published? | Role |
|---|---|---|
vuetify |
Yes (public) | The framework consumers install. |
@vuetify/api-generator |
No (private) | Reads built types, emits API metadata for the docs. |
vuetifyjs.com |
No (private) | The documentation website, consuming vuetify via workspace:*. |
Note
The docs site and api-generator depend on the library via
workspace:*, so they always build against the local source. This is why a change to a component's types is reflected in the generated API docs without publishing.
Module resolution
Inside packages/vuetify, the alias @/ maps to src/. This is configured three ways so
every toolchain agrees: in vite.config.ts (resolve.alias), in babel.config.js (the
module-resolver plugin), and in tsconfig.json (paths). The package's vite.config.ts
additionally aliases bare vuetify imports to src/framework.ts so the dev playground and
tests resolve to source rather than the built library.
3. The createVuetify Plugin: Initialization Flow
framework.ts exports createVuetify(options). Understanding its flow is the key to
understanding the whole framework.
export function createVuetify (vuetify: VuetifyOptions = {}) {
const { blueprint, ...rest } = vuetify
const options = mergeDeep(blueprint, rest) // 1. merge blueprint under user options
const { aliases = {}, components = {}, directives = {} } = options
const scope = effectScope() // 2. own reactive scope
return scope.run(() => {
const defaults = createDefaults(options.defaults) // 3. build subsystems
const display = createDisplay(options.display, options.ssr)
const theme = createTheme(options.theme)
const icons = createIcons(options.icons)
const locale = createLocale(options.locale)
const date = createDate(options.date, locale)
const goTo = createGoTo(options.goTo, locale)
function install (app: App) { /* 4. register + provide */ }
function unmount () { scope.stop() }
return { install, unmount, defaults, display, theme, icons, locale, date, goTo }
})!
}
Step-by-step
- Blueprint merge. If a
blueprintis supplied, the user's options are deep-merged over it (mergeDeep(blueprint, rest)), so explicit options always win. - Effect scope. The whole framework runs inside an
effectScope(), so all reactive effects (watchers, computeds) can be torn down at once viaunmount(). - Subsystem construction. Seven factory functions build the reactive subsystems. Note
the dependency order:
dateandgoToreceive the already-builtlocaleinstance, because dates and scrolling are locale/RTL-aware. install(app)runs whenapp.use(vuetify)is called. It:- Registers each directive via
app.directive(key, …). - Registers each component via
app.component(key, …). - Registers aliases — clones of components under a new name (with
aliasNameset). - Installs the theme into its own child
effectScopeand stops it on app unmount. - Provides each subsystem under its
InjectionKeysymbol. - For SSR, defers the first
display.update()until after mount/hydration (or hooks into Nuxt'sapp:suspense:resolve). - Adds a global mixin exposing the reactive
$vuetifyobject (when the Options API is enabled).
- Registers each directive via
Injection symbols
Each subsystem is provided under a Symbol.for(...) injection key. Components retrieve them
with inject() inside their composables:
| Subsystem | Symbol | Consumer composable |
|---|---|---|
| Defaults | vuetify:defaults |
injectDefaults() / useDefaults() |
| Display | vuetify:display |
useDisplay() |
| Theme | vuetify:theme |
useTheme() / provideTheme() |
| Icons | vuetify:icons |
useIcon() |
| Locale | vuetify:locale |
useLocale() / useRtl() |
| Date | vuetify:date-options, vuetify:date-adapter |
(date components) |
| GoTo | vuetify:goto |
useGoTo() |
Important
Because state is provided through Vue injection, provider components like
<v-theme-provider>,<v-locale-provider>, and<v-defaults-provider>work by calling the matchingprovide*function to override the injected instance for their subtree. This is the same mechanism the root plugin uses — there is no special-casing.
4. Component Composition Architecture
Every Vuetify component follows the same pattern. Studying VBtn reveals the template all
others follow.
propsFactory + make*Props mixins
Shared prop groups are defined once as factory functions (makeBorderProps,
makeDensityProps, makeElevationProps, makeRoundedProps, makeSizeProps,
makeTagProps, makeThemeProps, makeVariantProps, makeLocationProps,
makePositionProps, makeRouterProps, makeLoaderProps, makeGroupItemProps,
makeDimensionProps, makeComponentProps, …). A component composes its props by spreading
them:
export const makeVBtnProps = propsFactory({
active: { type: Boolean, default: undefined },
block: Boolean,
icon: [Boolean, String, Function, Object] as PropType<boolean | IconValue>,
ripple: { type: [Boolean, Object], default: true },
// …component-specific props…
...makeBorderProps(),
...makeComponentProps(),
...makeDensityProps(),
...makeElevationProps(),
...makeRoundedProps(),
...makeRouterProps(),
...makeSizeProps(),
...makeTagProps(),
...makeThemeProps(),
...makeVariantProps(),
// …
}, 'VBtn')
propsFactory(props, name) returns a callable that produces the prop definitions and
supports prop renaming/defaulting at call sites.
Composable hooks pair with the prop mixins
For every make*Props() there is a matching use*() composable that reads those props and
returns reactive classes/styles:
const { themeClasses } = provideTheme(props)
const { densityClasses } = useDensity(props)
const { elevationClasses } = useElevation(props)
const { roundedClasses, roundedStyles } = useRounded(props)
const { sizeClasses, sizeStyles } = useSize(props)
const { colorClasses, colorStyles, variantClasses } = useVariant(props)
const { dimensionStyles } = useDimension(props)
The render function then merges these into a single class/style binding. This is why a
prop like elevation, rounded, density, color, or variant behaves identically
across dozens of components — the behavior lives in one composable, not in each component.
genericComponent / defineComponent + useRender
Components are declared with genericComponent() (which preserves generic slot typing) or
defineComponent(), and their render logic is registered with useRender(() => <jsx/>).
Render functions are written in TSX, compiled by @vuetify/babel-plugin-jsx.
export const VBtn = genericComponent<VBtnSlots>()({
name: 'VBtn',
props: makeVBtnProps(),
emits: { /* … */ },
setup (props, { slots }) {
const { themeClasses } = provideTheme(props)
// …compose other hooks…
useRender(() => withDirectives(
<props.tag class={[ 'v-btn', themeClasses.value, /* … */ ]}>{ /* … */ }</props.tag>,
[[vRipple, props.ripple]],
))
},
})
Note
Components import sibling components directly (e.g.
VBtnimportsVIcon,VProgressCircular, andVDefaultsProvider). Theindex.tsof each component folder re-exports the public surface; the package'sexportsmap groups related components (e.g. allVList*undervuetify/components/VList).
The defaults proxy
useDefaults() (in composables/defaults.ts) wraps the raw props in a Proxy. On every
prop read it resolves: explicitly-set prop → component default (defaults[name]) →
global default (defaults.global) → the prop's built-in default. It also detects
PascalCase keys in a component's defaults and provides them as sub-component defaults to
descendants. This is what makes createVuetify({ defaults }) and <v-defaults-provider>
work transparently.
5. The Composables Layer
The composables/ directory (~63 files) is the framework's true engine. They fall into
groups:
| Group | Composables | Responsibility |
|---|---|---|
| Framework state | theme, display, defaults, locale, icons, goto, date/ |
The injected subsystems built by createVuetify. |
| Visual props | border, density, dimensions, elevation, rounded, size, variant, color, position, location, tag |
Map shared props to classes/styles. |
| Behavior | group, nested/, proxiedModel, form, validation, delay, focus, focusTrap, focusGroups, toggleScope |
Stateful interaction logic. |
| DOM/observers | resizeObserver, intersectionObserver, mutationObserver, hydration, documentVisibility, ssrBoot |
Browser integration. |
| Data | filter, virtual, list-items, selectLink, router |
Data tables, lists, virtual scrolling, routing. |
| Specialized | calendar, dateFormat, mask/, hotkey/, scroll, touch, transition, loader, stack, teleport, layout |
Feature-specific. |
Key cross-cutting composables
proxiedModel— implements thev-modelcontract: a writable computed that readsprops.modelValueand emitsupdate:modelValue, supporting prop transforms. Nearly every input component uses it.group/nested— power selection acrossVBtnToggle,VChipGroup,VItemGroup,VTabs,VList,VTreeview, etc.nestedprovides pluggable strategies for active, open, and select behavior (ActiveStrategy,OpenStrategy,SelectStrategy).form/validation—createFormprovides aFormProvideregistry. Each input registers{ validate, reset, resetValidation }; the form aggregates child validity into a tri-state model (true/false/null).validateOndefaults to'input';fastFailshort-circuits at the first failure.validate()returns{ valid, errors }.layout— coordinatesVApp, app bars, drawers, and footers. Each layout child registers its size and position;layoutcomputes CSS variables (--v-layout-top, etc.) soVMainandgoTo'slayoutoption can offset correctly.
6. Theming Internals
composables/theme.ts is one of the most intricate subsystems. It is worth a maintainer's
full attention.
What createTheme builds
- Parsed options merged over
genDefaults()— which defines the fulllightanddarkpalettes and a set ofvariables(opacities, border/shadow colors, kbd/code colors, elevation overlay settings). name— a computed that resolves'system'to the live OS preference (viamatchMedia('(prefers-color-scheme: dark)'), updated on change).computedThemes— for each theme it: merges onto the appropriate base (light/dark), generates color variations (lighten/darken shades), and computeson-colors automatically using luminance (hasLightForeground).styles— a computed string of CSS wrapped in cascade layers.
CSS architecture: cascade layers
The generated stylesheet uses @layer to control specificity deterministically:
@layer vuetify-utilities {
@layer theme-base { :root { --v-theme-primary: 24,103,192; … } .v-theme--dark { … } }
@layer theme-background { .bg-primary { … } }
@layer theme-foreground { .text-primary { … } .border-primary { … } }
}
Colors are emitted as RGB component triples (--v-theme-primary: 24,103,192) so
components can apply opacity with rgb(var(--v-theme-primary), 0.5). Each non-on- color
also gets an -overlay-multiplier derived from its luminance, used by the elevation overlay
system.
Stylesheet injection
install(app) injects the styles. If the app uses @unhead/vue or @vueuse/head (common
with Nuxt), the theme registers a head entry and patches it reactively. Otherwise it upserts
a <style id="vuetify-theme-stylesheet"> element directly (respecting cspNonce). On the
server it renders once without watchers.
Animated transitions
change/toggle/cycle optionally animate via the View Transitions API
(document.startViewTransition) with a circular clip-path reveal whose origin is set by
setTransitionOrigin(event). It bails to an instant change when prefers-reduced-motion is
set or the API is unavailable.
Caution
theme.global.name.value = 'x'is a deprecated assignment path (it logs a deprecation via aProxy). Usetheme.change('x')instead. This is the likely root of the upstream "inconsistenttoggle()after enabling transitions" report — mixing the deprecated setter with the transition path can leave a stale_transitionOrigin.
Scoping
theme.scoped and theme.scope allow restricting theme CSS to a selector (:where(scope))
rather than :root, for embedding Vuetify inside a host app without leaking variables.
7. The Build Pipeline
The library has a multi-target build, orchestrated from packages/vuetify/package.json:
pnpm build
# = rimraf lib dist
# && build:lib
# && (build:dist & build:types:lib) # concurrent
# && build:types:bundle
| Step | Command | Output | Tool |
|---|---|---|---|
build:lib |
babel src --out-dir lib … |
lib/ — per-file ESM, mirrors src/ |
Babel |
build:dist |
rollup -c build/rollup.config.js |
dist/vuetify.js, .esm.js, .cjs, labs bundles |
Rollup |
build:types:lib |
tsgo --emitDeclarationOnly -p tsconfig.dist.json + transform-types.js |
lib/**/*.d.ts |
TypeScript (native preview tsgo) |
build:types:bundle |
rollup -c build/rollup.types.config.js |
bundled .d.ts for dist/ |
rollup-plugin-dts |
lib/ vs dist/
lib/is the primary published artifact (main/module→lib/framework.js). It is per-file ESM, which is what enables tree-shaking by the consumer's bundler. The Babellibenv applies two custom plugins:babel-plugin-add-import-extension(adds.jsextensions) andbuild/babel-plugin-replace-import-extension(rewrites.sass/.scssimports to.css).dist/is the bundled UMD/ESM build for CDN usage (vuetify.min.js,vuetify.esm.js), produced by Rollup with Terser.
sideEffects and tree-shaking
package.json declares sideEffects: ["*.sass", "*.scss", "*.css", "*.vue"]. This tells
bundlers that JS modules are side-effect-free (safe to drop if unused) while stylesheet
imports must be preserved. Combined with per-file lib/ output and vite-plugin-vuetify's
auto-import, only the components a consumer references end up in their bundle.
The exports map
package.json#exports defines fine-grained entry points: ., ./styles,
./styles/core|colors|utilities, ./framework, ./blueprints/*, ./components/*,
./directives/*, ./locale, ./locale/adapters/*, ./iconsets/*, ./labs/*,
./util/colors, ./date/adapters/*, and the ./dist/* bundles. Editor tooling metadata
(vetur, web-types) points at generated JSON in dist/json/.
8. The API Generator
@vuetify/api-generator produces the exhaustive per-component API metadata that powers the
documentation site and editor IntelliSense. It does not ship to consumers.
How it works
- Reads the built library (
vuetify/dist/vuetify-labs.js) and its generatedimportMap.jsonto enumerate every component. - Uses
ts-morphto statically analyze the component/composable/directive TypeScript types and extract props, events, slots, and exposed members. - Parallelizes the heavy type analysis across worker threads with Piscina (thread count clamped to roughly half the CPU cores, scaled by free memory).
- Merges human-written descriptions from per-locale data and reports any props missing a description.
- Emits API JSON plus Vetur tags/attributes and web-types for IDE support.
CLI flags (from yargs): --components <list>, --skip-directives, --skip-composables.
cd packages/api-generator
node src/index.ts --components VBtn VCard # generate for specific components
node src/index.ts --skip-composables
Note
Because the generator reads built types, the documentation API is always a faithful reflection of the actual TypeScript signatures — there is no hand-maintained prop list that can drift from the code.
9. Configuration Reference
The VuetifyOptions interface (from framework.ts) is the complete configuration surface.
| Option | Type | Notes |
|---|---|---|
aliases |
Record<string, Component> |
Register components under alternate names. |
blueprint |
Blueprint |
A preset bundle of the options below; merged under user options. |
components |
Record<string, Component> |
Components to register globally. |
directives |
Record<string, Directive> |
Directives to register globally. |
defaults |
DefaultsOptions |
Global + per-component default props. |
display |
DisplayOptions |
mobileBreakpoint, thresholds. |
theme |
ThemeOptions |
See below. |
icons |
IconOptions |
defaultSet, aliases, sets. |
locale |
LocaleOptions & RtlOptions |
locale, fallback, messages, adapter, rtl, separators. |
date |
DateOptions |
adapter, locale, formats. |
goTo |
GoToOptions |
container, duration, offset, easing, layout, patterns. |
ssr |
boolean | { clientWidth, clientHeight? } |
Enable SSR mode. |
ThemeOptions
| Field | Default | Notes |
|---|---|---|
defaultTheme |
'system' |
'light'/'dark'/'system'/custom name. |
themes |
{ light, dark } |
Map of theme definitions (dark, colors, variables). |
variations |
{ colors: [], lighten: 0, darken: 0 } |
Auto-generate shades. |
transition |
false |
true or { origin, duration } for animated switches. |
utilities |
true |
Emit bg-/text-/border- utility classes. |
stylesheetId |
'vuetify-theme-stylesheet' |
ID of the injected <style>. |
cspNonce |
— | Nonce for the injected style element. |
scope / scoped |
— | Restrict theme CSS to a selector. |
prefix |
'v-' |
CSS variable/class prefix. |
DisplayOptions
| Field | Default |
|---|---|
mobileBreakpoint |
'lg' (1145px) |
thresholds |
{ xs:0, sm:600, md:840, lg:1145, xl:1545, xxl:2138 } |
GoToOptions
| Field | Default |
|---|---|
duration |
300 (ms) |
easing |
'easeInOutCubic' |
offset |
0 |
layout |
false |
container |
document scrolling element |
Build-time environment variables
These are read by the package's Vite/Vitest configs during development and CI — not at runtime by consumers:
| Variable | Used in | Purpose |
|---|---|---|
HOST, PORT |
vite.config.ts |
Dev/preview server host/port (default port 8090). |
TEST |
vite.config.ts |
Switches config into test mode (disables SSR plugin). |
VITE_SSR |
vite.config.ts |
Enable the SSR dev server. |
BABEL_TYPES_8_BREAKING |
vite.config.ts |
Babel compat flag (defined to 'false'). |
TEST_TDD_ONLY, TEST_BAIL |
vitest.config.ts |
TDD/visual-test toggles. |
GITHUB_ACTIONS |
vitest.config.ts |
Selects CI reporters. |
Compile-time constants defined by Babel/Vite: __VUETIFY_VERSION__, __REQUIRED_VUE__,
__VUE_OPTIONS_API__.
10. Styling & SASS System
src/styles/ is a structured SASS design system:
| Directory | Contents |
|---|---|
settings/ |
All SASS variables (colors, spacing scale, component variables). |
tools/ |
Mixins and functions. |
generic/ |
Resets and base element styles. |
utilities/ |
Display, elevation, pointer-events, screen-reader, spacing helpers. |
elements/ |
Base typographic/element styling. |
main.sass |
The aggregate entry compiled into lib/styles/main.css. |
Consumers can override variables by configuring the build plugin's styles.configFile and
@use 'vuetify/settings' with (...). Three SASS entry points are exposed: vuetify (global
CSS), vuetify/settings (variables), and vuetify/tools (mixins).
Important
The precompiled
vuetify/styles(CSS) cannot be customized. SASS customization requires the Vite/Webpack plugin so the SASS is recompiled with the consumer's overrides — this is the single most common source of "my variable override did nothing" confusion.
11. Testing Strategy
Testing runs on Vitest 4 with two projects, configured in
packages/vuetify/vitest.config.ts:
| Project | Pattern | Environment | Purpose |
|---|---|---|---|
unit |
**/*.spec.{ts,tsx} |
jsdom |
Fast logic/component unit tests. |
browser |
**/*.spec.browser.{ts,tsx} |
Playwright (Chromium) | Real-browser interaction tests. |
Key points:
- The browser project uses
@vitest/browser-playwrightwith a fixed1280×800viewport, reduced motion, and clipboard permissions for deterministic runs. - Visual regression uses Vizzly (
@vizzly-testing/vitest), configured invizzly.config.js(1px threshold, signature ontheme+device). Atddworkflow (vizzly tdd start) supports local snapshot iteration. unplugin-auto-importinjects Vitest globals (describe,it,expect,vi, …) into spec files so they need no imports.- Test scripts:
pnpm test(all),test:unit,test:browser,test:coverage(Istanbul), andtest:allwhich shards the browser suite into 5 parallel runs.
pnpm --filter vuetify test:unit # fast feedback
pnpm --filter vuetify test:browser # interaction tests
pnpm --filter vuetify test:coverage # coverage report
Tip
Component test templates live in
packages/vuetify/test/templates/. The generator there throws "Cannot generate examples from props without a component" if invoked without a target — pass a component when scripting story/template generation.
12. Security Analysis
Vuetify is a client-side UI library. It has no server, no authentication, no network layer, and no secret handling of its own. The relevant security surface is the DOM behavior of its components and the safety of its style injection.
| Area | Assessment |
|---|---|
| Secrets | None in the published package. The docs site's .env.example lists its own third-party keys (Cosmic.js, EmailJS, Algolia) — these belong to the website, not the framework, and ship empty. |
| Style injection | The theme writes a <style> element via innerHTML with framework-generated CSS only (color triples and variable names). It is not derived from user content, and it honors cspNonce for CSP compliance. |
| XSS | Components render through Vue's template/JSX system (escaped by default). As with any UI library, passing untrusted HTML into a slot or a component that renders raw HTML is the consumer's responsibility. |
| Dependencies | Runtime dependency surface is minimal — the public package's only true peer is Vue; date adapters and icon fonts are optional/peer. Heavy dependencies (Babel, Rollup, Playwright, ts-morph) are dev-only. |
| Accessibility as security-adjacent | ARIA correctness is actively maintained; e.g. an upstream fix added a missing aria-label to the treeview toggle (WCAG 4.1.2). |
Note
When embedding Vuetify in a page with a strict Content Security Policy, set
theme.cspNonce(and the same nonce on any icon-font CSS) so the injected stylesheet is permitted. Considertheme.scopedto avoid leaking:rootvariables into a host app.
Caution
Treat untrusted strings rendered through components carefully. Vuetify escapes normal bindings, but components that intentionally render markup (rich text, code highlighting) will render whatever you give them — sanitize upstream.
13. Performance & Bundle Strategy
- Tree-shaking first. Per-file
lib/ESM +sideEffectsdeclaration + the Vite plugin'sautoImportmean a real app bundles only referenced components. Importingvuetify/componentswholesale is explicitly discouraged for production. - Reactive efficiency. Subsystems use
shallowRef/toRefand a singlewatchEffectper system (e.g. the display system recomputes all breakpoint booleans in one effect on resize) rather than many independent watchers. - Virtualization.
VVirtualScroll,VDataTableVirtual, and thevirtualcomposable render only visible rows for large datasets. - Observer-based DOM integration. Resize/intersection/mutation observers are attached
with
{ passive: true }listeners and torn down viaonScopeDispose, avoiding leaks. - SSR hydration. The display subsystem can be seeded with
ssrdimensions and defers its first client update to prevent hydration mismatches. - CSS cascade layers keep theme/utility specificity flat, avoiding
!importantarms races and reducing the cost of overriding styles.
Tip
For the smallest production footprint: enable the Vite plugin's
autoImport, use the SVG icon set (vuetify/iconsets/mdi-svg) instead of the full icon font, and import only the locales you actually ship fromvuetify/locale.
14. Development & Operations
Working on the framework
# Install (pnpm workspace)
pnpm install
# Run the component playground (packages/vuetify/dev)
pnpm --filter vuetify dev # default http://localhost:8090
# SSR dev server
pnpm --filter vuetify dev:ssr
# Lint (type-check + eslint, concurrently)
pnpm --filter vuetify lint
pnpm --filter vuetify lint:fix
# Build the library
pnpm --filter vuetify build
Root-level orchestration scripts (scripts/):
| Script | Purpose |
|---|---|
dev.js |
Repo-wide dev launcher (pnpm dev). |
build.js |
Repo-wide build (pnpm build). |
post-install.js |
Husky + Playwright Chromium install on prepare. |
confirm-npm-tag.js / parse-npm-tag.js |
Release tag safety checks (pnpm version). |
post-release-merge.js |
Post-release branch merge. |
lint-commit-message.js / prepare-commit-message.js |
Conventional-commit enforcement. |
tdd-run.js |
Visual TDD runner. |
Running the docs site
pnpm --filter vuetifyjs.com dev # default host/port from packages/docs/.env (8095)
pnpm --filter vuetifyjs.com build
The docs site is a Vite SPA using vue-router, vue-i18n, Pinia, Algolia InstantSearch,
markdown-it (with many plugins), and a PWA service worker. Its .env configures Cosmic.js
CMS, EmailJS, and the search API.
Release & nightly builds
Versioning uses @lerna-lite. The master, dev, and next branches publish nightly to
the @vuetify/nightly npm namespace at 1200 UTC for testing.
15. Contributing
- Toolchain: Node ≥ 24.11.1,
pnpm@10.26.1. Runpnpm installat the repo root. - Commits: Conventional Commits, enforced by commit-message hooks and
conventional-changelog-vuetify. - Linting: ESLint (with the local-rules plugin,
eslint-plugin-vuetify, SonarJS, and Stylistic) plusvue-tsc/tsgotype-checking.--max-warnings 0— warnings fail CI. - Tests: Add
*.spec.ts(unit) and/or*.spec.browser.ts(browser) next to the code. Visual changes need Vizzly snapshots. all-checks:pnpm all-checksruns build + lint + tests across the workspace — the pre-PR gate.- Custom ESLint rules live in
scripts/rules/(e.g. import sorting) — respect them or CI fails.
Important
When adding or changing a component's props, the API generator picks them up from the built types automatically, but prop descriptions are authored separately per locale. The generator reports missing descriptions — fill them in so the docs site is complete.
16. Known Issues & Improvement Opportunities
Drawn from the project's own issue tracker and observable code behavior:
| Area | Observation | Suggested direction |
|---|---|---|
| Theme transitions | toggle() can behave inconsistently once theme transitions are enabled (#22900). |
Clear _transitionOrigin deterministically after each change; discourage the deprecated theme.global.name.value setter. |
VBtn sizing |
Numeric size does not scale font size (#16288). |
Document the named-size scale; consider linking font scale to numeric size. |
VSelect |
menu-props.maxHeight dropped %/vh support (#16257); multiple + duplicate items can't deselect (#22878). |
Restore unit support; key selection on a stable item-value. |
VDialog |
Percentage max-width not respected after a layout change (#22890). |
Re-validate percentage width handling against the layout system. |
| Offline PWA icons | MDI font glyphs fail offline (#20227). | Recommend the SVG icon set for PWAs; precache font assets. |
VTreeview |
Keyboard support requested (#10796); a missing toggle aria-label was fixed (#22898). |
Continue the a11y pass — keyboard navigation across nested groups. |
VCombobox |
Empty value for whitespace-only input (#14874). | Trim/normalize free-text input before commit. |
Architectural maintainability notes
- The composable-injection design is clean but means bugs often live in a shared
composable, not the reporting component. When triaging, check the relevant
use*composable (e.g. a sizing bug →composables/size.ts) before the component file. theme.tsconcentrates significant complexity (CSS generation, layers, variations, view-transitions, head integration). It is a good candidate for focused unit tests aroundgenCssVariables,genOnColors, andgenVariations.- The monorepo's docs site carries its own large dependency set (CMS, search, analytics) that is unrelated to the framework; keep that boundary clear when auditing the published package's risk surface.
17. Quick Reference
Subsystem → symbol → composable
| Subsystem | Injection key | Composable |
|---|---|---|
| Theme | vuetify:theme |
useTheme() |
| Display | vuetify:display |
useDisplay() |
| Defaults | vuetify:defaults |
useDefaults() |
| Locale | vuetify:locale |
useLocale(), useRtl() |
| Icons | vuetify:icons |
useIcon() |
| Date | vuetify:date-adapter |
(date components) |
| GoTo | vuetify:goto |
useGoTo() |
Build targets
| Target | Output | Consumer use |
|---|---|---|
lib/ (Babel, per-file ESM) |
lib/framework.js |
npm install (tree-shakeable). |
dist/ (Rollup, bundled) |
vuetify.min.js, vuetify.esm.js |
CDN / <script>. |
| Types | lib/**/*.d.ts, bundled dist/*.d.ts |
TypeScript consumers. |
Common commands
pnpm install # bootstrap workspace
pnpm --filter vuetify dev # component playground
pnpm --filter vuetify build # build the library
pnpm --filter vuetify lint # type-check + eslint
pnpm --filter vuetify test:unit # unit tests (jsdom)
pnpm --filter vuetify test:browser # browser tests (Playwright)
pnpm all-checks # build + lint + test (pre-PR gate)
Inventory
| Asset | Count |
|---|---|
| Stable components | 104 |
| Labs components | 10 |
| Directives | 8 |
| Composables | ~63 |
| Icon sets | 15 |
| Locales | 43 |
| Blueprints | 3 (md1, md2, md3) |