Skip to content

FormFields

Composant FormFields pour organiser et aligner des champs de formulaire avec un espacement contrôlé. Supporte la propagation de la prop size aux champs enfants et la gestion des sections avec slots head et footer.

Exemples d'utilisation

FormFields de base

Formulaire simple

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

const options = [
  { value: 'option1', label: 'Option 1' },
  { value: 'option2', label: 'Option 2' },
  { value: 'option3', label: 'Option 3' }
]
</script>

<template>
  <SuFormFields>
    <SuInputField 
      label="Nom complet"
      placeholder="Entrez votre nom"
      :prefixIcon="UserIcon"
      required
    />
    <SuInputField 
      type="email"
      label="Email"
      placeholder="nom@exemple.com"
      :prefixIcon="AtSymbolIcon"
      required
    />
    <SuSelectBoxField 
      :options="options"
      label="Préférence"
      placeholder="Choisissez une option"
    />
    <SuSwitchField 
      label="Notifications"
      rightLabel="Activées"
      message="Recevoir des notifications par email"
    />
  </SuFormFields>
</template>

Espacement entre les champs (gap)

Différents espacements

Gap Small

Gap Medium (défaut)

Gap Large

Gap Extra Large

vue
<template>
  <!-- Espacement petit -->
  <SuFormFields gap="sm">
    <SuInputField label="Nom" placeholder="Votre nom" />
    <SuInputField type="email" label="Email" placeholder="email@exemple.com" />
    <SuSwitchField label="Newsletter" rightLabel="Oui" />
  </SuFormFields>
  
  <!-- Espacement moyen (défaut) -->
  <SuFormFields gap="md">
    <SuInputField label="Nom" placeholder="Votre nom" />
    <SuInputField type="email" label="Email" placeholder="email@exemple.com" />
    <SuSwitchField label="Newsletter" rightLabel="Oui" />
  </SuFormFields>
  
  <!-- Espacement large -->
  <SuFormFields gap="lg">
    <SuInputField label="Nom" placeholder="Votre nom" />
    <SuInputField type="email" label="Email" placeholder="email@exemple.com" />
    <SuSwitchField label="Newsletter" rightLabel="Oui" />
  </SuFormFields>
  
  <!-- Espacement extra large -->
  <SuFormFields gap="xl">
    <SuInputField label="Nom" placeholder="Votre nom" />
    <SuInputField type="email" label="Email" placeholder="email@exemple.com" />
    <SuSwitchField label="Newsletter" rightLabel="Oui" />
  </SuFormFields>
</template>

Propagation de la taille

Taille forcée sur tous les champs

Taille Small forcée

Taille Medium forcée

Taille Large forcée

vue
<template>
  <!-- Tous les champs seront en taille small -->
  <SuFormFields size="sm">
    <SuInputField label="Nom" placeholder="Petit champ" />
    <SuSelectBoxField :options="options" label="Sélection" placeholder="Petit select" />
    <SuTextareaField label="Message" placeholder="Petit textarea" />
  </SuFormFields>
  
  <!-- Tous les champs seront en taille large -->
  <SuFormFields size="lg">
    <SuInputField label="Nom" placeholder="Grand champ" />
    <SuSelectBoxField :options="options" label="Sélection" placeholder="Grand select" />
    <SuTextareaField label="Message" placeholder="Grand textarea" />
  </SuFormFields>
</template>

Direction horizontale

Champs alignés horizontalement

vue
<template>
  <SuFormFields direction="horizontal" gap="md">
    <SuInputField 
      label="Prénom"
      placeholder="Votre prénom"
      required
    />
    <SuInputField 
      label="Nom"
      placeholder="Votre nom"
      required
    />
    <SuSelectBoxField 
      :options="countryOptions"
      label="Pays"
      placeholder="Sélectionnez votre pays"
    />
  </SuFormFields>
</template>

Formulaire avec en-tête et pied de page

vue
<script setup>
import { UserIcon, AtSymbolIcon, LockClosedIcon } from '@heroicons/vue/24/outline'
</script>

<template>
  <SuFormFields role="form" aria-label="Formulaire d'inscription">
    <template #head>
      <div class="form-header">
        <h2>Créer un compte</h2>
        <p>Remplissez les informations ci-dessous</p>
      </div>
    </template>

    <SuInputField 
      label="Nom complet"
      placeholder="Entrez votre nom complet"
      :prefixIcon="UserIcon"
      required
    />
    
    <SuInputField 
      type="email"
      label="Adresse email"
      placeholder="nom@exemple.com"
      :prefixIcon="AtSymbolIcon"
      required
    />
    
    <SuInputField 
      type="password"
      label="Mot de passe"
      placeholder="••••••••"
      :prefixIcon="LockClosedIcon"
      required
    />
    
    <SuSwitchField 
      label="Conditions d'utilisation"
      rightLabel="J'accepte"
      required
    />

    <template #footer>
      <div class="form-actions">
        <SuButton variant="outline">Annuler</SuButton>
        <SuButton variant="primary">Créer le compte</SuButton>
      </div>
    </template>
  </SuFormFields>
</template>

Espacement des sections (sectionGap)

Différents espacements entre sections

Section Gap Small

Section Gap Large (défaut)

Section Gap Extra Large

vue
<template>
  <!-- Espacement petit entre sections -->
  <SuFormFields sectionGap="sm">
    <template #head>
      <h3>Informations personnelles</h3>
    </template>
    <SuInputField label="Nom" placeholder="Votre nom" />
    <SuInputField label="Email" placeholder="votre@email.com" />
    <template #footer>
      <SuButton variant="primary" block>Sauvegarder</SuButton>
    </template>
  </SuFormFields>
  
  <!-- Espacement extra large entre sections -->
  <SuFormFields sectionGap="xl">
    <template #head>
      <h3>Informations personnelles</h3>
    </template>
    <SuInputField label="Nom" placeholder="Votre nom" />
    <SuInputField label="Email" placeholder="votre@email.com" />
    <template #footer>
      <SuButton variant="primary" block>Sauvegarder</SuButton>
    </template>
  </SuFormFields>
</template>

Tous les types de champs

Formulaire complet avec tous les types de champs

vue
<template>
  <SuFormFields gap="lg" size="md">
    <template #head>
      <div class="form-header">
        <h2>Profil complet</h2>
        <p>Remplissez toutes les informations</p>
      </div>
    </template>

    <SuInputField label="Nom d'utilisateur" placeholder="Nom d'utilisateur" required />
    <SuSelectBoxField :options="countryOptions" label="Pays" searchable />
    <SuRadioGroup :options="accountTypes" label="Type de compte" />
    <SuCheckboxGroup :options="skills" label="Compétences" />
    <SuSlider label="Niveau d'expérience" :min="1" :max="10" />
    <SuFileUpload label="Photo de profil" accept="image/*" />
    <SuTextareaField label="Bio" placeholder="Parlez-nous de vous..." />
    <SuSwitchField label="Profil public" leftLabel="Privé" rightLabel="Public" />

    <template #footer>
      <div class="form-actions">
        <SuButton variant="outline">Réinitialiser</SuButton>
        <SuButton variant="primary">Sauvegarder</SuButton>
      </div>
    </template>
  </SuFormFields>
</template>

API

Props

PropTypeDefaultDescription
gap'sm' | 'md' | 'lg' | 'xl''md'Espacement entre les champs de formulaire
sectionGap'sm' | 'md' | 'lg' | 'xl''lg'Espacement entre les sections (head, content, footer)
size'sm' | 'md' | 'lg'undefinedTaille forcée pour tous les champs
direction'horizontal' | 'vertical''vertical'Direction d'affichage des champs

Attributs d'accessibilité

PropTypeDefaultDescription
ariaLabelstringundefinedLabel accessible pour le groupe
ariaDescribedBystringundefinedID de l'élément de description
rolestringundefinedRôle ARIA personnalisé (ex: 'form', 'group')

Slots

SlotDescription
headContenu affiché avant les champs (en-tête)
defaultChamps de formulaire à afficher
footerContenu affiché après les champs (pied de page)

Comportement des props

🔄 Propagation automatique

Quand size est définie sur le FormFields, elle surcharge automatiquement les props des champs enfants :

vue
<!-- Les champs auront TOUS la taille 'lg' -->
<SuFormFields size="lg">
  <SuInputField size="sm" label="Champ 1" />  <!-- Devient lg -->
  <SuInputField label="Champ 2" />            <!-- Devient lg -->
  <SuSelectBoxField size="md" label="Select" /> <!-- Devient lg -->
</SuFormFields>

🎯 Validation du contenu

Le composant vérifie automatiquement que seuls des composants de formulaire sont passés dans le slot :

  • Composants de formulaire : InputField, SelectBoxField, RadioGroupField, CheckboxGroupField, SwitchField, FileUploadField, TextareaField, SliderField
  • ⚠️ Autres composants : Avertissement dans la console et élément ignoré
  • Commentaires/texte : Ignorés silencieusement (comportement normal de Vue)

Espacement et structure

📏 Valeurs de gap (entre les champs)

  • gap="sm" : 0.75rem (12px)
  • gap="md" : 1rem (16px) - défaut
  • gap="lg" : 1.5rem (24px)
  • gap="xl" : 2rem (32px)
  • sectionGap="sm" : 1rem (16px)
  • sectionGap="md" : 1.5rem (24px)
  • sectionGap="lg" : 2rem (32px) - défaut
  • sectionGap="xl" : 2.5rem (40px)

📱 Responsive

En mode horizontal, le composant passe automatiquement en vertical sur mobile (≤768px) pour une meilleure expérience utilisateur.

Direction

📐 Vertical vs Horizontal

  • Vertical (défaut) : Champs empilés en colonne
  • Horizontal : Champs alignés en ligne avec largeur égale
vue
<!-- Vertical -->
<SuFormFields direction="vertical">
  <SuInputField label="Champ 1" />
  <SuInputField label="Champ 2" />
</SuFormFields>

<!-- Horizontal -->
<SuFormFields direction="horizontal">
  <SuInputField label="Prénom" />
  <SuInputField label="Nom" />
</SuFormFields>

Accessibilité

Le composant FormFields respecte les normes WCAG 2.1 AA :

✅ Fonctionnalités d'accessibilité

  • Rôles ARIA : Support des rôles form, group, etc.
  • Labels de groupe : aria-label pour décrire le formulaire
  • Navigation au clavier : Préserve la navigation Tab entre les champs
  • Structure sémantique : Organisation logique des sections
  • Descriptions : Support d'aria-describedby pour les descriptions
  • Responsive : Adaptation automatique sur mobile

🎯 Bonnes pratiques

vue
<!-- Formulaire principal -->
<SuFormFields 
  gap="lg" 
  role="form" 
  aria-label="Formulaire d'inscription"
>
  <SuInputField label="Email" type="email" required />
  <SuInputField label="Mot de passe" type="password" required />
</SuFormFields>

<!-- Section de paramètres -->
<SuFormFields 
  gap="md" 
  role="group" 
  aria-label="Paramètres de notification"
  aria-describedby="notification-help"
>
  <SuSwitchField label="Notifications email" />
  <SuSwitchField label="Notifications push" />
</SuFormFields>
<div id="notification-help">Configurez vos préférences de notification</div>

<!-- Formulaire horizontal responsive -->
<SuFormFields 
  direction="horizontal" 
  gap="md"
  role="form"
  aria-label="Adresse de livraison"
>
  <SuInputField label="Prénom" required />
  <SuInputField label="Nom" required />
  <SuInputField label="Code postal" required />
</SuFormFields>

Exemples d'usage avancés

Formulaire de contact

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

const form = ref({
  name: '',
  email: '',
  subject: '',
  message: '',
  preferences: []
})

const subjects = [
  { value: 'support', label: 'Support technique' },
  { value: 'sales', label: 'Ventes' },
  { value: 'billing', label: 'Facturation' }
]

const preferences = [
  { value: 'email', label: 'Réponse par email' },
  { value: 'phone', label: 'Rappel téléphonique' }
]
</script>

<template>
  <SuFormFields 
    gap="lg" 
    sectionGap="xl"
    role="form" 
    aria-label="Formulaire de contact"
  >
    <template #head>
      <div class="contact-header">
        <h1>Nous contacter</h1>
        <p>Notre équipe vous répondra dans les plus brefs délais</p>
      </div>
    </template>

    <SuInputField 
      label="Nom complet"
      v-model="form.name"
      required
    />
    
    <SuInputField 
      type="email"
      label="Email"
      v-model="form.email"
      required
    />
    
    <SuSelectBoxField 
      :options="subjects"
      label="Sujet"
      v-model="form.subject"
      required
    />
    
    <SuTextareaField 
      label="Message"
      v-model="form.message"
      :maxLength="1000"
      :showCounter="true"
      required
    />
    
    <SuCheckboxGroup 
      :options="preferences"
      label="Préférences de contact"
      v-model="form.preferences"
    />

    <template #footer>
      <div class="form-footer">
        <SuButton variant="outline">Annuler</SuButton>
        <SuButton variant="primary">Envoyer</SuButton>
      </div>
    </template>
  </SuFormFields>
</template>

Formulaire de profil utilisateur

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

const profile = ref({
  username: '',
  email: '',
  country: '',
  accountType: '',
  theme: '',
  notifications: false,
  publicProfile: false
})

const countries = [
  { value: 'fr', label: 'France' },
  { value: 'us', label: 'États-Unis' },
  { value: 'de', label: 'Allemagne' }
]

const accountTypes = [
  { value: 'personal', label: 'Personnel' },
  { value: 'business', label: 'Entreprise' }
]

const themes = [
  { value: 'light', label: 'Clair' },
  { value: 'dark', label: 'Sombre' },
  { value: 'auto', label: 'Automatique' }
]
</script>

<template>
  <SuFormFields 
    gap="lg" 
    sectionGap="xl"
    size="md"
    role="form" 
    aria-label="Profil utilisateur"
  >
    <template #head>
      <div class="profile-header">
        <h2>👤 Profil utilisateur</h2>
        <p>Personnalisez votre profil et vos préférences</p>
      </div>
    </template>

    <SuInputField 
      label="Nom d'utilisateur"
      v-model="profile.username"
      required
    />
    
    <SuInputField 
      type="email"
      label="Email"
      v-model="profile.email"
      required
    />
    
    <SuSelectBoxField 
      :options="countries"
      label="Localisation"
      v-model="profile.country"
      searchable
    />
    
    <SuRadioGroup 
      :options="accountTypes"
      label="Type de compte"
      v-model="profile.accountType"
      direction="horizontal"
    />
    
    <SuRadioGroup 
      :options="themes"
      label="Thème"
      v-model="profile.theme"
      displayType="inline-card"
      direction="horizontal"
    />
    
    <SuSwitchField 
      label="Notifications"
      rightLabel="Activées"
      v-model="profile.notifications"
    />
    
    <SuSwitchField 
      label="Profil public"
      leftLabel="Privé"
      rightLabel="Public"
      v-model="profile.publicProfile"
    />

    <template #footer>
      <div class="profile-footer">
        <p>Les modifications sont sauvegardées automatiquement</p>
        <div class="actions">
          <SuButton variant="outline">Réinitialiser</SuButton>
          <SuButton variant="primary">Sauvegarder</SuButton>
        </div>
      </div>
    </template>
  </SuFormFields>
</template>

Formulaire multi-étapes

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

const currentStep = ref(1)
const totalSteps = 3

const nextStep = () => {
  if (currentStep.value < totalSteps) {
    currentStep.value++
  }
}

const previousStep = () => {
  if (currentStep.value > 1) {
    currentStep.value--
  }
}
</script>

<template>
  <SuFormFields 
    gap="lg" 
    sectionGap="xl"
    role="form" 
    aria-label="Inscription multi-étapes"
  >
    <template #head>
      <div class="steps-header">
        <h2>Inscription - Étape {{ currentStep }}/{{ totalSteps }}</h2>
        <div class="progress-bar">
          <div 
            class="progress-fill" 
            :style="{ width: (currentStep / totalSteps) * 100 + '%' }"
          />
        </div>
      </div>
    </template>

    <!-- Étape 1 : Informations personnelles -->
    <template v-if="currentStep === 1">
      <SuInputField label="Prénom" placeholder="Votre prénom" required />
      <SuInputField label="Nom" placeholder="Votre nom" required />
      <SuInputField type="email" label="Email" placeholder="email@exemple.com" required />
    </template>

    <!-- Étape 2 : Préférences -->
    <template v-if="currentStep === 2">
      <SuSelectBoxField :options="countries" label="Pays" required />
      <SuRadioGroup :options="accountTypes" label="Type de compte" required />
      <SuSwitchField label="Newsletter" rightLabel="S'abonner" />
    </template>

    <!-- Étape 3 : Confirmation -->
    <template v-if="currentStep === 3">
      <div class="confirmation">
        <h3>Récapitulatif</h3>
        <p>Vérifiez vos informations avant de finaliser l'inscription</p>
      </div>
    </template>

    <template #footer>
      <div class="steps-footer">
        <SuButton 
          variant="outline" 
          :disabled="currentStep === 1"
          @click="previousStep"
        >
          Précédent
        </SuButton>
        
        <SuButton 
          variant="primary"
          @click="currentStep === totalSteps ? null : nextStep()"
        >
          {{ currentStep === totalSteps ? 'Finaliser' : 'Suivant' }}
        </SuButton>
      </div>
    </template>
  </SuFormFields>
</template>

Contrôle du contenu du slot

Le composant FormFields vérifie automatiquement le contenu de son slot :

✅ Éléments acceptés

  • Composants de formulaire du design system : InputField, SelectBoxField, RadioGroupField, CheckboxGroupField, SwitchField, FileUploadField, TextareaField, SliderField
  • Commentaires Vue (ignorés)
  • Nœuds de texte vides (ignorés)

⚠️ Éléments rejetés

  • Autres composants ou éléments HTML
  • Avertissement dans la console de développement
  • Élément ignoré dans le rendu

🔍 Exemple de validation

vue
<!-- ✅ Correct -->
<SuFormFields>
  <SuInputField label="Nom" />
  <SuSelectBoxField :options="options" label="Choix" />
  <!-- Commentaire ignoré -->
</SuFormFields>

<!-- ⚠️ Avertissement dans la console -->
<SuFormFields>
  <SuInputField label="Champ valide" />
  <div>Élément non-champ</div> <!-- Ignoré avec avertissement -->
  <SuSwitchField label="Switch valide" />
</SuFormFields>

Cette approche garantit la cohérence visuelle et fonctionnelle tout en informant les développeurs des problèmes potentiels.

Publié sous licence MIT.