--- name: vuetify0 description: Build with @vuetify/v0 headless composables and components. Use when creating v0-based UIs, working with Selection/Registry/Step patterns, or integrating headless primitives. license: MIT metadata: author: vuetify version: "1.0.0" tags: - vue - composables - headless - ui --- # Vuetify0 Skill > **@vuetify/v0** — Headless composables and components for Vue 3. ## When to Use This Skill Use v0 whenever you need: - **Selection state** — single, multi, grouped, or stepped selection - **Form validation** — async rules, field-level errors - **Shared context** — type-safe provide/inject patterns - **Collection management** — registries with lifecycle events - **Accessible UI** — WAI-ARIA compliant headless components - **Browser utilities** — SSR-safe detection, observers, event handling ## Quick Reference ```ts // Import patterns - always use package imports, never relative paths import { ... } from '@vuetify/v0' // Everything import { ... } from '@vuetify/v0/composables' // Composables only import { ... } from '@vuetify/v0/components' // Components only import { ... } from '@vuetify/v0/utilities' // Utilities only import { ... } from '@vuetify/v0/types' // Types only import { ... } from '@vuetify/v0/constants' // Constants only ``` --- ## Code Style ### Prefer `shallowRef` Over `ref` ```ts // ❌ Bad - unnecessary deep reactivity const user = ref({ name: 'John' }) const items = ref([1, 2, 3]) const open = ref(false) // ✅ Good - shallow by default const user = shallowRef({ name: 'John' }) const items = shallowRef([1, 2, 3]) const open = shallowRef(false) ``` ### Prefer Single-Word Names ```ts // ❌ Bad const isMenuOpen = shallowRef(false) const selectedItems = shallowRef([]) function handleClick () {} // ✅ Good const open = shallowRef(false) const selected = shallowRef([]) function click () {} ``` ### Use Function Declarations ```ts // ❌ Bad const toggle = () => open.value = !open.value // ✅ Good function toggle () { open.value = !open.value } ``` --- ## Decision Tree Before writing custom logic, check: | Need | Use | |------|-----| | Single item selection | `createSingle` | | Multi-item selection | `createSelection` or `createGroup` | | Selection with "select all" | `createGroup` (tri-state support) | | Step wizard / carousel | `createStep` | | Tree / nested items | `createNested` | | Form validation | `createForm` | | Shared state (provide/inject) | `createContext` or `createTrinity` | | Collection tracking | `createRegistry` | | SSR check | `IN_BROWSER` | | Type guards | `isString`, `isNumber`, `isObject`, etc. | --- ## Composables ### Selection Patterns #### createSingle — Single Selection ```ts import { createSingle } from '@vuetify/v0' const selector = createSingle({ mandatory: 'force' }) selector.register({ id: 'light', value: 'light' }) selector.register({ id: 'dark', value: 'dark' }) selector.selectedId // Ref selector.selectedValue // Ref selector.select('dark') ``` #### createSelection — Multi Selection ```ts import { createSelection } from '@vuetify/v0' const selection = createSelection({ multiple: true }) selection.register({ id: 'a', value: 'A' }) selection.register({ id: 'b', value: 'B' }) selection.toggle('a') selection.selectedIds // Set selection.isSelected('a') // boolean ``` #### createGroup — Multi Selection with Tri-State ```ts import { createGroup } from '@vuetify/v0' const group = createGroup() group.selectAll() group.toggleAll() group.isMixed // true when partially selected group.isAllSelected ``` #### createStep — Sequential Navigation ```ts import { createStep } from '@vuetify/v0' const stepper = createStep({ circular: true }) stepper.register({ id: 'step1' }) stepper.register({ id: 'step2' }) stepper.register({ id: 'step3' }) stepper.next() stepper.prev() stepper.first() stepper.last() ``` #### createNested — Hierarchical Selection ```ts import { createNested } from '@vuetify/v0' const tree = createNested() tree.register({ id: 'parent', value: 'Parent' }) tree.register({ id: 'child', value: 'Child', parent: 'parent' }) tree.getPath('child') // ['parent', 'child'] tree.getDescendants('parent') // ['child'] ``` ### Form Handling #### createForm — Validation ```ts import { createForm } from '@vuetify/v0' const form = createForm() const email = form.register({ id: 'email', value: '', rules: [ v => !!v || 'Required', v => /.+@.+/.test(v) || 'Invalid email', async v => await checkAvailable(v) || 'Email taken' ] }) email.isValid // true | false | null (pending) email.errors // string[] email.validate() form.submit() // Validates all fields ``` ### Context & State Sharing #### createContext — Type-Safe Provide/Inject ```ts import { createContext } from '@vuetify/v0' interface ThemeContext { mode: Ref toggle: () => void } const [useTheme, provideTheme] = createContext('Theme') // Provider provideTheme({ mode, toggle }) // Consumer (throws helpful error if not provided) const { mode, toggle } = useTheme() ``` #### createTrinity — Context with Defaults ```ts import { createTrinity } from '@vuetify/v0' const [useConfig, provideConfig, defaultConfig] = createTrinity('Config', { theme: 'light', locale: 'en' }) ``` ### Collection Management #### createRegistry — Item Lifecycle ```ts import { createRegistry } from '@vuetify/v0' const registry = createRegistry<{ id: string; label: string }>() registry.register({ id: 'item1', label: 'Item 1' }) registry.unregister('item1') registry.items // Map registry.ids // string[] ``` ### Data Utilities #### useFilter — Array Filtering ```ts import { useFilter } from '@vuetify/v0' const { apply } = useFilter({ keys: ['name', 'email'] }) const query = shallowRef('') const users = shallowRef([...]) const filtered = apply(query, users) ``` #### usePagination ```ts import { usePagination } from '@vuetify/v0' const pagination = usePagination({ page: 1, itemsPerPage: 10, length: 100 }) pagination.next() pagination.prev() pagination.first() pagination.last() ``` #### useVirtual — Virtual Scrolling ```ts import { useVirtual } from '@vuetify/v0' const { virtualItems, totalHeight, scrollTo } = useVirtual({ items: largeList, itemHeight: 48 }) ``` #### createTimeline — Undo/Redo ```ts import { createTimeline } from '@vuetify/v0' const timeline = createTimeline({ maxSize: 50 }) timeline.push(state) timeline.undo() timeline.redo() timeline.canUndo timeline.canRedo ``` #### createQueue — FIFO with Timeouts ```ts import { createQueue } from '@vuetify/v0' const notifications = createQueue({ timeout: 5000 }) notifications.push({ message: 'Saved!' }) // Auto-removes after 5 seconds ``` ### Browser & Environment #### Constants ```ts import { IN_BROWSER, SUPPORTS_TOUCH, SUPPORTS_OBSERVER, SUPPORTS_INTERSECTION_OBSERVER } from '@vuetify/v0/constants' if (IN_BROWSER && SUPPORTS_OBSERVER) { // Safe to use ResizeObserver } ``` #### useBreakpoints ```ts import { useBreakpoints } from '@vuetify/v0' const { xs, sm, md, lg, xl, xxl, smAndUp, mdAndDown } = useBreakpoints() ``` #### useMediaQuery ```ts import { useMediaQuery } from '@vuetify/v0' const { matches } = useMediaQuery('(prefers-color-scheme: dark)') ``` ### DOM Observation #### useResizeObserver ```ts import { useResizeObserver } from '@vuetify/v0' const el = shallowRef() const { width, height, pause, resume } = useResizeObserver(el) ``` #### useIntersectionObserver ```ts import { useIntersectionObserver } from '@vuetify/v0' const el = shallowRef() const { isIntersecting } = useIntersectionObserver(el, { threshold: 0.1 }) ``` #### useMutationObserver ```ts import { useMutationObserver } from '@vuetify/v0' useMutationObserver(el, callback, { childList: true, subtree: true }) ``` ### Event Handling #### useEventListener ```ts import { useEventListener } from '@vuetify/v0' useEventListener(window, 'resize', onResize) // Auto-cleanup on unmount ``` #### useHotkey ```ts import { useHotkey } from '@vuetify/v0' useHotkey('ctrl+k', openCommandPalette) useHotkey('g-h', navigateHome) // Sequence: g then h ``` #### useClickOutside ```ts import { useClickOutside } from '@vuetify/v0' useClickOutside(menuRef, close, { touchThreshold: 500 }) ``` --- ## Headless Components All components are unstyled, accessible, and follow WAI-ARIA patterns. ### Tabs ```vue ``` ### Dialog ```vue ``` ### Checkbox ```vue ``` ### Radio ```vue ``` ### Available Components | Component | Purpose | |-----------|---------| | `Atom` | Polymorphic element (render as any tag) | | `Avatar` | Image with fallback | | `Checkbox` | Checkbox control (standalone or group) | | `Dialog` | Modal overlay with focus trap | | `ExpansionPanel` | Accordion (single or multi-expand) | | `Group` | Multi-selection container | | `Pagination` | Page navigation | | `Popover` | Toggle overlay (CSS anchor positioning) | | `Radio` | Radio buttons with roving tabindex | | `Selection` | Generic selection container | | `Single` | Single selection container | | `Step` | Stepper/wizard | | `Tabs` | Tab navigation | --- ## Utility Functions ```ts import { mergeDeep, clamp, range, debounce, useId } from '@vuetify/v0/utilities' // Deep merge (prototype pollution safe) const config = mergeDeep({}, defaults, overrides) // Clamp number to range const clamped = clamp(value, 0, 100) // Create number array const nums = range(5) // [0, 1, 2, 3, 4] const nums2 = range(5, 1) // [1, 2, 3, 4, 5] // Debounce with controls const search = debounce(query, 300) search.clear() search.immediate() // SSR-safe unique ID const id = useId() ``` ## Type Guards ```ts import { isString, isNumber, isBoolean, isObject, isArray, isFunction, isNull, isUndefined, isNullOrUndefined, isPrimitive, isSymbol, isNaN } from '@vuetify/v0/utilities' if (isObject(value)) { // value is Record } ``` --- ## Plugins For app-wide features: ```ts import { createThemePlugin, createFeaturesPlugin, createLoggerPlugin, createLocalePlugin, createBreakpointsPlugin, createDatePlugin, createStoragePlugin } from '@vuetify/v0' // In main.ts app.use(createThemePlugin({ themes: { light: { colors: { primary: '#3b82f6' } }, dark: { colors: { primary: '#60a5fa' } } } })) // In components const { current, toggle } = useTheme() ``` --- ## Anti-Patterns ### ❌ Don't write custom selection logic ```ts // Bad const selected = shallowRef([]) function toggle (id: string) { const idx = selected.value.indexOf(id) if (idx >= 0) selected.value.splice(idx, 1) else selected.value.push(id) } ``` ### ✅ Use createSelection ```ts // Good const selection = createSelection({ multiple: true }) selection.toggle(id) ``` ### ❌ Don't write custom provide/inject ```ts // Bad provide('theme', theme) const theme = inject('theme') // Could be undefined! ``` ### ✅ Use createContext ```ts // Good const [useTheme, provideTheme] = createContext('Theme') ``` ### ❌ Don't write SSR checks manually ```ts // Bad if (typeof window !== 'undefined') ``` ### ✅ Use v0 constants ```ts // Good import { IN_BROWSER } from '@vuetify/v0/constants' if (IN_BROWSER) ``` --- ## Composition Hierarchy ``` Foundation (no dependencies) ├── createContext → Basic DI ├── createPlugin → Vue plugin factory └── createTrinity → [use, provide, default] pattern Registry (uses Foundation) └── createRegistry → Collection management Selection (uses Registry) ├── createSelection → Multi-select (base) ├── createSingle → Single-select ├── createGroup → Multi + tri-state ├── createStep → Sequential navigation └── createNested → Hierarchical Forms (uses Registry) ├── createForm → Validation ├── createTokens → Design tokens ├── createQueue → FIFO with timeout └── createTimeline → Undo/redo ``` --- ## Summary **Always check v0 first.** The goal is to never reinvent what v0 already provides. | Need | Use | |------|-----| | Selection | `createSelection`, `createSingle`, `createGroup`, `createStep` | | Forms | `createForm` | | Context | `createContext`, `createTrinity` | | Collections | `createRegistry` | | Browser detection | `IN_BROWSER`, `SUPPORTS_*` | | Type checks | `isString`, `isNumber`, `isObject`, etc. | | Accessible UI | Headless components | | Deep merge | `mergeDeep` | | Unique IDs | `useId` | | Debouncing | `debounce` | | Clamping | `clamp` | | Ranges | `range` | | DOM observation | `useResizeObserver`, `useIntersectionObserver` | | Events | `useEventListener`, `useHotkey`, `useClickOutside` | | Filtering | `useFilter` | | Pagination | `usePagination` | | Virtual lists | `useVirtual` | --- ## Resources - **Docs**: https://0.vuetifyjs.com - **Source**: https://github.com/vuetifyjs/0 - **Discord**: https://discord.gg/vuetify