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.
Getting Started
New Project
Use vuetify0 create to scaffold a new project with v0 pre-configured:
pnpm create vuetify0npm create vuetify0bun create vuetify0Existing Project
Add v0 to an existing Vue project:
pnpm add @vuetify/v0v0 uses subpath exports↗ for tree-shaking:
// 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 |
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↗ for sharing state across component trees.
// Direct instance (component-local)
const tabs = createSingle({ mandatory: true })
// Context trinity (for dependency injection)
const [useTabs, provideTabs, defaultTabs] = createSingleContext()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”—reactive objects with built-in methods. This follows Vue’s composables pattern↗ where behavior is encapsulated alongside state.
tabs.register({ id: 'home', value: 'Home' })
const ticket = tabs.get('home')
ticket.id // 'home'
ticket.value // 'Home'
ticket.index // 0
ticket.isSelected // Ref<boolean>
ticket.toggle() // Toggle selection
ticket.select() // Select this itemTemplate Iteration
Registry collections are Maps, which don’t iterate well in Vue templates. Use useProxyRegistry to transform them into reactive iterables:
import { createSingle, useProxyRegistry } from '@vuetify/v0/composables'
const tabs = createSingle({ mandatory: 'force' })
const proxy = useProxyRegistry(tabs)
// Now iterate in template: v-for="tab in proxy.values"Pattern A: Behavior-Focused
Use v0’s composables directly when you need complex state management—selection, navigation, validation—but want full control over rendering.
Adding Keyboard Navigation
v0 composables handle state; you add the interaction layer:
Multi-Selection with Groups
Use createGroup for checkbox-style multi-selection with tri-state support:
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:
Styling Headless Components
Wrap v0’s compound components with custom CSS. The components expose data attributes like data-state for styling different states:
Form Components
v0’s form components handle focus, keyboard interaction, and ARIA attributes. Apply your styles via classes:
Plugin Architecture
v0’s plugins follow Vue’s plugin pattern↗ with additional structure for namespaced context provision.
v0 plugins are designed to be order-independent. Each plugin gracefully handles missing dependencies by providing sensible fallbacks.
SSR Safety
v0 is designed for universal rendering. Use the provided constants and composables to guard browser-only code:
import { IN_BROWSER } from '@vuetify/v0/constants'
import { useHydration } from '@vuetify/v0/composables'
// Static check for browser environment
if (IN_BROWSER) {
window.addEventListener('resize', handler)
}
// Reactive hydration state
const { isHydrated } = useHydration()
// In templates: v-if="isHydrated"The isHydrated ref is false during SSR and becomes true after the root component mounts. This prevents hydration mismatches when rendering browser-dependent content.
TypeScript Patterns
v0 uses generics extensively. When extending composables, provide your custom ticket and context types:
Vue’s shallowReactive↗ and 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.
The example package includes:
my-ui/
├── package.json # Peer deps on @vuetify/v0 and vue
├── 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 wrapperView the full source in the examples directory↗.
Next Steps
Explore createSingle for single-selection patterns
Learn about createGroup for multi-selection with tri-state
See createStep for wizard/stepper navigation
Review Atom for polymorphic rendering
