Keyboard shortcut: Ctrl + K
Get started

Switch

A setting that takes effect immediately. It is announced with role="switch", so a screen reader says on or off rather than ticked, and that is the whole reason it is not a checkbox.

Usage

vue
<script setup lang="ts">
import { ref } from 'vue'
import { VSwitch } from 'vectis-ui'

const notifications = ref(true)
</script>

<template>
  <VSwitch v-model="notifications">Notifications</VSwitch>
</template>

Examples

Hint

label writes the text beside the switch, and the default slot replaces it. hint adds a caption underneath, tied to the switch through aria-describedby, which is where a setting says what turning it on does.

vue
Joins known networks automatically.Visible to nearby devices while on.
<script setup lang="ts">
import { ref } from 'vue'
import { VSwitch } from 'vectis-ui'

const wifi = ref(true)
const bluetooth = ref(false)
</script>

<template>
  <div class="stack">
    <VSwitch v-model="wifi" label="Wi-Fi" hint="Joins known networks automatically." />
    <VSwitch v-model="bluetooth" label="Bluetooth" hint="Visible to nearby devices while on." />
  </div>
</template>

<style scoped>
.stack {
  display: grid;
  justify-items: start;
  gap: var(--vectis-space-3);
}
</style>

Label position

labelPosition moves the label before the switch instead of after it.

vue
<script setup lang="ts">
import { VSwitch } from 'vectis-ui'
</script>

<template>
  <div class="stack">
    <VSwitch>Label after the switch, the default</VSwitch>
    <VSwitch label-position="start">Label before the switch</VSwitch>
  </div>
</template>

<style scoped>
/* `justify-items: start` keeps each row as wide as its own label: the clickable area is
   the whole <label>, so a stretched row would be clickable well past its text. */
.stack {
  display: grid;
  justify-items: start;
  gap: var(--vectis-space-3);
}
</style>

Spread

spread takes the full width offered and pushes the label and the switch to opposite ends of the row.

vue
<script setup lang="ts">
import { ref } from 'vue'
import { VSwitch } from 'vectis-ui'

const digest = ref(true)
const twoFactor = ref(false)
</script>

<template>
  <div class="settings">
    <VSwitch v-model="digest" spread>Weekly digest</VSwitch>
    <VSwitch v-model="twoFactor" spread label-position="start">Two-factor authentication</VSwitch>
  </div>
</template>

<style scoped>
/* A spread row takes the width it is given, so the panel is what decides how far apart
   the label and the switch end up. */
.settings {
  display: grid;
  gap: var(--vectis-space-3);
  inline-size: 100%;
  max-inline-size: 24rem;
  padding: var(--vectis-space-4);
  border: 1px solid var(--vectis-color-border);
  border-radius: var(--vectis-radius-surface);
}
</style>

Read-only

readonly shows the setting without letting it change: a click or the Space key is cancelled. The switch stays focusable, is submitted with its form and is announced as read-only. It says so on screen either way round, an on switch trading the accent for the muted colour and an off one sinking behind a hairline ring.

vue
<script setup lang="ts">
import { ref } from 'vue'
import { VSwitch } from 'vectis-ui'

const on = ref(true)
const off = ref(false)
</script>

<template>
  <div class="stack">
    <VSwitch v-model="on" readonly label="Managed by your administrator" />
    <VSwitch v-model="off" readonly label="Locked until the next billing period" />
  </div>
</template>

<style scoped>
.stack {
  display: grid;
  justify-items: start;
  gap: var(--vectis-space-3);
}
</style>

Disabled

disabled prevents the switch from being flicked and greys it out through the colour tokens, on as well as off. The keyboard steps over it.

vue
<script setup lang="ts">
import { ref } from 'vue'
import { VSwitch } from 'vectis-ui'

const off = ref(false)
const on = ref(true)
</script>

<template>
  <div class="stack">
    <VSwitch v-model="off" disabled>Off, and cannot be turned on</VSwitch>
    <VSwitch v-model="on" disabled>On, and cannot be turned off</VSwitch>
  </div>
</template>

<style scoped>
.stack {
  display: grid;
  justify-items: start;
  gap: var(--vectis-space-3);
}
</style>

API

Props

PropTypeDefault
labelstringnone
The text beside the switch, which names it. The default slot replaces it.
hintstringnone
A line of help under the label. It is tied to the switch for assistive technology, so it is read out after the label rather than as part of it.
labelPositionSwitchLabelPosition'start' | 'end''end'
Which side of the switch the label sits on.
spreadbooleanfalse
Pushes the label and the switch to opposite ends of the line, so a column of settings lines its switches up down one edge.
invalidbooleanfalse
Marks the field as invalid, which rings the track and tells assistive technology so. Use it for a rule the browser cannot check by itself; native validity is already handled without it.
disabledbooleanfalse
Makes the switch unusable. It greys out through the colour tokens rather than through opacity, so it stays legible on any surface.
readonlybooleanfalse
Shows the setting without allowing it to be changed. The switch can still be focused, is announced as read-only and is still submitted with its form; a click or the Space key simply changes nothing.
v-modelbooleanfalse
Whether the switch is on. It is bound to a real hidden <input type="checkbox">, so the value submits with the form like any other field.

Slots

SlotType
default{}
The label, when it needs more than the label prop's text, a link or a piece of emphasis. It sits inside the <label>, so clicking the words toggles the switch.

CSS variables

TokenValue
--vectis-control-size-switch-w2.5rem
--vectis-control-size-switch-h1.25rem
--vectis-control-size-switch-pad0.125rem