# @vuetify/v0 - Complete Documentation > Headless Vue 3 UI primitives and composables for building modern applications and design systems. --- # Browser Support URL: https://0.vuetifyjs.com/introduction/browser-support # Browser Support Vuetify0 targets modern evergreen browsers. Some features require newer browser versions or degrade gracefully when unavailable. ## Baseline Support Vuetify0 requires Vue 3.5+, which targets browsers with native [ES2016 support](https://caniuse.com/es6): | Browser | Minimum Version | |---------|----------------:| | Chrome | 52+ | | Edge | 79+ | | Firefox | 52+ | | Safari | 10.1+ | | Opera | 39+ | > [!WARNING] > Internet Explorer is not supported and never will be. ## Feature Compatibility Some v0 features use modern browser APIs that have varying levels of support. The library includes feature detection and graceful degradation where possible. ### Cutting-Edge Features These features require the latest browser versions and may not work in all browsers: | Feature | | | | | Fallback | |---------|---:|---:|---:|---:|----------| | [CSS Anchor Positioning](https://caniuse.com/css-anchor-positioning) | 125+ | 147+ | —[^safari-anchor] | 125+ | Properties ignored | | [Popover API](https://developer.mozilla.org/en-US/docs/Web/API/Popover_API) | 114+ | 125+ | 17+ | 114+ | Optional chaining | | [Scrollend Event](https://developer.mozilla.org/en-US/docs/Web/API/Element/scrollend_event) | 112+ | 109+ | 18+ | 112+ | Falls back to scroll | [^safari-anchor]: Safari support for CSS Anchor Positioning is not yet available; track at [WebKit Bug 286106](https://bugs.webkit.org/show_bug.cgi?id=286106). Firefox 147+ requires the beta channel. ### Well-Supported Features These features have broad browser support with proper feature detection: | Feature | | | | | Fallback | |---------|---:|---:|---:|---:|----------| | [Native Dialog](https://developer.mozilla.org/en-US/docs/Web/API/HTMLDialogElement/showModal) | 37+ | 98+ | 15.4+ | 79+ | Optional chaining | | [ResizeObserver](https://caniuse.com/resizeobserver) | 64+ | 69+ | 13.1+ | 79+ | Feature detection | | [IntersectionObserver](https://caniuse.com/intersectionobserver) | 51+ | 55+ | 12.1+ | 79+ | Feature detection | | [MutationObserver](https://caniuse.com/mutationobserver) | 26+ | 14+ | 6+ | 12+ | Feature detection | | [matchMedia](https://caniuse.com/matchmedia) | 10+ | 6+ | 5.1+ | 12+ | Feature detection | | [Pointer Events](https://caniuse.com/pointer) | 55+ | 59+ | 13.1+ | 79+ | Assumed available | ### SSR & Hydration Safety All composables use the `IN_BROWSER` constant and `useHydration` composable to ensure safe server-side rendering: ```ts import { IN_BROWSER, useHydration } from '@vuetify/v0' const { isHydrated } = useHydration() // Browser APIs are only accessed after hydration if (isHydrated.value && IN_BROWSER) { // Safe to use browser APIs } ``` ## Feature Detection Constants v0 exposes feature detection constants for conditional logic: ```ts import { IN_BROWSER, SUPPORTS_TOUCH, SUPPORTS_MATCH_MEDIA, SUPPORTS_OBSERVER, SUPPORTS_INTERSECTION_OBSERVER, SUPPORTS_MUTATION_OBSERVER, } from '@vuetify/v0' ``` ## Documentation Site The documentation site you're viewing uses additional modern features for the best experience. For optimal documentation browsing: | Browser | Recommended Version | |---------|--------------------:| | Chrome | 123+ | | Edge | 123+ | | Firefox | 120+ | | Safari | 17.5+ | | Opera | 109+ | ### Docs-Specific Features The documentation site uses these modern CSS and browser features: - **`color-mix()`** — Theme color blending and glass effects - **`light-dark()`** — Native CSS theme switching - **`inert` attribute** — Modal focus management - **CSS Nesting** — Organized stylesheets - **Backdrop Filter** — Glass morphism effects - **Service Workers** — Offline support (PWA) > [!TIP] > If you experience visual issues or missing features while browsing the docs, try updating to the latest version of your browser. ## Known Limitations ### CSS Anchor Positioning The `Popover` component uses CSS Anchor Positioning for automatic placement relative to trigger elements. This is a new CSS feature with limited support: - **Chrome 125+**: Full support - **Edge 125+**: Full support - **Firefox 147+**: Beta support only - **Safari**: Not yet supported When anchor positioning is unavailable, the CSS properties are simply ignored. Consider using a JavaScript positioning library like [Floating UI](https://floating-ui.com) for broader browser support. ### Popover API The native Popover API provides built-in light dismiss behavior and top-layer rendering. Support is growing but not universal: - **Chrome 114+**: Full support - **Edge 114+**: Full support - **Firefox 125+**: Full support - **Safari 17+**: Full support - **Opera 100+**: Full support The v0 Popover component uses optional chaining (`element.showPopover?.()`) to gracefully handle missing API support. ### Native Dialog The `` element with `showModal()` is well-supported in modern browsers but older Safari versions lack support: - **Safari 15.4+**: Full support - **Safari < 15.4**: No support, no fallback Consider a dialog polyfill if you need to support older Safari versions. ## Polyfills Vuetify0 does not include polyfills to keep bundle sizes small. If you need to support older browsers, consider adding polyfills for: - **ResizeObserver**: [resize-observer-polyfill](https://www.npmjs.com/package/resize-observer-polyfill) · [caniuse](https://caniuse.com/resizeobserver) - **IntersectionObserver**: [intersection-observer](https://www.npmjs.com/package/intersection-observer) · [caniuse](https://caniuse.com/intersectionobserver) - **Dialog**: [dialog-polyfill](https://www.npmjs.com/package/dialog-polyfill) · [caniuse](https://caniuse.com/dialog) ## Testing Your Browser You can check your browser's feature support using these resources: - [Can I Use](https://caniuse.com) — Browser compatibility tables - [MDN Web Docs](https://developer.mozilla.org) — Feature documentation and support info - [Baseline](https://web.dev/baseline) — Web platform feature availability --- # Code of Conduct URL: https://0.vuetifyjs.com/introduction/code-of-conduct # Code of Conduct We are committed to providing a welcoming and inspiring community for all. This Code of Conduct outlines our expectations for participant behavior and the consequences for unacceptable behavior. ## Our Pledge We pledge to make participation in our project and community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation. ## Our Standards **Positive behaviors include:** - Using welcoming and inclusive language - Being respectful of differing viewpoints and experiences - Gracefully accepting constructive criticism - Focusing on what is best for the community - Showing empathy towards other community members **Unacceptable behaviors include:** - Sexualized language or imagery and unwelcome sexual attention - Trolling, insulting/derogatory comments, and personal attacks - Public or private harassment - Publishing others' private information without permission - Other conduct inappropriate in a professional setting ## Enforcement Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at [hello@vuetifyjs.com](mailto:hello@vuetifyjs.com). The project team will review and investigate all complaints, responding in a way appropriate to the circumstances. The team is obligated to maintain confidentiality regarding the reporter. ## Attribution This Code of Conduct is adapted from the [Contributor Covenant](http://contributor-covenant.org), version 1.4. View the full [CODE_OF_CONDUCT.md](https://github.com/vuetifyjs/0/blob/master/CODE_OF_CONDUCT.md) on GitHub. --- # Contributing to Vuetify0 URL: https://0.vuetifyjs.com/introduction/contributing # Contributing Thank you for your interest in contributing to Vuetify0! This guide will help you get started. ## Getting Started Before contributing, please: 1. Read the [Getting Started](/introduction/getting-started) guide to understand the project 2. Review existing [issues](https://github.com/vuetifyjs/0/issues) 3. Join our [Discord community](https://community.vuetifyjs.com) for questions ## Reporting Issues ### Bug Reports When reporting bugs, please include: - A clear, descriptive title - Steps to reproduce the issue - Expected vs actual behavior - Browser and OS information - A minimal reproduction (preferably a link to a repo or CodeSandbox) ### Feature Requests For new features: - Check if it's already been requested in [issues](https://github.com/vuetifyjs/0/issues) - Explain the use case and why it would benefit others - Consider if it fits the headless/composable philosophy of Vuetify0 ## Local Development ### Prerequisites - Node 26+ (matches .nvmrc) - pnpm 10.6+ - Git ### Setup ```bash # Clone the repository git clone https://github.com/vuetifyjs/0.git cd 0 # Install dependencies pnpm install # Start the dev environment pnpm dev # Start the docs site pnpm dev:docs ``` ### Project Structure ```text ├── packages/ │ └── 0/ # @vuetify/v0 - main package │ ├── src/ │ │ ├── components/ # Vue components │ │ ├── composables/ # Composable functions │ │ ├── utilities/ # Helper functions │ │ └── types/ # TypeScript types ├── apps/ │ └── docs/ # Documentation site └── dev/ # Development environment ``` ### Useful Commands ```bash # Development pnpm dev # Start dev environment pnpm dev:docs # Start docs site # Testing pnpm test # Run tests in watch mode pnpm test:run # Run tests once # Type checking pnpm typecheck # Check all packages # Linting pnpm lint # Lint codebase pnpm lint:fix # Auto-fix lint issues # Building pnpm build # Build packages ``` ## Pull Requests ### Before Submitting 1. Create a new branch from `master` 2. Make your changes 3. Run `pnpm lint:fix` to fix formatting 4. Run `pnpm typecheck` to check types 5. Run `pnpm test:run` to verify tests pass 6. Write tests for new functionality ### PR Guidelines - Keep PRs focused - one feature or fix per PR - Write a clear title and description - Reference any related issues - Be responsive to feedback ### Branch Naming Use descriptive branch names: - `fix/issue-description` - Bug fixes - `feat/feature-name` - New features - `docs/what-changed` - Documentation updates - `refactor/what-changed` - Code refactoring ## Commit Messages Follow the [Conventional Commits](https://www.conventionalcommits.org/) format: ```text type(scope): subject ``` ### Types - `feat` - New feature - `fix` - Bug fix - `docs` - Documentation changes - `refactor` - Code refactoring - `test` - Adding or updating tests - `chore` - Maintenance tasks ### Examples ```bash feat(createSelection): add toggle method fix(ExpansionPanel): correct aria-expanded state docs(getting-started): update installation instructions refactor(createRegistry): simplify reindex logic test(useForm): add validation edge cases ``` ### Guidelines - Use imperative mood ("add" not "added") - Keep the subject under 60 characters - Don't end with a period - Reference issues when applicable: `fix(useForm): validation error (#123)` ## Code Style ### General - Use TypeScript for all new code - Follow existing patterns in the codebase - Prefer `function` declarations over `const` arrow functions - Use single-word variable names when clear - Add JSDoc comments for public APIs ### Composables - Place in `packages/0/src/composables/` - Each composable in its own directory with `index.ts` - Include `@module` JSDoc block at the top - Colocate tests as `index.test.ts` - Export both standalone and context-creation functions ### Components - Follow the compound component pattern (Root/Item) - Extend `AtomProps` for polymorphic components - Use `useProxyModel` for v-model binding - Include proper ARIA attributes ## Testing - Write tests for new composables and components - Focus on behavior, not implementation details - Test edge cases and error conditions - Use `describe` blocks to organize related tests ```ts describe('createSelection', () => { it('should select an item', () => { // ... }) it('should respect mandatory option', () => { // ... }) }) ``` ## Skillz Feedback [Vuetify0 Skillz](/skillz) is our interactive tutorial system currently in beta. We're actively developing new content and improving the learning experience. ### How to Give Feedback - **Content issues**: Typos, unclear instructions, or incorrect examples - **Technical problems**: Bugs in the interactive editor or validation - **Suggestions**: New skill ideas or improvements to existing ones When reporting issues, please include: - The skill name and step number - What you expected vs what happened - Browser and OS information > [!DISCORD] Thank you for contributing! --- # Vuetify0 FAQ URL: https://0.vuetifyjs.com/introduction/frequently-asked # Frequently Asked Common questions and answers about Vuetify0. Have a question that isn't answered here? Try [Ask AI](/guide/essentials/using-the-docs#ask-ai) for instant answers, join our [Discord community](https://community.vuetifyjs.com), or open an [issue on GitHub](https://github.com/vuetifyjs/0/issues). ::: faq ??? What is Vuetify0? Vuetify0 is a collection of headless UI primitives and composables for Vue 3. It provides unstyled, logic-focused building blocks that handle accessibility, keyboard navigation, and state management while giving you complete control over styling. The library includes 70 composables covering everything from selection patterns to form validation, plus ready-to-use headless components like Dialog, Tabs, and ExpansionPanel. Get started instantly with `pnpm create vuetify0`. ??? How is Vuetify0 different from Vuetify? Vuetify is a full-featured Material Design component framework with opinionated styling, theming, and a complete design system. Vuetify0 is headless - components have no built-in styles. You bring your own CSS using Tailwind, UnoCSS, or plain CSS. Think of Vuetify0 as the foundation layer that Vuetify and other component libraries could be built on top of. ??? Do I need Vuetify to use Vuetify0? No. Vuetify0 is a standalone package with no dependency on Vuetify. You can use it in any Vue 3 project. ??? What styling approach should I use? Vuetify0 is style-agnostic - use whatever CSS approach you prefer. We recommend [UnoCSS](https://unocss.dev) for its speed and flexibility, but Tailwind CSS v4, plain CSS, CSS Modules, and CSS-in-JS solutions all work well. The theme plugin exposes CSS custom properties (`--v0-primary`, `--v0-surface`, etc.) that you can map to your framework's color system. All documentation examples use Tailwind-compatible utility classes. ??? Why use composables instead of components? Components are great for common UI patterns, but composables give you more flexibility. You can build custom components, combine behaviors, control the entire DOM structure, and import only the logic you need for smaller bundles. ??? Is Vuetify0 accessible? Yes. Components and composables implement WAI-ARIA patterns including proper ARIA attributes, roles, and states. Keyboard navigation is built-in - arrow keys for selection, Enter/Space for activation, Escape for dismissal. Focus management handles trapping focus in modals, restoring focus on close, and roving tabindex for composite widgets. The headless approach means you control the visual styling, but the accessibility logic is built-in. ??? Does Vuetify0 support SSR? Yes. All composables and components are SSR-safe and work with Nuxt, Vite SSR, and other server-rendering solutions. Use the `IN_BROWSER` constant to guard browser-only code, and the `useHydration` composable for hydration-aware rendering that prevents mismatches. Observer composables (`useResizeObserver`, `useIntersectionObserver`, etc.) automatically skip initialization on the server. ??? What browsers are supported? Vuetify0 targets modern evergreen browsers: Chrome 52+, Firefox 52+, Safari 10.1+, and Edge 79+. No IE11 support. Some cutting-edge features like CSS Anchor Positioning (Chrome/Edge 125+) and the native Popover API (Chrome 114+, Firefox 125+, Safari 17+) have more limited support but degrade gracefully. See [Browser Support](/introduction/browser-support) for detailed compatibility tables. ??? Can I use Vuetify0 with Nuxt? Yes. Create a Nuxt plugin that registers v0 plugins, add `@vuetify/v0` to `build.transpile` in your Nuxt config, and you're ready to go. Components and composables can be imported directly or configured for auto-imports. See [Getting Started](/introduction/getting-started#nuxt) for setup instructions and the [Nuxt Guide](/guide/integration/nuxt) for advanced configuration. ??? What version of Vue is required? Vue 3.5.0 or higher. Vuetify0 uses modern Vue features like `useId()` and improved reactivity that require Vue 3.5+. Node 22+ is recommended for development. ??? Is Vuetify0 tree-shakeable? Yes. Import only what you need and unused code is eliminated during bundling. Components and composables are individually exportable (`@vuetify/v0/components`, `@vuetify/v0/composables`), and the library has no side effects that prevent tree-shaking. ??? Does Vuetify0 support TypeScript? Yes. The library is written in TypeScript with full type definitions. Generic components preserve type inference, composables return properly typed refs and functions, and the plugin system is fully typed. No `@types` packages needed. ??? What are plugins in Vuetify0? Plugins provide app-wide functionality that composables can access via Vue's provide/inject. Core plugins include `createThemePlugin` (theming with CSS variables), `createLocalePlugin` (i18n), `createStoragePlugin` (persistent state), and `createLoggerPlugin` (debug logging). Additional plugins cover breakpoints, hydration, and permissions. Each plugin is optional - install only what you need. ??? How do I get started quickly? The fastest way is with the CLI: `pnpm create vuetify0`. This scaffolds a complete project with UnoCSS, theming, and example components pre-configured. For existing projects, install `@vuetify/v0` and start importing components and composables directly. See [Getting Started](/introduction/getting-started) for full instructions. ??? How do I contribute? See the [Contributing](/introduction/contributing) page for guidelines. The project is open source at [github.com/vuetifyjs/0](https://github.com/vuetifyjs/0). ??? Where can I get help? [GitHub Issues](https://github.com/vuetifyjs/0/issues) for bug reports and feature requests, or the [Vuetify Discord](https://community.vuetifyjs.com) community for real-time chat and questions. ::: ::: sponsor ::: --- # Getting Started URL: https://0.vuetifyjs.com/introduction/getting-started # Get started with Vuetify0 Vuetify0 provides headless UI primitives and composables for Vue 3. Components are unstyled and logic-focused, giving you complete control over styling while handling accessibility, keyboard navigation, and state management. ## Quick Start The fastest way to start a new Vuetify0 project is with the CLI. ### Vuetify Create Standalone scaffolding tool for Vuetify0 projects using [Vuetify Create](https://github.com/vuetifyjs/cli/tree/master/packages/create0). ::: code-group no-filename ```bash pnpm pnpm create vuetify0 ``` ```bash npm npm create vuetify0 ``` ```bash yarn yarn create vuetify0 ``` ```bash bun bun create vuetify0 ``` ::: ### Vuetify CLI Full-featured CLI with additional presets and options using [Vuetify CLI](/guide/tooling/vuetify-cli). ::: code-group no-filename ```bash pnpm pnpm dlx @vuetify/cli init --type vuetify0 ``` ```bash npm npx @vuetify/cli init --type vuetify0 ``` ```bash yarn yarn dlx @vuetify/cli init --type vuetify0 ``` ```bash bun bunx @vuetify/cli init --type vuetify0 ``` ::: Both options scaffold a complete project with UnoCSS, theming, and example components pre-configured — the same shape as [DevKey](/guide/integration/devkey), the reference v0 starter project. > [!TIP] > Use the Skill Filter to narrow down navigation to match your experience level: ## Manual Setup To add v0 to an existing project, follow the steps below. ### Create Vue Project ::: code-group no-filename ```bash pnpm pnpm create vue@latest ``` ```bash npm npm create vue@latest ``` ```bash yarn yarn create vue ``` ```bash bun bun create vue@latest ``` ::: ### Installation Install `@vuetify/v0` with your preferred package manager: ::: code-group no-filename ```bash pnpm pnpm add @vuetify/v0 ``` ```bash npm npm install @vuetify/v0 ``` ```bash yarn yarn add @vuetify/v0 ``` ```bash bun bun add @vuetify/v0 ``` ::: Create a plugin file to configure v0: ```ts src/plugins/vuetify0.ts import { createThemePlugin } from '@vuetify/v0' import type { App } from 'vue' export default function vuetify0 (app: App) { app.use( createThemePlugin({ default: 'light', themes: { light: { dark: false, colors: { primary: '#3b82f6', surface: '#ffffff', 'on-primary': '#ffffff', 'on-surface': '#212121', }, }, }, }), ) } ``` Register the plugin in your app entry: ```ts src/main.ts import { createApp } from 'vue' import App from './App.vue' import vuetify0 from './plugins/vuetify0' const app = createApp(App) vuetify0(app) app.mount('#app') ``` > [!NOTE] > For additional plugins, theming options, and advanced configuration, see the [Guide](/guide). ## Requirements - Vue 3.5.0 or higher - Node 22+ ## Usage Import and use components directly - no plugin installation required: ```vue QuickStart.vue playground no-filename ``` Components are completely unstyled. Add your own classes using Tailwind, UnoCSS, or plain CSS. ## Styling v0 is style-agnostic. Choose your preferred CSS framework and map theme colors to v0's CSS variables. ### UnoCSS [UnoCSS](https://unocss.dev) is our recommended choice for its speed and flexibility. #### 1. Install ::: code-group no-filename ```bash pnpm pnpm add -D unocss @unocss/preset-wind ``` ```bash npm npm install -D unocss @unocss/preset-wind ``` ```bash yarn yarn add -D unocss @unocss/preset-wind ``` ```bash bun bun add -D unocss @unocss/preset-wind ``` ::: #### 2. Configure ```ts uno.config.ts import { defineConfig, presetWind } from 'unocss' export default defineConfig({ presets: [presetWind()], theme: { colors: { primary: 'var(--v0-primary)', surface: 'var(--v0-surface)', 'on-primary': 'var(--v0-on-primary)', 'on-surface': 'var(--v0-on-surface)', // Add more theme colors as needed }, }, }) ``` #### 3. Add Vite Plugin ```ts vite.config.ts import UnoCSS from 'unocss/vite' export default defineConfig({ plugins: [ vue(), UnoCSS(), ], }) ``` #### 4. Import Styles ```ts src/main.ts import 'virtual:uno.css' ``` Now use utility classes in your components: ```vue ``` ### Tailwind CSS v4 [Tailwind v4](https://tailwindcss.com) uses CSS-first configuration with native cascade layers. #### 1. Install ::: code-group no-filename ```bash pnpm pnpm add -D tailwindcss @tailwindcss/vite ``` ```bash npm npm install -D tailwindcss @tailwindcss/vite ``` ```bash yarn yarn add -D tailwindcss @tailwindcss/vite ``` ```bash bun bun add -D tailwindcss @tailwindcss/vite ``` ::: #### 2. Add Vite Plugin ```ts vite.config.ts import tailwindcss from '@tailwindcss/vite' export default defineConfig({ plugins: [ vue(), tailwindcss(), ], }) ``` #### 3. Create Stylesheet ```css src/styles/main.css @import "tailwindcss"; @theme { --color-primary: var(--v0-primary); --color-surface: var(--v0-surface); --color-on-primary: var(--v0-on-primary); --color-on-surface: var(--v0-on-surface); } ``` #### 4. Import Styles ```ts src/main.ts import './styles/main.css' ``` Now use utility classes in your components: ```vue ``` ### CSS Modules Vue's built-in [CSS Modules](https://vuejs.org/api/sfc-css-features#css-modules) require zero configuration. ```vue Button.vue no-filename ``` Type-safe access via `useCssModule()`: ```vue ``` > [!TIP] > For dark mode, custom themes, and design tokens, see the [Theming Guide](/guide/features/theming). ## Nuxt v0 works with Nuxt via a standard plugin. ### 1. Create Plugin ```ts plugins/vuetify0.ts collapse import { createThemePlugin } from '@vuetify/v0' export default defineNuxtPlugin((nuxtApp) => { nuxtApp.vueApp.use( createThemePlugin({ default: 'light', themes: { light: { dark: false, colors: { primary: '#3b82f6', surface: '#ffffff', 'on-primary': '#ffffff', 'on-surface': '#212121', }, }, dark: { dark: true, colors: { primary: '#60a5fa', surface: '#1e1e1e', 'on-primary': '#1a1a1a', 'on-surface': '#e0e0e0', }, }, }, }), ) }) ``` ### 2. Configure Nuxt ```ts nuxt.config.ts export default defineNuxtConfig({ build: { transpile: ['@vuetify/v0'], }, }) ``` > [!TIP] > For auto-imports, SSR hydration, and theme persistence, see the [Nuxt Guide](/guide/integration/nuxt). ## Exposed Exports The following export paths exist for the Vuetify0 framework: | Name | Description | | ---- | ----------- | | `@vuetify/v0` | Main entry point exposing all components, composables, and utilities. | | `@vuetify/v0/components` | Components only. | | `@vuetify/v0/composables` | Composables only. | | `@vuetify/v0/utilities` | Utilities only. | | `@vuetify/v0/constants` | Constants only (not included in main entry). | ```ts // Everything import { ExpansionPanel, createSelection } from '@vuetify/v0' // Components only import { ExpansionPanel, Single, Group } from '@vuetify/v0/components' // Composables only import { createSelection, useTheme, createForm } from '@vuetify/v0/composables' // Utilities only import { isObject, isString } from '@vuetify/v0/utilities' // Constants only import { IN_BROWSER } from '@vuetify/v0/constants' ``` ## Next Steps Now that v0 is installed, choose your path: | Goal | Start Here | | - | - | | Understand the architecture | [Components](/guide/fundamentals/components) → [Composables](/guide/fundamentals/composables) → [Core](/guide/fundamentals/core) | | Build production UIs now | [Theming](/guide/features/theming) → [Accessibility](/guide/features/accessibility) | | Build a component library | [Building Frameworks](/guide/fundamentals/building-frameworks) | | Explore interactively | [Playground](/playground) | > [!TIP] > Use `Cmd+/` on any documentation page to ask AI questions about v0. ## Support v0 v0 is built and maintained in the open. If it's useful to you or your team, sponsoring funds the work and keeps it moving — and need a hand shipping? The [Services](/services) page covers direct support plans and fixed-scope project builds. ::: sponsor ::: > [!DISCORD] --- # License URL: https://0.vuetifyjs.com/introduction/license # License Vuetify0 is released under the [MIT License](https://opensource.org/licenses/MIT), one of the most permissive and widely-used open source licenses. ## What This Means The MIT License allows you to: - **Use** — Use Vuetify0 in personal and commercial projects - **Modify** — Modify the source code to fit your needs - **Distribute** — Distribute original or modified versions - **Sublicense** — Include in proprietary software **The only requirement** is that you include the original copyright notice and license text in any copy of the software or substantial portion of it. ## Full License Text ```text LICENSE.md collapse The MIT License (MIT) Copyright (c) 2016-now Vuetify, LLC Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ``` View the [LICENSE.md](https://github.com/vuetifyjs/0/blob/master/packages/0/README.md) on GitHub. > [!DISCORD] --- # Security Disclosure URL: https://0.vuetifyjs.com/introduction/security # Security Disclosure The Vuetify team takes security seriously. We appreciate your efforts to responsibly disclose vulnerabilities and will make every effort to acknowledge your contributions. ## Reporting a Vulnerability To report a security issue, email [security@vuetifyjs.com](mailto:security@vuetifyjs.com?subject=SECURITY) and include the word **"SECURITY"** in the subject line. **Please include:** - Description of the vulnerability - Steps to reproduce - Potential impact - Any suggested fixes (optional) ## What to Expect 1. **Initial Response** — We will acknowledge receipt within 48 hours 2. **Investigation** — We will investigate and keep you informed of progress 3. **Resolution** — We will prepare and release fixes as quickly as possible 4. **Credit** — We will credit you in the release notes (unless you prefer anonymity) ## Disclosure Policy When we receive a security report, we will: - Confirm the problem and determine affected versions - Audit code to find any similar issues - Prepare fixes for all maintained releases - Release fixes to npm as quickly as possible Internally, security incidents are handled according to a formal Incident Response Plan that defines severity classification, response timelines, and escalation procedures. ## Threat Model `@vuetify/v0` is a **client-side UI composables library**. It processes no secrets, manages no authentication, and communicates with no external services. This threat model uses the [STRIDE framework](https://en.wikipedia.org/wiki/STRIDE_(security)) to identify and mitigate threats across the project lifecycle. ### Assets | Asset | Impact if Compromised | |-------|----------------------| | npm package (`@vuetify/v0`) | Supply chain attack on all downstream consumers | | GitHub repository | Tampered source leads to tampered package | | CI/CD secrets (npm token, deploy tokens) | Unauthorized publish or deployment | | Documentation site | Defacement, phishing, malicious examples | | Consumer app security | Apps trust v0 to not introduce XSS or injection | ### Trust Boundaries ```mermaid "Trust Boundaries" flowchart LR Contributor -->|PR| GitHub GitHub -->|tag| CI CI -->|publish| npm npm -->|install| ConsumerApp[Consumer app] ConsumerApp -->|render| EndUser[End user] GitHub -->|push| CIDocs[CI] CIDocs -->|webhook| Deploy Deploy -->|serve| DocsSite[Docs site] ``` ### In Scope | Category | Threats | |----------|---------| | Supply chain | Compromised npm publish credentials, malicious dependency updates, build pipeline injection, typosquatting, mutable CI action references | | Consumer-facing | CSS injection via theme values, XSS through unsanitized slot/prop content, storage data exposure, CSP implications | | CI/CD | Fork PR code execution, preview package abuse, deploy webhook replay, secret leakage | | Contributor | Review bypass, social engineering via issues/PRs | | Infrastructure | Docs site compromise via deploy pipeline, availability | ### Out of Scope | Threat | Reason | |--------|--------| | XSS from user content | Consumer's responsibility to sanitize before passing to v0 | | Authentication / authorization | v0 has no auth layer | | Server-side code execution | v0 has no dynamic code execution; SSR rendering is stateless and read-only | | Issues in consumer applications | v0 controls only what it exports | ### Supply Chain Hardening These measures protect the integrity of `@vuetify/v0` from source to consumer: - **Lockfile committed** — `pnpm-lock.yaml` is version-controlled, ensuring reproducible installs - **Dependency cooldown** — pnpm's `minimumReleaseAge` (set in `pnpm-workspace.yaml`) enforces a release-age waiting period before a newly published dependency version can be installed, giving time for a compromised release to be detected and pulled. First-party Vuetify packages and the dev/build toolchain (linters, the bundler, CI preview tooling) are exempt via `minimumReleaseAgeExclude`, as they are either published by us or never ship in a consumer's runtime bundle - **Pinned CI actions** — Vuetify-owned GitHub Actions are pinned to commit SHAs, not mutable branch refs, preventing silent changes via force-push - **Scoped package** — published under the `@vuetify` npm org, reducing typosquatting risk - **No lifecycle scripts** — no `postinstall` or `preinstall` scripts that could execute arbitrary code at install time > [!IMPORTANT] > The current dependency release-age cooldown is **14 days** — a newly published version of any non-exempt dependency must be at least 14 days old before it can be installed. ### Security Properties These properties are verified in the codebase: - **No network requests** — v0 makes no HTTP calls; the Knock notification adapter is opt-in only - **No dynamic code evaluation** — no runtime code generation or arbitrary script execution - **Prototype pollution protection** — `mergeDeep` blocks `__proto__`, `constructor`, and `prototype` keys - **CSS injection protection** — Theme adapters validate theme names and color keys against a safe identifier pattern (`[a-zA-Z0-9_-]`), and reject color values containing dangerous CSS patterns. The browser adapter uses `adoptedStyleSheets` (no DOM parsing); SSR adapters use `innerHTML` on ` ``` ## Theme API ```ts const theme = useTheme() // Read current theme theme.selectedId.value // 'light' | 'dark' theme.selectedItem.value // Current theme ticket theme.isDark.value // boolean // Switch themes theme.cycle() // Cycle through themes theme.select('dark') // Select specific theme // Access theme values theme.colors.value // Map of all theme colors theme.colors.value[theme.selectedId.value] // Current theme's colors ``` ## CSS Variables v0 injects variables with the `--v0-` prefix: | Variable | Purpose | | - | - | | `--v0-primary` | Primary brand color | | `--v0-secondary` | Secondary color | | `--v0-background` | Page background | | `--v0-surface` | Card/component background | | `--v0-error` | Error state color | | `--v0-on-primary` | Text on primary background | | `--v0-on-surface` | Text on surface background | ### Using Variables ```css .btn-primary { background: var(--v0-primary); color: var(--v0-on-primary); } .card { background: var(--v0-surface); border: 1px solid var(--v0-divider); } ``` ### Tailwind CSS v4 Integration Map v0 variables to Tailwind theme colors in your CSS: ```css app.css @import "tailwindcss"; @theme { --color-primary: var(--v0-primary); --color-secondary: var(--v0-secondary); --color-background: var(--v0-background); --color-surface: var(--v0-surface); --color-error: var(--v0-error); --color-on-primary: var(--v0-on-primary); --color-on-surface: var(--v0-on-surface); } ``` Now use standard utilities: ```html
Card
``` Theme changes update automatically—the utilities reference CSS variables, not static values. ### UnoCSS Integration Map v0 variables to UnoCSS theme colors: ```ts uno.config.ts collapse import { defineConfig, presetUno } from 'unocss' export default defineConfig({ presets: [presetUno()], theme: { colors: { primary: 'var(--v0-primary)', secondary: 'var(--v0-secondary)', background: 'var(--v0-background)', surface: 'var(--v0-surface)', error: 'var(--v0-error)', 'on-primary': 'var(--v0-on-primary)', 'on-surface': 'var(--v0-on-surface)', }, }, }) ``` ## Dark Mode ### Manual Toggle ```vue playground ``` ### localStorage Persistence Use `useStorage` to persist theme selection across page loads. The key is installing the storage plugin first, then reading the stored preference during theme plugin setup: ```ts main.ts collapse import { createApp } from 'vue' import { createStoragePlugin, createThemePlugin, useStorage, IN_BROWSER, } from '@vuetify/v0' const app = createApp(App) // Install storage plugin first app.use(createStoragePlugin()) // Helper to resolve system preference function getSystemTheme(): 'light' | 'dark' { if (!IN_BROWSER) return 'light' return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light' } // Read stored preference using app context const storedTheme = app.runWithContext(() => useStorage().get('theme')) const initialTheme = storedTheme.value === 'dark' ? 'dark' : storedTheme.value === 'light' ? 'light' : getSystemTheme() app.use( createThemePlugin({ default: initialTheme, themes: { light: { dark: false, colors: { /* ... */ } }, dark: { dark: true, colors: { /* ... */ } }, }, }), ) ``` Then sync theme changes back to storage: ```vue ThemeToggle.vue ``` > [!TIP] > For SSR apps, use cookies instead of localStorage—see [Nuxt Theme Persistence](/guide/integration/nuxt#theme-persistence). ## Design Tokens with createTokens For fine-grained token management beyond colors: ```ts import { createTokens } from '@vuetify/v0' const tokens = createTokens() // Register tokens tokens.register({ id: 'spacing-sm', value: '0.5rem' }) tokens.register({ id: 'spacing-md', value: '1rem' }) tokens.register({ id: 'radius-sm', value: '4px' }) // Aliases use {path} syntax in the value tokens.register({ id: 'gap', value: '{spacing-md}' }) // Lookup resolves aliases tokens.resolve('gap') // '1rem' ``` ### Token Categories ```ts // Typography tokens.register({ id: 'font-body', value: 'Inter, sans-serif' }) tokens.register({ id: 'font-size-sm', value: '0.875rem' }) // Spacing tokens.register({ id: 'space-1', value: '0.25rem' }) tokens.register({ id: 'space-2', value: '0.5rem' }) // Shadows tokens.register({ id: 'shadow-sm', value: '0 1px 2px rgba(0,0,0,0.1)' }) ``` ## Scoped Themes Override theme for a subtree using the trinity pattern: ```ts import { createThemeContext, createTheme } from '@vuetify/v0' // Create a custom theme context with options const [useTheme, provideTheme] = createThemeContext({ default: 'custom', themes: { custom: { dark: false, colors: { primary: '#E91E63', // Pink instead of blue secondary: '#9C27B0', }, }, }, }) // Provide to descendants provideTheme() ``` ## Best Practices ### 1. Semantic Color Names ```ts // Good - semantic themes: { light: { colors: { primary: '#1976D2', error: '#B00020' } }, } // Avoid - literal themes: { light: { colors: { blue: '#1976D2', red: '#B00020' } }, } ``` ### 2. On-* Contrast Colors Always define contrast colors for accessibility: ```ts themes: { light: { colors: { primary: '#1976D2', 'on-primary': '#FFFFFF', // White text on primary surface: '#FFFFFF', 'on-surface': '#212121', // Dark text on surface }, }, } ``` ### 3. CSS Fallbacks ```css .card { /* Fallback for missing variable */ background: var(--v0-surface, #ffffff); } ``` --- # Types URL: https://0.vuetifyjs.com/guide/features/types # Types Shared TypeScript types used across the library and available for your own code. ```ts import type { ID, Activation, DeepPartial, Extensible, MaybeArray } from '@vuetify/v0' ``` ## ID Identifier type used throughout the registry system. All tickets, items, and registrable entities use this for their `id` property. ```ts type ID = string | number ``` ```ts const id: ID = 'item-1' // string const id: ID = 42 // number ``` ## Activation Keyboard activation mode for navigable components. ```ts type Activation = 'automatic' | 'manual' ``` - **automatic** — selection follows focus. Arrow keys select immediately. This is the WAI-ARIA standard for radio groups. - **manual** — arrow keys move focus only. Enter or Space is required to select. Use for toolbars or when deliberate selection is preferred. ```vue ``` ## DeepPartial Recursively makes all properties of `T` optional. Used by `mergeDeep` to type partial source objects. ```ts type DeepPartial = T extends object ? { [P in keyof T]?: DeepPartial; } : T ``` ```ts type User = { name: string; address: { city: string } } type PartialUser = DeepPartial // { name?: string; address?: { city?: string } } ``` ## Extensible Preserves string literal autocomplete while allowing arbitrary strings. TypeScript normally collapses `'a' | 'b' | string` into just `string`, losing IDE autocomplete. This type uses the `string & {}` trick to prevent that collapse. ```ts type Extensible = T | (string & {}) ``` Use for extensible APIs where you want autocomplete for known values but still accept custom strings — event names, theme tokens, CSS classes, etc. ```ts type Color = 'red' | 'blue' | 'green' // WITHOUT Extensible — autocomplete lost type BadColor = Color | string // collapses to just `string` // WITH Extensible — autocomplete preserved type GoodColor = Extensible function setColor(c: GoodColor) {} setColor('red') // autocomplete suggests 'red' | 'blue' | 'green' setColor('purple') // also OK — custom value accepted ``` ## MaybeArray Union type that accepts either a single value or an array. Use for APIs that accept both forms. ```ts type MaybeArray = T | T[] ``` ```ts function process(input: MaybeArray) { ... } process('single') // OK process(['a', 'b', 'c']) // OK ``` ## DOMElement Valid element types for Vue's `h()` render function. Includes HTML tag names, component definitions, and functional components. Used by the `Atom` component for polymorphic rendering. ```ts type DOMElement = Parameters[0] ``` ## UnknownObject Object with string keys and unknown values for generic record handling. Requires type narrowing at the use site, which catches more bugs at compile time than `Record`. ```ts type UnknownObject = Record ``` --- # Utilities Guide URL: https://0.vuetifyjs.com/guide/features/utilities # Utilities Standalone helpers for common UI patterns. These composables don't depend on context or plugins—use them anywhere. ## Overview | Utility | Purpose | | - | - | | [createFilter](/composables/data/create-filter) | Filter arrays with search queries | | [createPagination](/composables/data/create-pagination) | Page navigation state | | [createVirtual](/composables/data/create-virtual) | Virtual scrolling for large lists | | [createOverflow](/composables/semantic/create-overflow) | Compute visible item capacity | > [!TIP] > These utilities are standalone—they don't require plugins or context. Use them anywhere, including outside Vue components. ## createFilter Filter arrays based on search queries: ```ts import { createFilter } from '@vuetify/v0' const items = ref(['Apple', 'Banana', 'Cherry']) const query = ref('') const filter = createFilter() const { items: filtered } = filter.apply(query, items) query.value = 'an' filtered.value // ['Banana'] ``` ### With Object Keys ```ts const users = ref([ { name: 'Alice', email: 'alice@example.com' }, { name: 'Bob', email: 'bob@example.com' }, ]) const filter = createFilter({ keys: ['name', 'email'], }) const query = ref('alice') const { items: filtered } = filter.apply(query, users) filtered.value // [{ name: 'Alice', ... }] ``` ### Filter Modes ```ts const filter = createFilter({ mode: 'intersection', // 'some' | 'every' | 'union' | 'intersection' keys: ['name', 'tags'], }) ``` ## createPagination Pagination state management: ```ts import { createPagination } from '@vuetify/v0' const pagination = createPagination({ size: 100, itemsPerPage: 10, }) pagination.page.value // 1 pagination.pages // 10 pagination.isFirst.value // true pagination.isLast.value // false pagination.next() // Go to page 2 pagination.prev() // Go to page 1 pagination.select(5) // Go to page 5 pagination.first() // Go to page 1 pagination.last() // Go to page 10 ``` ### With Reactive Size ```ts const items = ref([...]) const pagination = createPagination({ size: () => items.value.length, itemsPerPage: 20, }) ``` ### Page Items ```ts pagination.items.value // [{ type: 'page', value: 1 }, { type: 'page', value: 2 }, ...] ``` ## createVirtual Virtual scrolling for large datasets: ```ts import { createVirtual } from '@vuetify/v0' const items = ref(Array.from({ length: 10000 }, (_, i) => `Item ${i}`)) // createVirtual takes items as first arg, options as second const virtual = createVirtual(items, { itemHeight: 40, }) ``` ```vue playground VirtualList.vue ``` ### Variable Height ```ts const virtual = createVirtual(items, { height: 400, // Container height (or use element ref) }) ``` ## createOverflow Compute how many items fit in a container: ```ts import { createOverflow } from '@vuetify/v0' import { useTemplateRef } from 'vue' const containerRef = useTemplateRef('container') const overflow = createOverflow({ container: containerRef, itemWidth: 100, gap: 8, }) overflow.capacity.value // Number of items that fit overflow.isOverflowing.value // Boolean: items exceed capacity ``` ### Use Case: Responsive Chips ```vue playground ResponsiveChips.vue ``` ## Transformers Value transformation utilities: ### toArray Normalize any value to an array: ```ts import { toArray } from '@vuetify/v0' toArray('single') // ['single'] toArray(['array']) // ['array'] toArray(null) // [] toArray(undefined) // [] ``` ### toReactive Convert ref objects to reactive proxies: ```ts import { toReactive } from '@vuetify/v0' const configRef = ref({ debug: false }) // Unwraps the ref and returns a reactive object const config = toReactive(configRef) config.debug // Reactive access ``` ## Color Color manipulation utilities for hex and RGB formats. ### hexToRgb / rgbToHex Convert between hex strings and `RGB` objects: ```ts import { hexToRgb, rgbToHex } from '@vuetify/v0' hexToRgb('#1976d2') // { r: 25, g: 118, b: 210 } hexToRgb('#fff') // { r: 255, g: 255, b: 255 } rgbToHex({ r: 25, g: 118, b: 210 }) // '#1976d2' ``` The `RGB` interface is available for typing: ```ts import type { RGB } from '@vuetify/v0' const color: RGB = { r: 255, g: 0, b: 0 } ``` ### APCA Contrast [APCA](https://github.com/Myndex/SAPC-APCA) (Advanced Perceptual Contrast Algorithm) for accessible color pairings. Returns a signed contrast value — higher magnitude means more contrast: ```ts import { apca, foreground, hexToRgb } from '@vuetify/v0' // Raw APCA contrast between two colors const text = hexToRgb('#1a1a1a') const bg = hexToRgb('#ffffff') apca(text, bg) // ~-106 (high contrast, dark on light) // Pick black or white text for a background foreground('#1976d2') // '#ffffff' (white reads better on this blue) foreground('#ffd600') // '#000000' (black reads better on yellow) ``` > [!TIP] > `foreground` is ideal for computing on-color text in design tokens. It uses APCA to select the higher-contrast option. ## Best Practices ### Combine Utilities ```ts // Filter + Paginate const query = ref('') const filter = createFilter() const { items: filtered } = filter.apply(query, items) const pagination = createPagination({ size: () => filtered.value.length, itemsPerPage: 10, }) const displayedItems = computed(() => { const start = pagination.pageStart.value const end = pagination.pageStop.value return filtered.value.slice(start, end) }) ``` ### Virtual + Filter ```ts const query = ref('') const filter = createFilter() const { items: filtered } = filter.apply(query, items) const virtual = createVirtual(filtered, { itemHeight: 40, }) ``` ### Filter + Pagination + Virtual For large datasets that need all three utilities working together: ```ts collapse import { shallowRef, computed } from 'vue' import { createFilter, createPagination, createVirtual } from '@vuetify/v0' // Source data const items = shallowRef(Array.from({ length: 10000 }, (_, i) => ({ id: i, name: `Item ${i}`, }))) // 1. Filter first const query = shallowRef('') const filter = createFilter({ keys: ['name'] }) const { items: filtered } = filter.apply(query, items) // 2. Paginate the filtered results const pagination = createPagination({ size: () => filtered.value.length, itemsPerPage: 100, }) // 3. Get current page slice const pageItems = computed(() => { const start = pagination.pageStart.value const end = pagination.pageStop.value return filtered.value.slice(start, end) }) // 4. Virtual scroll the current page const virtual = createVirtual(pageItems, { itemHeight: 40 }) ``` ```vue ``` ```mermaid "Data Pipeline" flowchart LR A[All Items] --> B[Filtered] B --> C[Paginated] C --> D[Virtualized] ``` Each layer is reactive—changing the search query refilters, which updates pagination, which updates the virtual list. ## Type Guards Type-narrowing functions that replace raw `typeof` / `===` checks. All are tree-shakeable. ```ts import { isString, isNullOrUndefined, isObject } from '@vuetify/v0' if (isString(value)) { // value is string } if (!isNullOrUndefined(x)) { // x is defined } ``` | Guard | Narrows to | | - | - | | `isFunction(x)` | `Function` | | `isString(x)` | `string` | | `isNumber(x)` | `number` (includes NaN) | | `isBoolean(x)` | `boolean` | | `isObject(x)` | `Record` (excludes null and arrays) | | `isArray(x)` | `unknown[]` | | `isElement(x)` | `Element` | | `isNull(x)` | `null` | | `isUndefined(x)` | `undefined` | | `isNullOrUndefined(x)` | `null \| undefined` | | `isPrimitive(x)` | `string \| number \| boolean` | | `isSymbol(x)` | `symbol` | | `isNaN(x)` | `number` (only the NaN value)[^isnan-vs-global] | [^isnan-vs-global]: `isNaN` here uses `Number.isNaN()` internally — it does not coerce strings like the global `isNaN()`. `isNaN("foo")` returns `false`, not `true`. > [!TIP] > Prefer these over raw comparisons. ## Helpers General-purpose utilities for numbers, arrays, and objects. ### clamp Clamp a number between a minimum and maximum: ```ts import { clamp } from '@vuetify/v0' clamp(15, 0, 10) // 10 clamp(-5, 0, 10) // 0 clamp(5, 0, 10) // 5 clamp(0.5) // 0.5 (defaults: min=0, max=1) ``` ### range Create an array of sequential numbers: ```ts import { range } from '@vuetify/v0' range(5) // [0, 1, 2, 3, 4] range(3, 1) // [1, 2, 3] range(5, 10) // [10, 11, 12, 13, 14] range(0) // [] ``` ### mergeDeep Deep-merge objects without mutating inputs. Arrays are replaced, not concatenated: ```ts import { mergeDeep } from '@vuetify/v0' mergeDeep({ a: { b: 1 } }, { a: { c: 2 } }) // { a: { b: 1, c: 2 } } mergeDeep({}, { a: 1 }, { b: 2 }) // { a: 1, b: 2 } mergeDeep({ arr: [1, 2] }, { arr: [3] }) // { arr: [3] } ``` ### useId SSR-safe unique ID generation. Uses Vue's `useId()` inside components, falls back to a counter outside: ```ts import { useId } from '@vuetify/v0' // In component setup const id = useId() // 'v:0' (Vue's format) // Outside component const id = useId() // 'v0-0', 'v0-1', ... ``` --- # Benchmarks URL: https://0.vuetifyjs.com/guide/fundamentals/benchmarks # Benchmarks v0 maintains performance benchmarks for all core composables. This page explains what gets benchmarked, how to interpret metrics, and what the performance tiers mean. ## Why Benchmark Headless UI libraries must be fast—they're foundational infrastructure. v0 benchmarks exist to: 1. **Catch regressions** — CI fails if performance drops 2. **Guide optimization** — Data-driven decisions, not guesses 3. **Set expectations** — Users know what to expect at scale 4. **Validate minimal reactivity** — Prove the tradeoffs are worth it ## What Gets Benchmarked ### Core Composables | Composable | Why It's Benchmarked | | - | - | | `createRegistry` | Foundation for all collections—performance here affects everything | | `createModel` | Value store underlying all selection—selection benchmarks depend on it | | `createSelection` | Base for all selection patterns—select, toggle, mandatory, batch | | `createNested` | Hierarchical trees with cascade—tree traversal scales with depth | | `createTokens` | Design tokens can grow large—alias resolution must scale | | `createFilter` | Search/filter on large datasets must remain responsive | | `createVirtual` | Virtual scrolling is performance-critical by definition | | `createDataTable` | Composed orchestrator—measures sorting, filtering, and pagination together | | `useDate` | Date operations are frequent in UIs | | `useProxyRegistry` | Reactive proxy for templates—shows reactivity overhead vs raw registry | ### Operation Categories Each benchmark file covers multiple operation types: | Category | Fixture Type | What It Measures | | - | - | - | | Initialization | Fresh | Setup/creation cost | | Lookup operations | Shared | Single item access (O(1) expected) | | Mutation operations | Fresh | Updates and modifications | | Batch operations | Fresh | Bulk actions (onboard, offboard) | | Computed access | Shared | Cached/derived value reads | | Seek operations | Shared | Directional search | **Shared fixtures** reuse the same data structure across iterations—safe for read-only operations. **Fresh fixtures** create new data per iteration—required for mutations to get accurate measurements. ## Performance Tiers
Blazing
O(1): ≥100,000 ops/s
O(n): ≥10,000 ops/s
O(n²): ≥1,000 ops/s
Fast
O(1): ≥10,000 ops/s
O(n): ≥1,000 ops/s
O(n²): ≥100 ops/s
Good
O(1): ≥1,000 ops/s
O(n): ≥100 ops/s
O(n²): ≥10 ops/s
Slow
O(1): <1,000 ops/s
O(n): <100 ops/s
O(n²): <10 ops/s
Each benchmark is assigned a tier based on its throughput and detected complexity. Group tiers are the average of their individual benchmark tiers. ### Complexity Detection Tiers adjust based on detected algorithmic complexity: | Pattern in Benchmark Name | Complexity | | - | - | | "single" or "one item/query/key" | O(1) | | Number + items/objects/entries/elements | O(n) — default for most benchmarks | | "nested" or "recursive" | O(n²) | | No pattern matched | O(n) — conservative fallback | ### Reading Results ```bash ✓ createRegistry/index.bench.ts lookup operations ✓ Get by id (1,000 items) 1,234,567 ops/s ✓ Get by id (10,000 items) 1,198,432 ops/s ``` - **ops/s** — Operations per second (higher is better) - **Consistent across sizes** — O(1) complexity confirmed - **10x data, ~same speed** — Good scaling behavior ## Dataset Sizes Benchmarks test multiple sizes to reveal complexity: | Size | Items | Purpose | | - | - | - | | Medium | 1,000 | Baseline measurement | | Large | 10,000 | Reveals O(n) vs O(1) | | Small | 100 | Optional edge case | | Stress | 100,000 | Optional stress test | If a 10,000-item benchmark is 10x slower than 1,000-item, the operation is O(n). If it's roughly the same speed, it's O(1). ## Running Benchmarks ```bash # Run all benchmarks pnpm test:bench # Run specific file pnpm vitest bench packages/0/src/composables/createRegistry/index.bench.ts # Generate metrics report pnpm metrics ``` ## Interpreting for Your Use Case ::: faq single ??? Is this fast enough for my app? Most v0 composables handle 10,000+ items at interactive speeds (>60fps). For typical UIs: - **<100 items** — Instant, no optimization needed - **100-1,000 items** — Smooth, standard usage - **1,000-10,000 items** — Consider virtual scrolling - **10,000+ items** — Use `createVirtual`, paginate, or filter ??? Should I use events or polling? - **Events** — Real-time updates, notifications, debugging - **Polling (`values()`)** — Periodic snapshots, non-critical freshness - **`useProxyRegistry`** — Template-bound lists that must stay current Events add minimal overhead when enabled. Benchmarks show raw operation cost; event emission adds ~1-5% overhead. ??? Why minimal reactivity instead of full reactivity? Vue's reactivity system is powerful but not free. Each reactive wrapper adds: - Memory for dependency tracking - CPU cycles for change detection - Potential for unnecessary re-renders By keeping reactivity minimal, v0 composables stay predictable—you know exactly what triggers updates. When you need reactivity, opt in explicitly with `useProxyRegistry`. ??? How do I compare raw vs reactive performance? Compare `createRegistry` benchmarks with `useProxyRegistry` to see the reactivity overhead. It's worth it when needed, but shouldn't be the default. ::: ## Explorer Browse all benchmark results. Select a composable to filter, or expand groups to compare individual operations. ## Contributing Benchmarks New composables should include benchmarks if they: - Manage collections (registries, arrays, maps) - Perform search/filter operations - Have user-perceived latency (loading, transitions) - Are called frequently (every render, every keystroke) See [createRegistry benchmarks](https://github.com/vuetifyjs/0/blob/master/packages/0/src/composables/createRegistry/index.bench.ts) for the canonical example. --- # Building Frameworks URL: https://0.vuetifyjs.com/guide/fundamentals/building-frameworks # Building Frameworks When building a component framework, you're often reimplementing the same patterns: selection state, keyboard navigation, form validation, and focus management. v0 provides these behaviors as headless primitives so you can focus on what makes your framework unique—its design language. > [!NOTE] Prerequisites > This guide builds on concepts from [Core](/guide/fundamentals/core), [Components](/guide/fundamentals/components), and [Composables](/guide/fundamentals/composables). Read those first if you're new to v0. ## Getting Started ### New Project Use `vuetify0 create` to scaffold a new project with v0 pre-configured: ::: code-group no-filename ```bash pnpm pnpm create vuetify0 ``` ```bash npm npm create vuetify0 ``` ```bash yarn yarn create vuetify0 ``` ```bash bun bun create vuetify0 ``` ::: ### Existing Project Add v0 to an existing Vue project: ::: code-group no-filename ```bash pnpm pnpm add @vuetify/v0 ``` ```bash npm npm install @vuetify/v0 ``` ```bash yarn yarn add @vuetify/v0 ``` ```bash bun bun add @vuetify/v0 ``` ::: v0 uses [subpath exports](https://nodejs.org/api/packages.html#subpath-exports) for tree-shaking: ```ts // Import everything import { createSingle, Atom } from '@vuetify/v0' // Or import specific modules import { createSingle } from '@vuetify/v0/composables' import { Atom } from '@vuetify/v0/components' import type { ID } from '@vuetify/v0/types' import { isObject } from '@vuetify/v0/utilities' import { IN_BROWSER } from '@vuetify/v0/constants' ``` ## Two Integration Patterns This guide covers two approaches: | Pattern | When to Use | | - | - | | **Pattern A: Behavior-Focused** | Need complex state (selection, navigation, validation) with full rendering control | | **Pattern B: Component Wrappers** | Building styled components on top of v0's headless primitives | > [!TIP] > v0's composables are completely headless—they manage state and behavior without any DOM assumptions. This makes them ideal for building design systems that need complete control over markup and styling. ## Core Concepts ### Direct vs Context APIs v0 composables offer two API surfaces. The direct API creates standalone instances—perfect for component-local state. The context API uses Vue's [provide/inject](https://vuejs.org/guide/components/provide-inject.html) for sharing state across component trees. ```ts // Direct instance (component-local) const tabs = createSingle({ mandatory: true }) // Context trinity (for dependency injection) const [useTabs, provideTabs, defaultTabs] = createSingleContext() ``` > [!TIP] > Start with the direct API. Only reach for contexts when you need to share state between parent and child components that can't communicate via props. ### The Ticket System When you register items with a v0 registry, you get back "tickets"—plain objects that contain reactive properties and bound methods. Properties like `isSelected` are Vue refs that update automatically when selection state changes. ```ts tabs.register({ id: 'home', value: 'Home' }) const ticket = tabs.get('home') ticket.id // 'home' (static) ticket.value // 'Home' (static) ticket.index // 0 (static, position in registry) ticket.isSelected // Ref - reactive! ticket.isSelected.value // true/false - access the value ticket.toggle() // Toggle selection ticket.select() // Select this item ``` ### Template Iteration Registry internals aren't directly exposed for template iteration. `useProxyRegistry` wraps a registry and provides reactive arrays that update when items change: ```vue playground ``` > [!TIP] > The `events: true` option is required. `useProxyRegistry` listens for registry events to invalidate its cache. ## Pattern A: Behavior-Focused Use v0's composables directly when you need complex state management—selection, navigation, validation—but want full control over rendering. ::: example /guide/building-frameworks/pattern-a/tabs-basic ### Behavior-Focused Tabs Custom tabs using `createSingle` and `useProxyRegistry` with ARIA attributes and accessible keyboard navigation. ::: ### Adding Keyboard Navigation v0 composables handle state; you add the interaction layer: ::: example /guide/building-frameworks/pattern-a/tabs-keyboard ### Keyboard Navigation Arrow key, Home, and End key support added to the tabs above. ::: ### Multi-Selection with Groups Use `createGroup` for checkbox-style multi-selection with tri-state support: ::: example /guide/building-frameworks/pattern-a/accordion ### Multi-Select Accordion Accordion with expand/collapse-all using `createGroup` and tri-state support. ::: > [!ASKAI] How does building a Tabs UI manually with createSingle and useProxyRegistry compare to v0's built-in Tabs component? ## Pattern B: Component Wrappers Wrap v0's headless components with your design system's styling. v0 handles behavior, accessibility, and keyboard navigation—you control the visual presentation. ### Polymorphic Elements The `Atom` component provides polymorphic rendering via the `as` prop—render as any HTML element while keeping your component's API consistent: ::: example /guide/building-frameworks/pattern-b/button ### Polymorphic Button An Atom-based button with BEM class names and style/size/color variants. ::: > [!TIP] > The example uses [BEM naming](https://getbem.com/)—a convention for organizing CSS in component libraries. Blocks (`.my-button`), elements (`__icon`), and modifiers (`--filled`, `--primary`) create predictable, collision-free class names. ### Styling Headless Components Wrap v0's compound components with custom CSS. The components expose data attributes like `data-state` for styling different states: ::: example /guide/building-frameworks/pattern-b/card ### Styled ExpansionPanel ExpansionPanel wrapped with custom CSS targeting data attributes and slot props for visual state. ::: ### Form Components v0's form components handle focus, keyboard interaction, and ARIA attributes. Apply your styles via classes: ::: example /guide/building-frameworks/pattern-b/input ### Styled Checkbox Checkbox wrapped with label and description, styled via data attributes for checked and disabled states. ::: ## Plugin Architecture v0's plugins follow Vue's [plugin pattern](https://vuejs.org/guide/reusability/plugins.html) with additional structure for namespaced context provision. ::: example /guide/building-frameworks/plugins/setup ### Plugin Access Reading and toggling theme and breakpoint state anywhere in the component tree via `useTheme` and `useBreakpoints`. ::: > [!TIP] > v0 plugins are designed to be order-independent. Each plugin gracefully handles missing dependencies by providing sensible fallbacks. ### Creating Custom Plugins Use `createPluginContext` to build your own plugins without boilerplate. It returns the standard `[createContext, createPlugin, useContext]` triple: ```ts import { createPluginContext } from '@vuetify/v0' interface ThemeOptions { namespace?: string primary?: string } interface ThemeContext { primary: string setPrimary: (color: string) => void } function createThemeFeature (options: Omit): ThemeContext { const primary = shallowRef(options.primary ?? '#1976d2') return { primary, setPrimary: (color) => { primary.value = color } } } export const [createMyThemeContext, createMyThemePlugin, useMyTheme] = createPluginContext( 'my-ui:theme', options => createThemeFeature(options), ) // In main.ts app.use(createMyThemePlugin({ primary: '#e91e63' })) // In any component const { primary, setPrimary } = useMyTheme() ``` See [createPlugin](/composables/foundation/create-plugin) for the full API including `persist`/`restore` lifecycle hooks for saving and rehydrating plugin state. ## SSR Safety v0 is designed for universal rendering. Use the provided constants and composables to guard browser-only code: ```ts import { useHydration, useWindowEventListener } from '@vuetify/v0' // SSR-safe event listener (no-op on server, auto-cleanup) useWindowEventListener('resize', handler) // Reactive hydration state for conditional rendering const { isHydrated } = useHydration() // In templates: v-if="isHydrated" ``` The `isHydrated` shallowRef is `false` during SSR and becomes `true` after the root component mounts. This prevents hydration mismatches when rendering browser-dependent content. ::: example /guide/building-frameworks/ssr/hydration-guard ### SSR Hydration Guard Using `useHydration()` and `IN_BROWSER` to safely defer browser-only rendering until after hydration. ::: ## TypeScript Patterns v0 uses generics extensively. When extending composables, provide your custom ticket and context types: ::: example /guide/building-frameworks/typescript/type-extension ### Type Extension Extending `SelectionTicket` and `SelectionContext` with custom properties for type-safe file selection. ::: Vue's [shallowReactive](https://vuejs.org/api/reactivity-advanced.html#shallowreactive) and [computed](https://vuejs.org/api/reactivity-core.html#computed) are used internally—understanding these helps when debugging reactivity issues. ## Complete Example: @example/my-ui To see everything come together, we've included a complete example library that demonstrates the patterns in this guide. ::: example /guide/building-frameworks/my-ui/demo ### Complete Design System MyButton, MyTabs, and MyAccordion assembled from v0 primitives — all patterns from this guide working together. ::: The example package includes: ``` my-ui/ ├── package.json # Peer deps on @vuetify/v0 and vue ├── vite.config.ts # Library build config ├── src/ │ ├── index.ts # Public exports │ ├── plugin.ts # Vue plugin with v0 setup │ └── components/ │ ├── MyButton.vue # Atom wrapper (polymorphic) │ ├── MyTabs.vue # createSingle + keyboard nav │ └── MyAccordion.vue # ExpansionPanel wrapper ``` View the full source in the [examples directory](https://github.com/vuetifyjs/0/tree/master/apps/docs/src/examples/guide/building-frameworks/my-ui). --- # Components Guide URL: https://0.vuetifyjs.com/guide/fundamentals/components # Components v0 components are Vue wrappers around composables. Composables hold logic, components provide Vue integration via slots, props, and emits. ## Component Philosophy - **Headless**: Zero styling - you own all CSS - **Slot-driven**: All customization through scoped slots - **Accessible**: ARIA attributes via `attrs` object - **Composable-backed**: Use components or composables directly > [!TIP] > Always spread the `attrs` object onto your elements. It contains ARIA attributes and data attributes for accessibility and styling. ## Component Categories | Category | Purpose | Examples | | - | - | - | | **Primitives** | Base building blocks | [Atom](/components/primitives/atom) | | **Providers** | Pure state management, no DOM | [Selection](/components/providers/selection), [Single](/components/providers/single), [Group](/components/providers/group), [Step](/components/providers/step) | | **Forms** | Form controls with accessibility | [Checkbox](/components/forms/checkbox) | | **Semantic** | Meaningful HTML defaults | [Avatar](/components/semantic/avatar), [Pagination](/components/semantic/pagination) | | **Disclosure** | Show/hide patterns | [Dialog](/components/disclosure/dialog), [ExpansionPanel](/components/disclosure/expansion-panel), [Popover](/components/disclosure/popover) | ## Atom: The Foundation The `Atom` component is a polymorphic base element supporting any HTML tag: ```vue ``` ### Rendering Modes | Mode | Usage | Output | | - | - | - | | Element | `as="button"` | `