Skip to main content
You are viewing Pre-Alpha documentation.
Vuetify0 Logo
Mode
Accessibility
Vuetify

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.

Edit this page
Report a Bug
Copy Page as Markdown
IntermediateJan 14, 2026

Getting Started

New Project

Use vuetify0 create to scaffold a new project with v0 pre-configured:

pnpm
pnpm create vuetify0

Existing Project

Add v0 to an existing Vue project:

bash
pnpm add @vuetify/v0

v0 uses 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:

PatternWhen to Use
Pattern A: Behavior-FocusedNeed complex state (selection, navigation, validation) with full rendering control
Pattern B: Component WrappersBuilding 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↗ 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”—reactive objects with built-in methods. This follows Vue’s composables pattern↗ where behavior is encapsulated alongside state.

ts
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 item

Template Iteration

Registry collections are Maps, which don’t iterate well in Vue templates. Use useProxyRegistry to transform them into reactive iterables:

ts
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:

Ask AI
Want to see how this compares to using v0's built-in components?

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.

Tip

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:

ts
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 wrapper

View 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


© 2016-1970 Vuetify, LLC
Ctrl+/