Skip to main content
Vuetify0 is now a release candidate!
Vuetify0 Logo
Theme
Mode
Palettes
Accessibility
Vuetify One
Sign in to Vuetify One

Access premium tools across the Vuetify ecosystem — Bin, Play, Studio, and more.

Not a subscriber? See what's included

Switch

A switch for on/off state or multi-selection groups with tri-state support.

Usage

<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-surface-variant transition-colors data-[state=checked]:bg-primary"
      >
        <Switch.Thumb
          class="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

vue
<script setup lang="ts">
  import { Switch } from '@vuetify/v0'
</script>

<template>
  <Switch.Root>
    <Switch.Track>
      <Switch.Thumb />
    </Switch.Track>

    <Switch.HiddenInput />
  </Switch.Root>

  <Switch.Group>
    <Switch.SelectAll>
      <Switch.Track>
        <Switch.Thumb />
      </Switch.Track>
    </Switch.SelectAll>

    <Switch.Root>
      <Switch.Track>
        <Switch.Thumb />
      </Switch.Track>
    </Switch.Root>
  </Switch.Group>
</template>

Examples

App Settings Panel

A notification preferences panel that groups four independent switches under a master “Enable all” lever and submits the result through a Form. Each Switch.Root carries a name prop, so v0 auto-renders a hidden native input and the toggled values participate in FormData — there is no Switch.HiddenInput to place by hand. The panel pairs each switch with a label and a line of helper text, and a summary line below echoes the saved selection.

The interesting piece is the master toggle. Switch.SelectAll is not a group item — it never registers a value into the array v-model. Instead it reads the group’s aggregate isAllSelected / isMixed state and calls toggleAll, so it renders checked when every setting is on, unchecked when all are off, and indeterminate (aria-checked="mixed", data-state="indeterminate") when only some are on. The tri-state and batch operations come straight from createGroup — the same multi-selection logic that powers Group. The thumb slides between the off and on positions via data-[state=...] transform variants on Switch.Thumb, which stays visible in every state.

Reach for this pattern for any settings surface — notification preferences, feature flags, privacy controls — where a list of independent on/off toggles needs a single shared model, a select-all lever, and form submission. Because Form’s @submit is pass-through (it fires on every native submit regardless of validity), the handler guards on payload.valid before committing the saved state. See Form for the validation surface and Checkbox for the equivalent committed-selection control.

FileRole
useSettings.tsComposable — setting definitions, the enabled-array model, and the guarded save/reset logic
SettingsPanel.vueReusable component — renders the Form, the Switch.Group with a SelectAll master and per-row label + helper text, owns the UnoCSS classes
settings-panel.vueEntry — wires the composable to the panel and renders the saved-state summary line

Toggle settings and press Save to capture the current selection.

Recipes

Form Integration

Pass the name prop on Switch.Root and a hidden native <input type="checkbox"> is rendered automatically — no Switch.HiddenInput placement is required. The input is visually hidden, inert, and tabindex="-1", so it only participates in FormData submission:

vue
<template>
  <Switch.Root name="notifications" value="on">
    <Switch.Track>
      <Switch.Thumb />
    </Switch.Track>
  </Switch.Root>
</template>

Switch.HiddenInput is exported as an internal building block for custom layouts, but auto-rendering via name is the only supported form integration path — placing Switch.HiddenInput as a child of a Switch.Root that already has a name will produce two hidden inputs.

Styling with Data Attributes

Switch subcomponents expose data attributes for CSS styling without conditional classes. Switch.Root and Switch.SelectAll emit both data-state and data-disabled, while Switch.Track and Switch.Thumb emit only data-state (they inherit disabled styling from the Root ancestor):

AttributeValuesComponents
data-statechecked, unchecked, indeterminateRoot, SelectAll, Track, Thumb
data-disabledtrueRoot, SelectAll
vue
<template>
  <Switch.Root class="data-[disabled]:opacity-50">
    <Switch.Track class="bg-gray-300 transition-colors data-[state=checked]:bg-primary">
      <Switch.Thumb />
    </Switch.Track>
  </Switch.Root>
</template>

Switch.Thumb stays visible in every state and emits data-state (checked / unchecked / indeterminate), so a sliding transform can animate directly between the off and on positions — for example translate-x-1 data-[state=checked]:translate-x-6. Pair it with a transition-transform on the thumb and a transition-colors on the Switch.Track for the rail.

Accessibility

The Switch.Root component renders as a button and handles all ARIA attributes automatically:

  • role="switch" for proper semantics

  • aria-checked reflects state (true, false, or "mixed")

  • aria-disabled when switch is disabled

  • aria-label from the label prop

  • tabindex="0" for keyboard focus (removed when disabled)

  • Space key toggles the switch (Enter works when rendered as button)

For custom implementations, use renderless mode and bind the attrs slot prop to your element:

vue
<template>
  <Switch.Root v-slot="{ attrs }" renderless>
    <div v-bind="attrs">
      <!-- Custom switch visual -->
    </div>
  </Switch.Root>
</template>

FAQ

Discord
Need help? Join our community for support and discussions ↗
Was this page helpful?

Ctrl+/