Skip to content

Switch ​

Switch (toggle) component for on/off actions. W3C compliant with complete accessibility support and intelligent label positioning.

Description ​

Switch is an enhanced toggle component that allows switching between two states (on/off). It offers different label positioning options (left, right, inside), supports custom icons, and complies with all accessibility standards.

Usage examples ​

Simple usage (without SuFormField) ​

vue
<script setup>
import { ref } from 'vue'

const isEnabled = ref(false)
</script>

<template>
  <SuSwitch v-model="isEnabled" />
</template>

With right label ​

vue
<template>
  <SuSwitch 
    rightLabel="Enabled"
    v-model="darkMode"
  />
</template>

With labels on both sides ​

vue
<template>
  <SuSwitch 
    leftLabel="Private"
    rightLabel="Public"
    v-model="isPublic"
  />
</template>

With icons ​

vue
<script setup>
import { SunIcon, MoonIcon } from '@heroicons/vue/24/outline'
</script>

<template>
  <SuSwitch 
    leftLabel="Light"
    rightLabel="Dark"
    :leftIcon="SunIcon"
    :rightIcon="MoonIcon"
    v-model="theme"
  />
</template>

Sizes ​

vue
<template>
  <SuSwitch size="sm" rightLabel="Small" />
  <SuSwitch size="md" rightLabel="Medium" />
  <SuSwitch size="lg" rightLabel="Large" />
</template>

States ​

vue
<template>
  <SuSwitch rightLabel="Default" />
  <SuSwitch state="error" rightLabel="Error" :value="true" />
  <SuSwitch state="success" rightLabel="Success" :value="true" />
  <SuSwitch state="warning" rightLabel="Warning" :value="true" />
</template>

Label positions ​

vue
<template>
  <!-- Outside labels (default) -->
  <SuSwitch 
    leftLabel="OFF"
    rightLabel="ON"
    labelPosition="outside"
  />
  
  <!-- Inside labels -->
  <SuSwitch 
    leftLabel="OFF"
    rightLabel="ON"
    labelPosition="inside"
  />
</template>

Usage with SuFormField ​

Basic usage ​

vue
<template>
  <SuFormField 
    label="Notifications" 
    message="Enable push notifications"
  >
    <template #default="slotProps">
      <SuSwitch 
        rightLabel="Enabled"
        v-bind="slotProps"
        v-model="notifications"
      />
    </template>
  </SuFormField>
</template>

With validation ​

vue
<script setup>
import { ref, computed } from 'vue'

const termsAccepted = ref(false)

const termsState = computed(() => {
  return termsAccepted.value ? 'success' : 'error'
})

const termsMessage = computed(() => {
  return termsAccepted.value 
    ? 'Terms accepted' 
    : 'You must accept the terms'
})
</script>

<template>
  <SuFormField 
    label="Accept terms"
    :required="true"
    :state="termsState"
    :message="termsMessage"
  >
    <template #default="slotProps">
      <SuSwitch 
        rightLabel="I accept"
        v-bind="slotProps"
        v-model="termsAccepted"
      />
    </template>
  </SuFormField>
</template>

Settings panel ​

vue
<script setup>
import { ref } from 'vue'

const settings = ref({
  notifications: true,
  darkMode: false,
  autoSave: true,
  publicProfile: false
})
</script>

<template>
  <div class="settings-panel">
    <h2>Settings</h2>
    
    <SuFormField 
      label="Dark mode"
      message="Switch between themes"
    >
      <template #default="slotProps">
        <SuSwitch 
          leftLabel="Light"
          rightLabel="Dark"
          v-bind="slotProps"
          v-model="settings.darkMode"
        />
      </template>
    </SuFormField>
    
    <SuFormField 
      label="Auto-save"
      message="Automatically save your changes"
    >
      <template #default="slotProps">
        <SuSwitch 
          rightLabel="Enabled"
          v-bind="slotProps"
          v-model="settings.autoSave"
        />
      </template>
    </SuFormField>
  </div>
</template>

API ​

Props ​

PropTypeDefaultDescription
modelValuebooleanfalseSwitch state (v-model)
size'sm' | 'md' | 'lg''md'Switch size
state'default' | 'error' | 'success' | 'warning''default'Visual state
disabledbooleanfalseDisable the switch
readonlybooleanfalseRead-only switch
requiredbooleanfalseRequired field
leftLabelstringundefinedLeft label
rightLabelstringundefinedRight label
leftIconComponentundefinedIcon shown in the thumb when the switch is off (none by default)
rightIconComponentundefinedIcon shown in the thumb when the switch is on (none by default)
paddedbooleantrueGap between the thumb and the track edge. false: the thumb sits flush with the edge
labelPosition'outside' | 'inside''outside'Label position

Accessibility props (inherited from SuFormField) ​

When used with SuFormField, automatically receives:

PropTypeDescription
idstringUnique field ID (fieldId)
aria-describedbystringAssociated message ID (messageId)
statestringValidation state
disabledbooleanDisabled state

Events ​

EventTypeDescription
@update:modelValue(value: boolean) => voidEmitted on change (v-model)
@change(value: boolean) => voidEmitted on change
@focus(event: FocusEvent) => voidEmitted on focus
@blur(event: FocusEvent) => voidEmitted on blur

Label positioning ​

Automatic positioning based on provided labels:

  • No side labels: Switch alone
  • One label (leftLabel OR rightLabel): Switch aligned next to label
  • Two labels (leftLabel AND rightLabel): Switch centered between labels

Accessibility ​

WCAG 2.1 AA compliant with W3C best practices:

✅ Accessibility features ​

  • ARIA role: role="switch" with aria-checked
  • Keyboard navigation: Space and Enter keys
  • Associated labels: Via aria-describedby (messageId)
  • State messages: With aria-live
  • Color contrast: WCAG AA ratios (4.5:1)
  • Visible focus: Clear outline indicator
  • Minimum sizes: 44px touch target minimum
  • Reduced motion: Respects prefers-reduced-motion

🎯 Best practices ​

vue
<!-- ✅ GOOD: With SuFormField -->
<SuFormField 
  label="Notifications"
  :required="true"
  message="Enable push notifications"
>
  <template #default="slotProps">
    <SuSwitch 
      rightLabel="Enabled"
      v-bind="slotProps"
      v-model="notifications"
    />
  </template>
</SuFormField>

<!-- ✅ GOOD: Standalone with aria-label -->
<SuSwitch 
  aria-label="Enable notifications"
  rightLabel="Enabled"
  v-model="notifications"
/>

<!-- ❌ BAD: Without label or context -->
<SuSwitch v-model="value" />

Keyboard navigation ​

KeyAction
TabNavigate to/from switch
SpaceToggle state
EnterToggle state

Advanced examples ​

Conditional validation ​

vue
<script setup>
import { ref, computed } from 'vue'

const termsAccepted = ref(false)
const privacyAccepted = ref(false)

const canProceed = computed(() => {
  return termsAccepted.value && privacyAccepted.value
})

const getState = (value) => value ? 'success' : 'error'
const getMessage = (value, success, error) => value ? success : error
</script>

<template>
  <form>
    <h2>Consents</h2>
    
    <SuFormField 
      label="Terms of use"
      :required="true"
      :state="getState(termsAccepted)"
      :message="getMessage(
        termsAccepted,
        'Terms accepted',
        'You must accept the terms'
      )"
    >
      <template #default="slotProps">
        <SuSwitch 
          rightLabel="I accept"
          v-bind="slotProps"
          v-model="termsAccepted"
        />
      </template>
    </SuFormField>
    
    <button :disabled="!canProceed">Continue</button>
  </form>
</template>

SuSwitchField Component ​

For simpler usage, use SuSwitchField which combines SuFormField and SuSwitch:

vue
<template>
  <!-- Instead of -->
  <SuFormField label="Notifications" message="Enable notifications">
    <template #default="slotProps">
      <SuSwitch 
        rightLabel="Enabled"
        v-bind="slotProps"
        v-model="notifications"
      />
    </template>
  </SuFormField>
  
  <!-- Simply write -->
  <SuSwitchField 
    label="Notifications"
    rightLabel="Enabled"
    message="Enable notifications"
    v-model="notifications"
  />
</template>

See the complete SuSwitchField documentation for more details.

Publié sous licence MIT.