Skip to main content
Vuetify0 v1.0 is here
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

EmSwitch

Edit this page
Report a Bug
Open issues
Copy Markdown

Renders elementIntermediateAug 14, 2026

The on/off control — a sliding toggle in three sizes with a label that is part of the control, so clicking the text flips the switch.

Usage

EmSwitch is a shell component: fixed anatomy, so everything is a prop and the default slot is the label. v-model is the on/off state as a plain boolean.

The whole component is one <label> — track, thumb and text — so the entire surface is a click target and the cursor says so. Reach for a switch over a checkbox when the toggle takes effect immediately — a setting that applies the moment it flips — and for a checkbox when the choice is collected now and submitted later.

<script setup lang="ts">
  import { EmSwitch } from '@paper/emerald'
  import { shallowRef } from 'vue'

  const notifications = shallowRef(true)
  const autosave = shallowRef(false)
  const telemetry = shallowRef(false)
</script>

<template>
  <div class="emerald-docs-stack">
    <EmSwitch v-model="notifications">Desktop notifications</EmSwitch>

    <EmSwitch v-model="autosave">Autosave drafts</EmSwitch>

    <EmSwitch v-model="telemetry">Share anonymous usage data</EmSwitch>
  </div>
</template>

<style>
  .emerald-docs-stack {
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    gap: var(--emerald-spacing-xs, 8px);
  }
</style>

Anatomy

vue
<script setup lang="ts">
  import { EmSwitch } from '@paper/emerald'
</script>

<template>
  <EmSwitch />
</template>

Composed on v0

EmSwitch renders v0’s Switch compound — Switch.Root, Switch.Track and Switch.Thumb — and adds nothing to its behavior.

The split is clean. Switch.Root owns the state machine: role="switch" on a native button host, aria-checked, the Space handler, the data-state / data-disabled attributes every Emerald style rule hangs off, and the hidden native input that appears when name is set. Switch.Track and Switch.Thumb are decorative aria-hidden spans whose only job is to carry data-state down to something styleable. Emerald owns everything you can see: the pill, the thumb and its travel distance, the sizing tokens, and the hover and disabled palettes.

One deliberate quirk lives in the track: v0 renders it with a hardcoded dir="ltr", so the thumb’s travel is physical, not logical — on is always thumb-right, even under an RTL document. Emerald’s CSS honors that and does not reverse the travel under [dir=rtl]; the checked state is carried by the fill color either way.

Two inherited capabilities are not surfaced. Switch.Root supports an indeterminate state, which EmSwitch does not pass — an Emerald switch is strictly two-state. And the root is dual-mode: rendered inside a v0 Switch.Group it registers with the group and defers its checked state to it, ignoring the v-model. Emerald does not ship a group part, so treat that as a v0 escape hatch rather than a documented Emerald surface.

Examples

Sizes

size steps the control geometry through Emerald’s switch tokens — a 28×16px track for sm, 36×20 for md, 44×24 for lg — and scales the thumb and its travel with it, so the proportions hold at every step.

What size does not change is the label. The text stays on the b1 body step at every size, so a small switch is a smaller toggle beside the same text, not a smaller row. Pick sm for dense surfaces — table rows, filter panels — and lg where the toggle is the point of the surface.

Label prop

When the visible text lives outside the control — a settings row with the name on the left and the toggle on the right — there is no slot content to name the switch, and an unnamed role="switch" is announced as just “switch”. label covers that case: it becomes the root’s aria-label, giving assistive technology a name with nothing rendered.

Keep the label text identical to the visible text it stands in for. A reader using voice control targets the switch by what they can see, and a name that diverges from the on-screen text breaks that match. When the switch does have slot content, prefer the slot — the two mechanisms both work, but the slot names by reference to the actual rendered text, so it can never drift.

Disabled

disabled blocks the toggle in every form it can arrive — click, label click, Space — and drops the whole control to Emerald’s neutral tokens: greyed track, greyed text, not-allowed cursor across the entire label.

The state is preserved, not erased. A disabled switch keeps showing on or off exactly as it was, just without the color that invites interaction — which is what you want when a setting is locked by a precondition rather than discarded by one.

One behavior to know: a disabled EmSwitch stays in the tab order. The root is a native button with no disabled attribute — the block happens in the state layer — so keyboard users can still reach it and hear it announced as disabled, they just cannot flip it. See Accessibility for why that is the announced-but-inert pattern rather than a gap.

Props

PropTypeDefaultDescription
v-modelbooleanOn/off state
size'sm' | 'md' | 'lg''md'Control geometry step. Scales the track, thumb and travel; the label stays on the body scale
disabledbooleanfalseBlocks toggling and greys the control. The root stays focusable and announces aria-disabled
labelstringAccessible name when no default-slot label is rendered
namestringForm field name; renders a hidden native checkbox when set
valueunknownSubmitted value for the hidden input ('on' when unset; objects are JSON-serialized)
namespacestringWhich v0 Switch instance the parts bind to. Only needed when nesting

The default slot is the visible label. There are no named slots.

Accessibility

Switch.Root renders a native <button type="button"> carrying role="switch", so the state is announced through aria-checkedtrue or false, matching the visual data-state one for one.

Naming

A wrapping <label> does not name a <button> the way it names an <input>, so Emerald names the control by reference instead: the slot text renders in a sibling span with a generated id, and the root points at it with aria-labelledby. The result is the same as a native pairing — one accessible name, one click target — without relying on an association the platform does not provide for buttons.

When there is no visible text, pass label; it becomes the root’s aria-label. An EmSwitch with neither slot content nor label is an unnamed control, and a screen reader will announce only “switch” with no hint of what it governs.

Keyboard

Space toggles — that is the switch pattern’s key, and Switch.Root handles it directly. Because the host is a native button, Enter activates it as well; the extra key is a platform bonus, not something to design around.

Clicking the label text toggles too: the wrapper is a real <label> and the platform forwards its activation to the button inside.

Disabled

disabled sets aria-disabled and data-disabled and blocks activation in the state layer — click, label forwarding and Space all hit the same guard. What it does not set is the native disabled attribute, so the control keeps its place in the tab order. A keyboard user tabbing through a form still encounters the switch and hears it announced as disabled, rather than having it silently vanish from navigation; the trade is one extra tab stop for a state that stays discoverable.

Forms

When name is set, v0 renders a visually hidden native <input type="checkbox">inert, tabindex="-1", never focusable — that mirrors the on/off and disabled state and submits value (or 'on') with the surrounding form. It exists purely for form serialization; assistive technology interacts with the role="switch" button, never the mirror.

Right-to-left

The thumb’s travel does not mirror under RTL — the track is rendered dir="ltr" by v0, so on is always thumb-right. The announced state comes from aria-checked and the visual state from the track’s fill, neither of which depends on which side the thumb sits.

Focus

The focus ring is a pill-shaped outline around the toggle, shown only for :focus-visible — keyboard focus draws it, a mouse click does not leave a ring behind.

Was this page helpful?

© 2016-1970 Vuetify, LLC
Services
Ctrl+/