Skip to content

Textarea

Composant Textarea flexible avec compteur de caractères, ajustement automatique de hauteur et conformité aux normes W3C d'accessibilité.

Description

SuTextarea est un composant de zone de texte enrichi qui étend les capacités d'un élément <textarea> natif. Il offre des fonctionnalités avancées telles que le compteur de caractères, l'ajustement automatique de la hauteur, et une gestion complète de l'accessibilité.

Exemples d'utilisation

Utilisation simple (sans SuFormField)

Textarea de base

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

const text = ref('')
</script>

<template>
  <SuTextarea 
    placeholder="Entrez votre texte..."
    :rows="4"
    v-model="text"
  />
</template>

Avec compteur de caractères

Compteur de caractères

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

const comment = ref('')
</script>

<template>
  <SuTextarea 
    placeholder="Votre commentaire..."
    :maxLength="200"
    :showCounter="true"
    :rows="3"
    v-model="comment"
  />
</template>

Ajustement automatique de hauteur

Auto-resize

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

const message = ref('')
</script>

<template>
  <SuTextarea 
    placeholder="Tapez votre message..."
    :autoResize="true"
    :minRows="2"
    :maxRows="8"
    v-model="message"
  />
</template>

Tailles

Tailles disponibles

vue
<template>
  <SuTextarea 
    size="sm" 
    placeholder="Petit textarea" 
    :rows="2"
  />
  
  <SuTextarea 
    size="md" 
    placeholder="Textarea moyen" 
    :rows="3"
  />
  
  <SuTextarea 
    size="lg" 
    placeholder="Grand textarea" 
    :rows="4"
  />
</template>

États

États visuels

vue
<template>
  <SuTextarea 
    placeholder="État par défaut"
  />
  
  <SuTextarea 
    state="error"
    value="Texte avec erreur"
  />
  
  <SuTextarea 
    state="success"
    value="Texte validé"
  />
  
  <SuTextarea 
    state="warning"
    value="Texte avec avertissement"
  />
</template>

Utilisation avec SuFormField

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

Usage basique avec SuFormField

vue
<template>
  <SuFormField 
    label="Description" 
    message="Décrivez votre projet en quelques phrases"
  >
    <template #default="slotProps">
      <SuTextarea 
        placeholder="Entrez votre description..."
        :rows="4"
        v-bind="slotProps"
        v-model="description"
      />
    </template>
  </SuFormField>
</template>

Avec destructuration des slot props

vue
<template>
  <SuFormField 
    label="Commentaire"
    :required="true"
    message="Partagez votre avis"
  >
    <template #default="{ fieldId, messageId, state, disabled }">
      <SuTextarea 
        :id="fieldId"
        :aria-describedby="messageId"
        :state="state"
        :disabled="disabled"
        placeholder="Votre commentaire..."
        :rows="3"
        v-model="comment"
      />
    </template>
  </SuFormField>
</template>

Validation avec états

Textarea avec validation

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

const message = ref('')
const messageError = ref('')

const validateMessage = () => {
  if (!message.value) {
    messageError.value = 'Le message est requis'
  } else if (message.value.length < 10) {
    messageError.value = 'Le message doit contenir au moins 10 caractères'
  } else {
    messageError.value = ''
  }
}

const messageState = computed(() => {
  if (!message.value) return 'default'
  return messageError.value ? 'error' : 'success'
})
</script>

<template>
  <SuFormField 
    label="Message"
    :required="true"
    :state="messageState"
    :message="messageError || 'Rédigez votre message'"
  >
    <template #default="slotProps">
      <SuTextarea 
        placeholder="Tapez votre message..."
        :rows="3"
        v-bind="slotProps"
        v-model="message"
        @blur="validateMessage"
      />
    </template>
  </SuFormField>
</template>

Avec compteur de caractères et FormField

vue
<template>
  <SuFormField 
    label="Tweet"
    message="Partagez vos pensées"
    :required="true"
  >
    <template #default="slotProps">
      <SuTextarea 
        placeholder="Que se passe-t-il ?"
        :maxLength="280"
        :showCounter="true"
        :autoResize="true"
        :minRows="2"
        :maxRows="6"
        v-bind="slotProps"
        v-model="tweet"
      />
    </template>
  </SuFormField>
</template>

Auto-resize avec FormField

vue
<template>
  <SuFormField 
    label="Description du projet"
    message="La hauteur s'ajuste automatiquement au contenu"
  >
    <template #default="slotProps">
      <SuTextarea 
        placeholder="Décrivez votre projet..."
        :autoResize="true"
        :minRows="3"
        :maxRows="12"
        v-bind="slotProps"
        v-model="projectDescription"
      />
    </template>
  </SuFormField>
</template>

Label et message personnalisés

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

<template>
  <SuFormField 
    label="Contenu" 
    :required="true"
    message="Rédigez votre article"
  >
    <template #label="{ label, required, htmlFor }">
      <label 
        :for="htmlFor" 
        class="flex items-center gap-2 font-bold text-gray-900"
      >
        <DocumentTextIcon class="w-4 h-4" />
        {{ label }}
        <span v-if="required" class="text-red-500">*</span>
      </label>
    </template>
    
    <template #default="slotProps">
      <SuTextarea 
        placeholder="Commencez à écrire..."
        :autoResize="true"
        :minRows="4"
        :maxRows="12"
        :maxLength="5000"
        :showCounter="true"
        v-bind="slotProps"
        v-model="content"
      />
    </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'">⚠️ Le contenu est trop court</span>
        <span v-else>Rédigez votre article (minimum 100 caractères)</span>
      </div>
    </template>
  </SuFormField>
</template>

Limitation de caractères avec feedback

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

const feedback = ref('')

const remainingChars = computed(() => 500 - feedback.value.length)
const feedbackState = computed(() => {
  if (!feedback.value) return 'default'
  if (feedback.value.length < 10) return 'error'
  if (feedback.value.length > 500) return 'error'
  return 'success'
})
</script>

<template>
  <SuFormField 
    label="Feedback produit"
    :required="true"
    :state="feedbackState"
    :message="remainingChars < 0 ? 'Limite dépassée' : `${remainingChars} caractères restants`"
  >
    <template #default="slotProps">
      <SuTextarea 
        placeholder="Partagez votre expérience..."
        :maxLength="500"
        :showCounter="true"
        :autoResize="true"
        :minRows="3"
        :maxRows="8"
        v-bind="slotProps"
        v-model="feedback"
      />
    </template>
  </SuFormField>
</template>

Champ désactivé ou en lecture seule

vue
<template>
  <!-- Désactivé -->
  <SuFormField 
    label="Textarea désactivé"
    :disabled="true"
    message="Ce champ est temporairement indisponible"
  >
    <template #default="slotProps">
      <SuTextarea 
        value="Ce contenu est désactivé"
        v-bind="slotProps"
      />
    </template>
  </SuFormField>
  
  <!-- Lecture seule -->
  <SuFormField 
    label="Textarea en lecture seule"
    message="Consultation uniquement"
  >
    <template #default="{ fieldId, messageId }">
      <SuTextarea 
        :id="fieldId"
        :aria-describedby="messageId"
        :readonly="true"
        value="Ce contenu ne peut pas être modifié"
      />
    </template>
  </SuFormField>
</template>

API

Props

PropTypeDefaultDescription
modelValuestringundefinedValeur du textarea (v-model)
size'sm' | 'md' | 'lg''md'Taille du textarea
state'default' | 'error' | 'success' | 'warning''default'État visuel
disabledbooleanfalseDésactive le textarea
readonlybooleanfalseTextarea en lecture seule
requiredbooleanfalseChamp requis
placeholderstringundefinedTexte de placeholder
rowsnumber3Nombre de lignes par défaut
minRowsnumber2Nombre minimum de lignes (auto-resize)
maxRowsnumber10Nombre maximum de lignes (auto-resize)
maxLengthnumberundefinedNombre maximum de caractères
showCounterbooleanfalseAfficher le compteur
autoResizebooleanfalseAjustement automatique de hauteur
spellcheckbooleantrueVérification orthographique
wrap'soft' | 'hard' | 'off''soft'Mode de retour à la ligne

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 la valeur est invalide
ariaRequiredbooleanundefinedIndique si le champ est requis
autocompletestringundefinedAttribut autocomplete HTML

Events

EventTypeDescription
@update:modelValue(value: string) => voidÉmis lors du changement de valeur (v-model)
@input(event: Event) => voidÉmis lors de la saisie
@change(event: Event) => 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
@keyup(event: KeyboardEvent) => voidÉmis lors du relâchement d'une touche

Méthodes exposées

MéthodeTypeDescription
focus()() => voidDonne le focus au textarea
select()() => voidSélectionne le texte
blur()() => voidRetire le focus
textareaRefRef<HTMLTextAreaElement>Référence à l'élément textarea natif

Accessibilité

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

✅ Fonctionnalités d'accessibilité

  • Attributs ARIA : Support complet (aria-label, aria-describedby, aria-invalid, etc.)
  • Compteur accessible : Annonces vocales pour les limites de caractères
  • Messages d'état : Via aria-describedby (messageId) avec aria-live
  • Contraste des couleurs : Ratios conformes WCAG AA (4.5:1)
  • Focus visible : Indicateur de focus clair avec outline
  • Tailles minimales : Cible tactile respectée
  • Mode sombre : Contraste adapté
  • Réduction d'animation : Respect de prefers-reduced-motion

🎯 Bonnes pratiques d'utilisation

vue
<!-- ✅ BON : Utilisation avec SuFormField pour l'accessibilité complète -->
<SuFormField 
  label="Description"
  :required="true"
  :state="descriptionError ? 'error' : 'default'"
  :message="descriptionError || 'Minimum 50 caractères'"
>
  <template #default="slotProps">
    <SuTextarea 
      placeholder="Décrivez votre projet..."
      :maxLength="500"
      :showCounter="true"
      :autoResize="true"
      v-bind="slotProps"
      v-model="description"
    />
  </template>
</SuFormField>

<!-- ✅ BON : Utilisation standalone avec attributs ARIA manuels -->
<label for="comment-textarea">Commentaire</label>
<SuTextarea 
  id="comment-textarea"
  aria-describedby="comment-help"
  aria-required="true"
  placeholder="Votre commentaire..."
  v-model="comment"
/>
<span id="comment-help">Partagez votre avis (minimum 10 caractères)</span>

<!-- ❌ MAUVAIS : Sans label ni description -->
<SuTextarea 
  placeholder="Texte..."
  v-model="text"
/>

Fonctionnalités avancées

🔄 Auto-resize intelligent

Le textarea ajuste automatiquement sa hauteur selon le contenu :

  • Hauteur minimale : Définie par minRows
  • Hauteur maximale : Définie par maxRows
  • Ajustement fluide : Transition douce
  • Performance optimisée : Calcul efficace

📊 Compteur de caractères

Le compteur offre un feedback visuel et vocal :

  • Affichage dynamique : caractères_saisis/limite_max
  • États visuels : Normal, proche limite, dépassement
  • Annonces vocales : Messages pour lecteurs d'écran
  • Couleurs adaptées : Vert, orange, rouge selon l'état
ToucheAction
TabNaviguer vers/depuis le textarea
Shift + TabNavigation inverse
Ctrl/Cmd + ASélectionner tout le texte
Ctrl/Cmd + ZAnnuler
Ctrl/Cmd + YRefaire

Exemples d'usage avancés

Formulaire de contact complet

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

const formData = ref({
  subject: '',
  message: ''
})

const errors = ref({})

const validateForm = () => {
  errors.value = {}
  
  if (!formData.value.subject || formData.value.subject.length < 5) {
    errors.value.subject = 'Minimum 5 caractères'
  }
  
  if (!formData.value.message || formData.value.message.length < 20) {
    errors.value.message = 'Minimum 20 caractères'
  }
  
  return Object.keys(errors.value).length === 0
}
</script>

<template>
  <form @submit.prevent="validateForm">
    <h2>Nous contacter</h2>
    
    <SuFormField 
      label="Sujet"
      :required="true"
      :state="errors.subject ? 'error' : 'default'"
      :message="errors.subject || 'Sujet de votre message'"
    >
      <template #default="slotProps">
        <SuInput 
          placeholder="Sujet..."
          v-bind="slotProps"
          v-model="formData.subject"
        />
      </template>
    </SuFormField>
    
    <SuFormField 
      label="Message"
      :required="true"
      :state="errors.message ? 'error' : 'default'"
      :message="errors.message || 'Décrivez votre demande en détail'"
    >
      <template #default="slotProps">
        <SuTextarea 
          placeholder="Votre message..."
          :maxLength="2000"
          :showCounter="true"
          :autoResize="true"
          :minRows="4"
          :maxRows="12"
          v-bind="slotProps"
          v-model="formData.message"
        />
      </template>
    </SuFormField>
    
    <button type="submit">Envoyer</button>
  </form>
</template>

Éditeur avec statistiques

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

const content = ref('')

const stats = computed(() => ({
  words: content.value.trim().split(/\s+/).filter(w => w.length > 0).length,
  chars: content.value.length,
  readTime: Math.ceil(content.value.trim().split(/\s+/).filter(w => w.length > 0).length / 200)
}))
</script>

<template>
  <div class="editor">
    <div class="stats">
      <span>{{ stats.words }} mots</span>
      <span>{{ stats.chars }} caractères</span>
      <span>~{{ stats.readTime }} min</span>
    </div>
    
    <SuFormField 
      label="Contenu"
      message="Rédigez votre article"
    >
      <template #default="slotProps">
        <SuTextarea 
          placeholder="Commencez à écrire..."
          :autoResize="true"
          :minRows="8"
          :maxRows="20"
          :maxLength="5000"
          :showCounter="true"
          v-bind="slotProps"
          v-model="content"
        />
      </template>
    </SuFormField>
  </div>
</template>

Composant SuTextareaField

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

vue
<template>
  <!-- Au lieu de -->
  <SuFormField label="Description" message="Décrivez votre projet">
    <template #default="slotProps">
      <SuTextarea 
        placeholder="Entrez votre description..."
        v-bind="slotProps"
        v-model="description"
      />
    </template>
  </SuFormField>
  
  <!-- Vous pouvez simplement écrire -->
  <SuTextareaField 
    label="Description"
    message="Décrivez votre projet"
    placeholder="Entrez votre description..."
    v-model="description"
  />
</template>

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

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

Publié sous licence MIT.