Skip to content

SwitchField

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

Exemples d'utilisation

SwitchField de base

Switch simple

vue
<script setup>
const notifications = ref(false)
</script>

<template>
  <SuSwitchField
    label="Notifications"
    message="Activer les notifications push"
    v-model="notifications"
  />
</template>

Switch avec labels latéraux

Avec un seul label (aligné à côté)

vue
<template>
  <SuSwitchField
    label="Mode sombre"
    rightLabel="Activé"
    message="Basculer vers le thème sombre"
    v-model="darkMode"
  />
</template>

Switch avec labels des deux côtés

Avec labels gauche et droite (centré)

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

Tailles

Tailles disponibles

vue
<template>
  <SuSwitchFieldsize="sm" label="Small" rightLabel="Petit" />
  <SuSwitchFieldsize="md" label="Medium" rightLabel="Moyen" />
  <SuSwitchFieldsize="lg" label="Large" rightLabel="Grand" />
</template>

États et validation

États de validation

vue
<template>
  <SuSwitchField
    state="default"
    label="État par défaut"
    message="Fonctionnement normal"
  />
  <SuSwitchField
    state="error"
    label="État d'erreur"
    message="Une erreur s'est produite"
  />
  <SuSwitchField
    state="success"
    label="État de succès"
    message="Configuration sauvegardée !"
  />
  <SuSwitchField
    state="warning"
    label="État d'avertissement"
    message="Cette action nécessite une confirmation"
  />
</template>

États disabled et readonly

États spéciaux

vue
<template>
  <SuSwitchField
    label="Switch désactivé"
    :disabled="true"
    :value="true"
    message="Ce switch est désactivé"
  />
  <SuSwitchField
    label="Switch en lecture seule"
    :readonly="true"
    :value="false"
    message="Cette valeur ne peut pas être modifiée"
  />
  <SuSwitchField
    label="Champ requis"
    :required="true"
    message="Ce paramètre est obligatoire"
  />
</template>

API

Props

PropTypeDefaultDescription
valuebooleanfalseÉtat du switch (activé/désactivé)
size'sm' | 'md' | 'lg''md'Taille du switch
state'default' | 'error' | 'success' | 'warning''default'État visuel du switch
disabledbooleanfalseDésactive le switch
readonlybooleanfalseSwitch en lecture seule
requiredbooleanfalseChamp requis
labelstringundefinedLabel principal du switch
leftLabelstringundefinedLabel affiché à gauche du switch
rightLabelstringundefinedLabel affiché à droite du switch
messagestringundefinedMessage affiché (style déterminé par le state)

Attributs d'accessibilité

PropTypeDefaultDescription
ariaLabelstringundefinedLabel accessible
ariaDescribedBystringundefinedID de l'élément de description
ariaInvalidbooleanundefinedIndique si la valeur est invalide
ariaRequiredbooleanundefinedIndique si le champ est requis

Events

EventTypeDescription
@update:modelValue(value: boolean) => voidÉmis lors du changement d'état (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 lors de l'appui sur une touche

Positionnement des labels

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

🎯 Logique de positionnement

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

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

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

Icônes personnalisées

Switch avec icônes personnalisées

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

<template>
  <SuSwitchField
    label="Mode sombre"
    leftLabel="Clair"
    rightLabel="Sombre"
    :leftIcon="SunIcon"
    :rightIcon="MoonIcon"
    message="Basculer entre les thèmes"
  />
</template>

Position des labels

Labels à l'extérieur vs à l'intérieur

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

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 : Support des touches Espace et Entrée
  • Labels associés : Chaque switch a un label correctement associé
  • Messages d'état : Messages avec aria-live pour les lecteurs d'écran
  • Contraste des couleurs : Ratios conformes WCAG AA (4.5:1 minimum)
  • Focus visible : Indicateur de focus clair et contrasté
  • Tailles minimales : Respecte les tailles minimales de cible tactile (44px)
  • Mode sombre : Contraste adapté automatiquement
  • Contraste élevé : Support de prefers-contrast: high
  • Réduction d'animation : Respect de prefers-reduced-motion

🎯 Bonnes pratiques

vue
<!-- Switch avec accessibilité complète -->
<SuSwitchField
  label="Notifications push"
  rightLabel="Activées"
  :required="true"
  message="Recevoir des notifications en temps réel"
  ariaLabel="Activer les notifications push"
  v-model="pushNotifications"
/>

<!-- Switch avec gestion d'erreur -->
<SuSwitchField
  label="Synchronisation"
  leftLabel="Désactivée"
  rightLabel="Activée"
  :state="hasError ? 'error' : 'default'"
  :message="hasError ? 'Erreur de synchronisation' : 'Synchronisation automatique des données'"
  v-model="syncEnabled"
/>

<!-- Switch avec validation -->
<SuSwitchField
  label="Accepter les conditions"
  rightLabel="J'accepte"
  :required="true"
  :state="!termsAccepted ? 'error' : 'success'"
  :message="!termsAccepted ? 'Vous devez accepter les conditions' : 'Conditions acceptées'"
  v-model="termsAccepted"
/>
ToucheAction
TabNaviguer vers/depuis le switch
EspaceBasculer l'état du switch
EntréeBasculer l'état du switch

Exemples d'usage avancés

Panneau de paramètres

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

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

<template>
  <div class="settings-panel">
    <h2>Paramètres</h2>
    
    <div class="settings-group">
      <h3>Interface</h3>
      <SuSwitchField
        label="Mode sombre"
        leftLabel="Clair"
        rightLabel="Sombre"
        message="Basculer entre les thèmes clair et sombre"
        v-model="settings.darkMode"
      />
      <SuSwitchField
        label="Sauvegarde automatique"
        rightLabel="Activée"
        message="Sauvegarder automatiquement vos modifications"
        v-model="settings.autoSave"
      />
    </div>
    
    <div class="settings-group">
      <h3>Confidentialité</h3>
      <SuSwitchField
        label="Profil public"
        leftLabel="Privé"
        rightLabel="Public"
        message="Contrôler la visibilité de votre profil"
        v-model="settings.publicProfile"
      />
    </div>
    
    <div class="settings-group">
      <h3>Notifications</h3>
      <SuSwitchField
        label="Notifications push"
        rightLabel="Activées"
        message="Recevoir des notifications en temps réel"
        v-model="settings.notifications"
      />
      <SuSwitchField
        label="Mises à jour par email"
        rightLabel="Activées"
        message="Recevoir les nouveautés par email"
        v-model="settings.emailUpdates"
      />
    </div>
  </div>
</template>

<style scoped>
.settings-panel {
  max-width: 500px;
  margin: 0 auto;
}

.settings-group {
  margin-bottom: 2rem;
}

.settings-group h3 {
  margin-bottom: 1rem;
  color: #374151;
  font-weight: 600;
}

.settings-group > * + * {
  margin-top: 1rem;
}
</style>

Switch avec validation conditionnelle

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

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

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

const getState = (isRequired, value) => {
  if (!isRequired) return 'default'
  return value ? 'success' : 'error'
}

const getMessage = (isRequired, value, successMsg, errorMsg) => {
  if (!isRequired) return undefined
  return value ? successMsg : errorMsg
}
</script>

<template>
  <form class="consent-form">
    <h2>Consentements</h2>
    
    <SuSwitchField
      label="Conditions d'utilisation"
      rightLabel="J'accepte"
      :required="true"
      :state="getState(true, termsAccepted)"
      :message="getMessage(
        true, 
        termsAccepted,
        'Conditions acceptées',
        'Vous devez accepter les conditions d\'utilisation'
      )"
      v-model:value="termsAccepted"
    />
    
    <SuSwitchField
      label="Politique de confidentialité"
      rightLabel="J'accepte"
      :required="true"
      :state="getState(true, privacyAccepted)"
      :message="getMessage(
        true,
        privacyAccepted,
        'Politique acceptée',
        'Vous devez accepter la politique de confidentialité'
      )"
      v-model:value="privacyAccepted"
    />
    
    <SuSwitchField
      label="Communications marketing"
      rightLabel="J'accepte"
      message="Recevoir des offres et actualités (optionnel)"
      v-model:value="marketingAccepted"
    />
    
    <button 
      type="submit" 
      :disabled="!canProceed"
      class="submit-button"
    >
      Continuer
    </button>
  </form>
</template>

<style scoped>
.consent-form {
  max-width: 400px;
  margin: 0 auto;
}

.consent-form > * + * {
  margin-top: 1.5rem;
}

.submit-button {
  width: 100%;
  padding: 0.75rem;
  background-color: #3b82f6;
  color: white;
  border: none;
  border-radius: 0.5rem;
  font-weight: 500;
  cursor: pointer;
  transition: all 0.2s;
}

.submit-button:disabled {
  opacity: 0.5;
  cursor: not-allowed;
}

.submit-button:not(:disabled):hover {
  background-color: #2563eb;
}
</style>

Publié sous licence MIT.