Switch
A headless switch component with dual-mode support: standalone boolean binding or group multi-selection with tri-state.
Usage
The Switch component supports two modes:
Standalone mode: Use
v-modelonSwitch.Rootfor simple boolean stateGroup mode: Wrap in
Switch.Groupfor multi-selection with array v-model
<script setup lang="ts">
import { Switch } from '@vuetify/v0'
import { shallowRef } from 'vue'
const enabled = shallowRef(false)
</script>
<template>
<label class="inline-flex items-center gap-3 cursor-pointer">
<Switch.Root
v-model="enabled"
class="inline-flex items-center border-none bg-transparent p-0 outline-none"
>
<Switch.Track
class="relative inline-flex items-center w-11 h-6 rounded-full bg-gray-300 transition-colors data-[state=checked]:bg-primary"
>
<Switch.Thumb
class="![visibility:visible] block size-4 rounded-full bg-white shadow-sm transition-transform translate-x-1 data-[state=checked]:translate-x-6"
/>
</Switch.Track>
</Switch.Root>
<span>Enable notifications</span>
</label>
</template>
Anatomy
<script setup lang="ts">
import { Switch } from '@vuetify/v0'
</script>
<template>
<!-- Standalone -->
<Switch.Root>
<Switch.Track>
<Switch.Thumb />
</Switch.Track>
</Switch.Root>
<!-- Group -->
<Switch.Group>
<Switch.Root>
<Switch.Track>
<Switch.Thumb />
</Switch.Track>
</Switch.Root>
<Switch.Root>
<Switch.Track>
<Switch.Thumb />
</Switch.Track>
</Switch.Root>
</Switch.Group>
<!-- Group with Select All -->
<Switch.Group>
<Switch.SelectAll>
<Switch.Track>
<Switch.Thumb />
</Switch.Track>
</Switch.SelectAll>
<Switch.Root>
<Switch.Track>
<Switch.Thumb />
</Switch.Track>
</Switch.Root>
</Switch.Group>
<!-- With form submission -->
<Switch.Root>
<Switch.Track>
<Switch.Thumb />
</Switch.Track>
<Switch.HiddenInput />
</Switch.Root>
</template>Accessibility
The Switch.Root component renders as a button and handles all ARIA attributes automatically:
role="switch"for proper semanticsaria-checkedreflects state (true,false, or"mixed")aria-disabledwhen switch is disabledaria-labelfrom thelabelproptabindex="0"for keyboard focus (removed when disabled)Space key toggles the switch
For custom implementations, use renderless mode and bind the attrs slot prop to your element:
<template>
<Switch.Root v-slot="{ attrs }" renderless>
<div v-bind="attrs">
<!-- Custom switch visual -->
</div>
</Switch.Root>
</template>Recipes
Group Mode
Wrap switches in Switch.Group for multi-selection with array-based v-model:
Selected: none
<script setup lang="ts">
import { Switch } from '@vuetify/v0'
import { ref } from 'vue'
const selected = ref<string[]>([])
const options = [
{ value: 'wifi', label: 'Wi-Fi' },
{ value: 'bluetooth', label: 'Bluetooth' },
{ value: 'location', label: 'Location services' },
]
</script>
<template>
<Switch.Group v-model="selected" class="flex flex-col gap-3" label="Connectivity">
<label
v-for="option in options"
:key="option.value"
class="inline-flex items-center justify-between gap-4 cursor-pointer"
>
<span>{{ option.label }}</span>
<Switch.Root
class="inline-flex items-center border-none bg-transparent p-0 outline-none"
:value="option.value"
>
<Switch.Track
class="relative inline-flex items-center w-11 h-6 rounded-full bg-gray-300 transition-colors data-[state=checked]:bg-primary"
>
<Switch.Thumb
class="![visibility:visible] block size-4 rounded-full bg-white shadow-sm transition-transform translate-x-1 data-[state=checked]:translate-x-6"
/>
</Switch.Track>
</Switch.Root>
</label>
</Switch.Group>
<p class="mt-4 text-sm text-on-surface-variant">
Selected: {{ selected.join(', ') || 'none' }}
</p>
</template>
Form Integration
When the name prop is provided on Switch.Root, a hidden native checkbox is automatically rendered for form submission:
<template>
<!-- Auto-renders hidden input for form submission -->
<Switch.Root name="notifications" value="on">
<Switch.Track>
<Switch.Thumb />
</Switch.Track>
</Switch.Root>
</template>For custom form integration, use Switch.HiddenInput explicitly:
<template>
<Switch.Root>
<Switch.Track>
<Switch.Thumb />
</Switch.Track>
<Switch.HiddenInput name="custom" value="override" />
</Switch.Root>
</template>Indeterminate State
Use Switch.SelectAll within a group for “select all” patterns. It automatically reflects the group’s aggregate state and toggles all items on click:
Selected: none
<script setup lang="ts">
import { Switch } from '@vuetify/v0'
import { ref } from 'vue'
const selected = ref<string[]>([])
const options = [
{ value: 'camera', label: 'Camera' },
{ value: 'microphone', label: 'Microphone' },
{ value: 'notifications', label: 'Notifications' },
]
</script>
<template>
<Switch.Group v-model="selected" label="Permissions">
<div class="flex flex-col gap-3">
<label class="inline-flex items-center justify-between gap-4 cursor-pointer font-medium">
<span>All permissions</span>
<Switch.SelectAll
class="inline-flex items-center border-none bg-transparent p-0 outline-none"
label="Toggle all"
>
<Switch.Track
class="relative inline-flex items-center w-11 h-6 rounded-full bg-gray-300 transition-colors data-[state=checked]:bg-primary data-[state=indeterminate]:bg-gray-400"
>
<Switch.Thumb
class="![visibility:visible] block size-4 rounded-full bg-white shadow-sm transition-transform translate-x-1 data-[state=checked]:translate-x-6 data-[state=indeterminate]:translate-x-3.5"
/>
</Switch.Track>
</Switch.SelectAll>
</label>
<div class="ml-6 flex flex-col gap-3">
<label
v-for="option in options"
:key="option.value"
class="inline-flex items-center justify-between gap-4 cursor-pointer"
>
<span>{{ option.label }}</span>
<Switch.Root
class="inline-flex items-center border-none bg-transparent p-0 outline-none"
:value="option.value"
>
<Switch.Track
class="relative inline-flex items-center w-11 h-6 rounded-full bg-gray-300 transition-colors data-[state=checked]:bg-primary"
>
<Switch.Thumb
class="![visibility:visible] block size-4 rounded-full bg-white shadow-sm transition-transform translate-x-1 data-[state=checked]:translate-x-6"
/>
</Switch.Track>
</Switch.Root>
</label>
</div>
</div>
</Switch.Group>
<p class="mt-4 text-sm text-on-surface-variant">
Selected: {{ selected.join(', ') || 'none' }}
</p>
</template>
The SelectAll component:
Binds to the group’s
isAllSelectedandisMixedstateCalls
toggleAllon clickDoes NOT register as a group item
Sets
aria-checked="mixed"anddata-state="indeterminate"when partially selected
Styling with Data Attributes
Switch components expose data-state attributes for CSS styling:
| Attribute | Values | Components |
|---|---|---|
data-state | checked, unchecked, indeterminate | Root, Track, Thumb |
data-disabled | true | Root |
<template>
<Switch.Root class="...">
<Switch.Track class="bg-gray-300 data-[state=checked]:bg-primary">
<Switch.Thumb class="translate-x-0.5 data-[state=checked]:translate-x-5.5" />
</Switch.Track>
</Switch.Root>
</template>Switch.Root
Props
namespace
stringNamespace for context provision to children (Track, Thumb, HiddenInput)
Default: "v0:switch:root"
modelValue
booleanEvents
update:model-value
[value: boolean]Slots
default
SwitchRootSlotProps<V>Switch.Group
Props
mandatory
boolean | "force"Require at least one switch to be on. `'force'` prevents deselecting the last item
Default: false
modelValue
T | T[]Events
update:model-value
[value: T | T[]]Slots
default
SwitchGroupSlotPropsSwitch.HiddenInput
Props
Switch.SelectAll
Props
Slots
default
SwitchSelectAllSlotPropsSwitch.Thumb
Props
Slots
default
SwitchThumbSlotPropsSwitch.Track
Props
Slots
default
SwitchTrackSlotProps