Quickstart
Get started with Vuetify Zero by building a functional checkbox component in just a few minutes.
Prerequisites
Before starting, make sure you have:
Set up a Vue 3 project
You can use Vite, Nuxt, or any Vue 3.5+ setup: npm create vite@latest my-app -- --template vue-ts
cd my-app
npm install
Your First Component
Let’s build a functional checkbox component to understand Vuetify Zero’s core concepts.
Step 1: Import the Checkbox
Vuetify Zero uses compound components with sub-components for maximum flexibility:
< script setup lang = "ts" >
import { Checkbox } from '@vuetify/v0'
import { ref } from 'vue'
const agreed = ref ( false )
</ script >
< template >
< div class = "container" >
< h1 > My First Vuetify Zero Component </ h1 >
< label class = "checkbox-label" >
< Checkbox.Root v-model = " agreed " class = "checkbox" >
< Checkbox.Indicator class = "indicator" >
✓
</ Checkbox.Indicator >
</ Checkbox.Root >
< span > I agree to the terms and conditions </ span >
</ label >
< p v-if = " agreed " class = "success" >
Thank you for agreeing!
</ p >
</ div >
</ template >
Step 2: Add Styling
Vuetify Zero is headless—you control all styling. Add CSS to style your checkbox:
< style scoped >
.container {
max-width : 600 px ;
margin : 2 rem auto ;
padding : 2 rem ;
font-family : system-ui , sans-serif ;
}
h1 {
font-size : 2 rem ;
margin-bottom : 1.5 rem ;
color : #333 ;
}
.checkbox-label {
display : inline-flex ;
align-items : center ;
gap : 0.5 rem ;
cursor : pointer ;
font-size : 1 rem ;
}
.checkbox {
width : 1.25 rem ;
height : 1.25 rem ;
border : 2 px solid #ddd ;
border-radius : 0.25 rem ;
display : inline-flex ;
align-items : center ;
justify-content : center ;
transition : all 0.2 s ;
}
.checkbox [ data-state = "checked" ] {
background : #3b82f6 ;
border-color : #3b82f6 ;
}
.indicator {
color : white ;
font-size : 0.875 rem ;
opacity : 0 ;
transition : opacity 0.2 s ;
}
.checkbox [ data-state = "checked" ] .indicator {
opacity : 1 ;
}
.success {
margin-top : 1 rem ;
padding : 0.75 rem ;
background : #dcfce7 ;
border : 1 px solid #86efac ;
border-radius : 0.375 rem ;
color : #166534 ;
}
</ style >
Step 3: Run Your App
Start your development server:
You should see a styled, functional checkbox that:
Responds to clicks
Updates the agreed ref
Shows a success message when checked
Has smooth transitions
Notice the data-state attribute? Vuetify Zero components expose state through data attributes for easy styling.
Understanding the Component
Compound Components
Vuetify Zero uses compound components for flexibility:
< Checkbox . Root > <!-- Container with logic -->
<Checkbox.Indicator> <!-- Visual indicator -->
✓
</Checkbox.Indicator>
</Checkbox.Root>
Each sub-component has a specific role:
Checkbox.Root - Manages state and accessibility
Checkbox.Indicator - Visual indicator (only shown when checked)
Checkbox.HiddenInput - Native input for forms (auto-rendered when name prop is set)
Data Attributes
Components expose state through data-* attributes:
/* Unchecked state */
.checkbox [ data-state = "unchecked" ] { }
/* Checked state */
.checkbox [ data-state = "checked" ] { }
/* Indeterminate state */
.checkbox [ data-state = "indeterminate" ] { }
/* Disabled state */
.checkbox [ data-disabled ] { }
v-model Support
All Vuetify Zero components support v-model for two-way binding:
< script setup >
const checked = ref ( false )
</ script >
< template >
< Checkbox.Root v-model = " checked " >
<!-- Will automatically sync with checked ref -->
</ Checkbox.Root >
</ template >
Try More Components
Dialog Component
Create an accessible modal dialog:
< script setup lang = "ts" >
import { Dialog } from '@vuetify/v0'
</ script >
< template >
< Dialog.Root >
< Dialog.Activator class = "btn-primary" >
Open Dialog
</ Dialog.Activator >
< Dialog.Content class = "dialog" >
< Dialog.Title class = "dialog-title" >
Confirm Action
</ Dialog.Title >
< Dialog.Description class = "dialog-description" >
Are you sure you want to proceed?
</ Dialog.Description >
< div class = "dialog-actions" >
< Dialog.Close class = "btn-secondary" >
Cancel
</ Dialog.Close >
< Dialog.Close class = "btn-primary" >
Confirm
</ Dialog.Close >
</ div >
</ Dialog.Content >
</ Dialog.Root >
</ template >
< style scoped >
.btn-primary {
padding : 0.5 rem 1 rem ;
background : #3b82f6 ;
color : white ;
border : none ;
border-radius : 0.375 rem ;
font-size : 0.875 rem ;
font-weight : 500 ;
cursor : pointer ;
}
.dialog {
margin : auto ;
padding : 1.5 rem ;
background : white ;
border : 1 px solid #e5e7eb ;
border-radius : 0.5 rem ;
max-width : 28 rem ;
width : 100 % ;
}
.dialog-title {
font-size : 1.25 rem ;
font-weight : 600 ;
margin-bottom : 0.5 rem ;
}
.dialog-description {
color : #6b7280 ;
margin-bottom : 1.5 rem ;
}
.dialog-actions {
display : flex ;
gap : 0.75 rem ;
justify-content : flex-end ;
}
.btn-secondary {
padding : 0.5 rem 1 rem ;
background : white ;
color : #374151 ;
border : 1 px solid #e5e7eb ;
border-radius : 0.375 rem ;
font-size : 0.875 rem ;
cursor : pointer ;
}
</ style >
Using Composables
Vuetify Zero composables let you build custom logic:
< script setup lang = "ts" >
import { createSelection } from '@vuetify/v0'
import { ref } from 'vue'
interface Task {
id : string
label : string
}
const selection = createSelection < Task >({
multiple: true ,
mandatory: false
})
const tasks = [
{ id: '1' , label: 'Write documentation' },
{ id: '2' , label: 'Review pull requests' },
{ id: '3' , label: 'Update dependencies' },
]
// Register tasks with selection
tasks . forEach ( task => {
selection . register ({ id: task . id , value: task })
})
function deleteSelected () {
const selected = Array . from ( selection . selectedValues . value )
console . log ( 'Deleting:' , selected )
selection . unselectAll ()
}
</ script >
< template >
< div class = "task-manager" >
< h2 > Task Manager </ h2 >
< div class = "task-list" >
< label
v-for = " task in tasks "
: key = " task . id "
class = "task-item"
>
< input
type = "checkbox"
: checked = " selection . selectedIds . has ( task . id ) "
@ change = " selection . toggle ( task . id ) "
>
{{ task . label }}
</ label >
</ div >
< button
v-if = " selection . selectedIds . size > 0 "
class = "delete-btn"
@ click = " deleteSelected "
>
Delete {{ selection . selectedIds . size }} selected
</ button >
</ div >
</ template >
< style scoped >
.task-manager {
max-width : 32 rem ;
margin : 2 rem auto ;
padding : 1.5 rem ;
border : 1 px solid #e5e7eb ;
border-radius : 0.5 rem ;
}
h2 {
margin-bottom : 1 rem ;
font-size : 1.5 rem ;
}
.task-list {
display : flex ;
flex-direction : column ;
gap : 0.75 rem ;
margin-bottom : 1 rem ;
}
.task-item {
display : flex ;
align-items : center ;
gap : 0.5 rem ;
padding : 0.75 rem ;
border : 1 px solid #e5e7eb ;
border-radius : 0.375 rem ;
cursor : pointer ;
}
.delete-btn {
width : 100 % ;
padding : 0.75 rem ;
background : #ef4444 ;
color : white ;
border : none ;
border-radius : 0.375 rem ;
font-weight : 500 ;
cursor : pointer ;
}
</ style >
Key Concepts
Headless Components No imposed styling—you have complete control over appearance using CSS, Tailwind, UnoCSS, or any styling solution.
Compound Components Components are split into sub-components (Root, Item, Content, etc.) for maximum flexibility and composition.
Accessibility Built-in ARIA attributes, keyboard navigation, and focus management are handled automatically following WAI-ARIA best practices.
TypeScript Support Full type safety with generics, interfaces, and comprehensive type definitions for all components and composables.
Common Patterns
Slot Props
Access component state through slot props:
< Dialog . Root v-slot = " { isOpen , open , close } " >
<button @click="open">Open</button>
<p v-if="isOpen">Dialog is open</p>
<button @click="close">Close</button>
</Dialog.Root>
Custom Styling with Data Attributes
Style based on component state:
/* Style based on selection state */
.item [ data-selected ] {
background : blue ;
color : white ;
}
/* Style based on disabled state */
.item [ data-disabled ] {
opacity : 0.5 ;
cursor : not-allowed ;
}
/* Style based on checkbox state */
.checkbox [ data-state = "indeterminate" ] .indicator {
background : gray ;
}
Use the name prop for automatic form integration:
< form @ submit . prevent = " handleSubmit " >
<Checkbox.Root
v-model="agreed"
name="terms"
value="accepted"
>
<Checkbox.Indicator>✓</Checkbox.Indicator>
</Checkbox.Root>
<!-- Hidden input automatically rendered -->
<button type="submit">Submit</button>
</ form >
When you provide a name prop, Vuetify Zero automatically renders a hidden input for native form submission.
Next Steps
Now that you understand the basics, explore more:
Components Explore all 18 headless components with examples
Composables Learn about 40+ composables for building custom logic
Styling Guide Master styling patterns with CSS, Tailwind, and more
API Reference Dive deep into component props, slots, and events