Skip to content

Switch ​

Composant Switch (interrupteur) pour les actions de basculement on/off. Conforme aux normes W3C avec support complet de l'accessibilité et positionnement intelligent des labels.

Description ​

Switch est un composant interrupteur enrichi qui permet de basculer entre deux états (activé/désactivé). Il offre différentes options de positionnement des labels (gauche, droite, intérieur), supporte les icônes personnalisées, et respecte toutes les normes d'accessibilité.

Exemples d'utilisation ​

Utilisation simple (sans SuFormField) ​

Switch de base

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

const isEnabled = ref(false)
</script>

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

Switch avec label droit ​

Avec label à droite

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

const darkMode = ref(false)
</script>

<template>
  <SuSwitch 
    rightLabel="Activé"
    v-model="darkMode"
  />
</template>

Switch avec labels des deux côtés ​

Labels gauche et droite

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

const isPublic = ref(false)
</script>

<template>
  <SuSwitch 
    leftLabel="Privé"
    rightLabel="Public"
    v-model="isPublic"
  />
</template>

Avec icônes ​

Switch avec icônes

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

const theme = ref(false)
</script>

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

Tailles ​

Tailles disponibles

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

États ​

États visuels

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>

Position des labels ​

Labels extérieurs vs intérieurs

vue
<template>
  <!-- Labels à l'extérieur (défaut) -->
  <SuSwitch 
    leftLabel="OFF"
    rightLabel="ON"
    labelPosition="outside"
  />
  
  <!-- Labels à l'intérieur -->
  <SuSwitch 
    leftLabel="OFF"
    rightLabel="ON"
    labelPosition="inside"
  />
</template>

États spéciaux ​

Désactivé et lecture seule

vue
<template>
  <SuSwitch 
    :disabled="true"
    rightLabel="Désactivé"
    :value="true"
  />
  
  <SuSwitch 
    :readonly="true"
    rightLabel="Lecture seule"
    :value="false"
  />
</template>

Utilisation avec SuFormField ​

Le composant SuSwitch peut être utilisé avec SuFormField pour bénéficier d'une structure de formulaire complète avec label principal, message et gestion des états.

Usage basique avec SuFormField ​

vue
<template>
  <SuFormField 
    label="Notifications" 
    message="Activer les notifications push"
  >
    <template #default="slotProps">
      <SuSwitch 
        rightLabel="Activées"
        v-bind="slotProps"
        v-model="notifications"
      />
    </template>
  </SuFormField>
</template>

Avec destructuration des slot props ​

vue
<template>
  <SuFormField 
    label="Mode sombre"
    message="Basculer vers le thème sombre"
  >
    <template #default="{ fieldId, messageId, state, disabled }">
      <SuSwitch 
        :id="fieldId"
        :aria-describedby="messageId"
        :state="state"
        :disabled="disabled"
        leftLabel="Clair"
        rightLabel="Sombre"
        v-model="darkMode"
      />
    </template>
  </SuFormField>
</template>

Validation avec états ​

Switch avec 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 
    ? 'Conditions acceptées' 
    : 'Vous devez accepter les conditions'
})
</script>

<template>
  <SuFormField 
    label="Accepter les conditions"
    :required="true"
    :state="termsState"
    :message="termsMessage"
  >
    <template #default="slotProps">
      <SuSwitch 
        rightLabel="J'accepte"
        v-bind="slotProps"
        v-model="termsAccepted"
      />
    </template>
  </SuFormField>
</template>

Avec labels personnalisés ​

vue
<template>
  <SuFormField 
    label="Visibilité du profil"
    message="Contrôler qui peut voir votre profil"
  >
    <template #default="slotProps">
      <SuSwitch 
        leftLabel="Privé"
        rightLabel="Public"
        v-bind="slotProps"
        v-model="isPublic"
      />
    </template>
  </SuFormField>
</template>

Label et message personnalisés ​

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

<template>
  <SuFormField 
    label="Notifications push" 
    :required="true"
    message="Recevoir des notifications"
  >
    <template #label="{ label, required, htmlFor }">
      <label 
        :for="htmlFor" 
        class="flex items-center gap-2 font-bold text-gray-900"
      >
        <BellIcon class="w-4 h-4" />
        {{ label }}
        <span v-if="required" class="text-red-500">*</span>
      </label>
    </template>
    
    <template #default="slotProps">
      <SuSwitch 
        rightLabel="Activées"
        v-bind="slotProps"
        v-model="pushNotifications"
      />
    </template>
    
    <template #message="{ state }">
      <div class="flex items-center gap-2 text-sm text-gray-600">
        <InformationCircleIcon class="w-4 h-4" />
        <span v-if="state === 'error'">⚠️ Notifications requises</span>
        <span v-else>Recevoir des notifications en temps réel</span>
      </div>
    </template>
  </SuFormField>
</template>

Avec icônes et descriptions ​

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

<template>
  <SuFormField 
    label="Thème de l'interface"
    message="Choisissez le thème qui vous convient"
  >
    <template #default="slotProps">
      <SuSwitch 
        leftLabel="Clair"
        rightLabel="Sombre"
        :leftIcon="SunIcon"
        :rightIcon="MoonIcon"
        v-bind="slotProps"
        v-model="darkMode"
      />
    </template>
  </SuFormField>
</template>

Panneau de paramètres ​

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>Paramètres</h2>
    
    <SuFormField 
      label="Mode sombre"
      message="Basculer entre les thèmes"
    >
      <template #default="slotProps">
        <SuSwitch 
          leftLabel="Clair"
          rightLabel="Sombre"
          v-bind="slotProps"
          v-model="settings.darkMode"
        />
      </template>
    </SuFormField>
    
    <SuFormField 
      label="Sauvegarde automatique"
      message="Sauvegarder automatiquement vos modifications"
    >
      <template #default="slotProps">
        <SuSwitch 
          rightLabel="Activée"
          v-bind="slotProps"
          v-model="settings.autoSave"
        />
      </template>
    </SuFormField>
    
    <SuFormField 
      label="Profil public"
      message="Contrôler la visibilité de votre profil"
    >
      <template #default="slotProps">
        <SuSwitch 
          leftLabel="Privé"
          rightLabel="Public"
          v-bind="slotProps"
          v-model="settings.publicProfile"
        />
      </template>
    </SuFormField>
    
    <SuFormField 
      label="Notifications push"
      message="Recevoir des notifications en temps réel"
    >
      <template #default="slotProps">
        <SuSwitch 
          rightLabel="Activées"
          v-bind="slotProps"
          v-model="settings.notifications"
        />
      </template>
    </SuFormField>
  </div>
</template>

Champ désactivé ​

vue
<template>
  <SuFormField 
    label="Option désactivée"
    :disabled="true"
    message="Ce paramètre n'est pas modifiable"
  >
    <template #default="slotProps">
      <SuSwitch 
        rightLabel="Désactivé"
        :value="true"
        v-bind="slotProps"
      />
    </template>
  </SuFormField>
</template>

API ​

Props ​

PropTypeDefaultDescription
modelValuebooleanfalseÉtat du switch (v-model)
size'sm' | 'md' | 'lg''md'Taille du switch
state'default' | 'error' | 'success' | 'warning''default'État visuel
disabledbooleanfalseDésactive le switch
readonlybooleanfalseSwitch en lecture seule
requiredbooleanfalseChamp requis
leftLabelstringundefinedLabel affiché à gauche
rightLabelstringundefinedLabel affiché à droite
leftIconComponentundefinedIcône affichée dans la pastille quand le switch est désactivé (aucune par défaut)
rightIconComponentundefinedIcône affichée dans la pastille quand le switch est activé (aucune par défaut)
paddedbooleantrueMarge entre la pastille et le bord de la piste. false : la pastille affleure le bord
labelPosition'outside' | 'inside''outside'Position des labels

Props d'accessibilité (héritées de SuFormField) ​

Lorsque utilisé avec SuFormField, le composant reçoit automatiquement :

PropTypeDescription
idstringID unique du champ (fieldId)
aria-describedbystringID du message associé (messageId)
statestringÉtat de validation
disabledbooleanÉtat désactivé

Attributs d'accessibilité supplémentaires ​

PropTypeDefaultDescription
ariaLabelstringundefinedLabel accessible
ariaInvalidbooleanundefinedIndique si invalide
ariaRequiredbooleanundefinedIndique si requis

Events ​

EventTypeDescription
@update:modelValue(value: boolean) => voidÉmis lors du changement (v-model)
@change(value: boolean) => voidÉmis lors du changement
@focus(event: FocusEvent) => voidÉmis lors du focus
@blur(event: FocusEvent) => voidÉmis lors de la perte de focus
@keydown(event: KeyboardEvent) => voidÉmis sur appui de touche

Positionnement des labels ​

Le composant Switch adapte automatiquement le positionnement selon les labels fournis :

🎯 Logique de positionnement ​

  • Aucun label latéral : Switch seul
  • Un seul label (leftLabel OU rightLabel) : Switch aligné à côté
  • Deux labels (leftLabel ET rightLabel) : Switch centré entre les deux
vue
<!-- Switch seul -->
<SuSwitch />

<!-- Switch à côté du label -->
<SuSwitch rightLabel="Activé" />

<!-- Switch centré -->
<SuSwitch leftLabel="Privé" rightLabel="Public" />

Accessibilité ​

Le composant Switch respecte les normes WCAG 2.1 AA et les bonnes pratiques W3C :

✅ Fonctionnalités d'accessibilité ​

  • Rôle ARIA : role="switch" avec aria-checked
  • Navigation au clavier : Espace et Entrée
  • Labels associés : Via aria-describedby (messageId)
  • Messages d'état : Avec aria-live
  • Contraste des couleurs : Ratios conformes WCAG AA (4.5:1)
  • Focus visible : Indicateur clair avec outline
  • Tailles minimales : Cible tactile de 44px minimum
  • Mode sombre : Contraste adapté
  • Réduction d'animation : Respect de prefers-reduced-motion

🎯 Bonnes pratiques d'utilisation ​

vue
<!-- ✅ BON : Utilisation avec SuFormField -->
<SuFormField 
  label="Notifications"
  :required="true"
  message="Activer les notifications push"
>
  <template #default="slotProps">
    <SuSwitch 
      rightLabel="Activées"
      v-bind="slotProps"
      v-model="notifications"
    />
  </template>
</SuFormField>

<!-- ✅ BON : Standalone avec aria-label -->
<SuSwitch 
  aria-label="Activer les notifications"
  rightLabel="Activées"
  v-model="notifications"
/>

<!-- ❌ MAUVAIS : Sans label ni contexte -->
<SuSwitch v-model="value" />
ToucheAction
TabNaviguer vers/depuis le switch
EspaceBasculer l'état
EntréeBasculer l'état

Exemples d'usage avancés ​

Validation conditionnelle ​

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>Consentements</h2>
    
    <SuFormField 
      label="Conditions d'utilisation"
      :required="true"
      :state="getState(termsAccepted)"
      :message="getMessage(
        termsAccepted,
        'Conditions acceptées',
        'Vous devez accepter les conditions'
      )"
    >
      <template #default="slotProps">
        <SuSwitch 
          rightLabel="J'accepte"
          v-bind="slotProps"
          v-model="termsAccepted"
        />
      </template>
    </SuFormField>
    
    <SuFormField 
      label="Politique de confidentialité"
      :required="true"
      :state="getState(privacyAccepted)"
      :message="getMessage(
        privacyAccepted,
        'Politique acceptée',
        'Vous devez accepter la politique'
      )"
    >
      <template #default="slotProps">
        <SuSwitch 
          rightLabel="J'accepte"
          v-bind="slotProps"
          v-model="privacyAccepted"
        />
      </template>
    </SuFormField>
    
    <button :disabled="!canProceed">Continuer</button>
  </form>
</template>

Composant SuSwitchField ​

Pour une utilisation encore plus simple, vous pouvez utiliser le composant SuSwitchField qui combine automatiquement SuFormField et SuSwitch :

vue
<template>
  <!-- Au lieu de -->
  <SuFormField label="Notifications" message="Activer les notifications">
    <template #default="slotProps">
      <SuSwitch 
        rightLabel="Activées"
        v-bind="slotProps"
        v-model="notifications"
      />
    </template>
  </SuFormField>
  
  <!-- Vous pouvez simplement écrire -->
  <SuSwitchField 
    label="Notifications"
    rightLabel="Activées"
    message="Activer les notifications"
    v-model="notifications"
  />
</template>

Le composant SuSwitchField accepte toutes les props de SuFormField et SuSwitch combinées, offrant une syntaxe plus concise tout en conservant toutes les fonctionnalités.

Voir la documentation complète de SuSwitchField pour plus de détails.

Publié sous licence MIT.