Skip to content

Textarea Field

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

Exemples d'utilisation

Textarea de base

Textarea simple

vue
<script setup>
const description = ref('')
</script>

<template>
  <SuTextarea 
    label="Description"
    placeholder="Entrez votre description..."
    message="Décrivez votre projet en quelques phrases"
    v-model="description"
  />
</template>

Avec compteur de caractères

Compteur de caractères

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

<template>
  <SuTextarea 
    label="Commentaire"
    placeholder="Votre commentaire..."
    :maxLength="200"
    :showCounter="true"
    message="Partagez votre avis sur ce produit"
    v-model="comment"
  />
</template>

Ajustement automatique de hauteur

Auto-resize

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

<template>
  <SuTextarea 
    label="Message"
    placeholder="Tapez votre message..."
    :autoResize="true"
    :minRows="2"
    :maxRows="8"
    message="La hauteur s'ajuste automatiquement au contenu"
    v-model="message"
  />
</template>

Tailles

Tailles disponibles

vue
<template>
  <SuTextarea size="sm" label="Small" placeholder="Petit textarea" />
  <SuTextarea size="md" label="Medium" placeholder="Textarea moyen" />
  <SuTextarea size="lg" label="Large" placeholder="Grand textarea" />
</template>

États et validation

États de validation

vue
<template>
  <SuTextarea 
    state="default"
    label="État par défaut"
    message="Texte d'aide pour guider l'utilisateur"
  />
  <SuTextarea 
    state="error"
    label="État d'erreur"
    message="Ce champ contient une erreur"
  />
  <SuTextarea 
    state="success"
    label="État de succès"
    message="Contenu valide !"
  />
  <SuTextarea 
    state="warning"
    label="État d'avertissement"
    message="Attention au contenu"
  />
</template>

Limitation de caractères avec feedback

Gestion des limites

vue
<script setup>
const tweet = ref('')
</script>

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

États disabled et readonly

États spéciaux

vue
<template>
  <SuTextarea 
    label="Textarea désactivé"
    :disabled="true"
    value="Ce contenu est désactivé"
    message="Ce champ est temporairement indisponible"
  />
  <SuTextarea 
    label="Textarea en lecture seule"
    :readonly="true"
    value="Ce contenu ne peut pas être modifié"
    message="Consultation uniquement"
  />
  <SuTextarea 
    label="Champ requis"
    :required="true"
    placeholder="Ce champ est obligatoire"
    message="Veuillez remplir ce champ"
  />
</template>

API

Props

PropTypeDefaultDescription
size'sm' | 'md' | 'lg''md'Taille du textarea
state'default' | 'error' | 'success' | 'warning''default'État visuel du textarea
disabledbooleanfalseDésactive le textarea
readonlybooleanfalseTextarea en lecture seule
requiredbooleanfalseChamp requis
placeholderstringundefinedTexte de placeholder
valuestringundefinedValeur du textarea
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 de caractères
autoResizebooleanfalseAjustement automatique de la hauteur
labelstringundefinedLabel du textarea
messagestringundefinedMessage affiché (style déterminé par le state)
spellcheckbooleantrueVérification orthographique
wrap'soft' | 'hard' | 'off''soft'Mode de retour à la ligne

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
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 du textarea
textareaRefRef<HTMLTextAreaElement>Référence à l'élément textarea

Accessibilité

Le composant Textarea respecte les normes WCAG 2.1 AA :

✅ Fonctionnalités d'accessibilité

  • Labels associés : Chaque textarea a un label correctement associé via for/id
  • Attributs ARIA : Support complet des attributs ARIA (aria-label, aria-describedby, aria-invalid, etc.)
  • Messages d'état : Messages d'erreur/succès/avertissement avec aria-live
  • Compteur accessible : Annonces vocales pour les limites de caractères
  • Contraste des couleurs : Ratios de contraste conformes WCAG AA (4.5:1 minimum)
  • Focus visible : Indicateur de focus clair et contrasté
  • Tailles minimales : Respecte les tailles minimales de cible tactile
  • Mode sombre : Contraste adapté automatiquement
  • Contraste élevé : Support de prefers-contrast: high
  • Réduction d'animation : Respect de prefers-reduced-motion

🎯 Bonnes pratiques

vue
<!-- Textarea avec validation et accessibilité -->
<SuTextarea 
  label="Description du projet"
  :required="true"
  :maxLength="500"
  :showCounter="true"
  :autoResize="true"
  placeholder="Décrivez votre projet..."
  message="Minimum 50 caractères recommandés"
  autocomplete="off"
  v-model="description"
/>

<!-- Textarea avec gestion d'erreur -->
<SuTextarea 
  label="Commentaire"
  :maxLength="1000"
  :showCounter="true"
  :state="hasError ? 'error' : 'default'"
  :message="hasError ? 'Le commentaire ne peut pas être vide' : 'Partagez votre avis'"
  v-model="comment"
/>

<!-- Textarea auto-resize avec limites -->
<SuTextarea 
  label="Message"
  :autoResize="true"
  :minRows="3"
  :maxRows="12"
  :maxLength="2000"
  :showCounter="true"
  placeholder="Rédigez votre message..."
  v-model="message"
/>

Fonctionnalités avancées

🔄 Auto-resize intelligent

Le textarea peut ajuster automatiquement sa hauteur selon le contenu :

  • Hauteur minimale : Définie par minRows
  • Hauteur maximale : Définie par maxRows
  • Ajustement fluide : Transition douce lors du redimensionnement
  • Performance optimisée : Calcul efficace de la hauteur

📊 Compteur de caractères

Le compteur offre un feedback visuel et vocal :

  • Affichage dynamique : caractères_saisis/limite_max
  • États visuels : Normal, proche de la limite, dépassement
  • Annonces vocales : Messages pour les lecteurs d'écran
  • Couleurs adaptées : Vert, orange, rouge selon l'état

🎨 États visuels

  • Normal : Bordure grise, texte noir
  • Proche de la limite : Compteur orange (≤10% restant)
  • Dépassement : Bordure et texte rouge, compteur rouge
  • Validation : Bordures colorées selon l'état (error, success, warning)
ToucheAction
TabNaviguer vers/depuis le textarea
Shift + TabNavigation inverse
Ctrl/Cmd + ASélectionner tout le texte
Ctrl/Cmd + ZAnnuler la dernière action
Ctrl/Cmd + YRefaire l'action

Exemples d'usage avancés

Formulaire de contact

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

const subject = ref('')
const message = ref('')

const isValid = computed(() => {
  return subject.value.length >= 5 && message.value.length >= 20
})

const subjectState = computed(() => {
  if (!subject.value) return 'default'
  return subject.value.length >= 5 ? 'success' : 'error'
})

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

<template>
  <form class="contact-form">
    <h2>Nous contacter</h2>
    
    <SuInput 
      label="Sujet"
      :required="true"
      :state="subjectState"
      :message="subject.length < 5 && subject.length > 0 ? 'Minimum 5 caractères' : undefined"
      placeholder="Sujet de votre message"
      v-model:value="subject"
    />
    
    <SuTextarea 
      label="Message"
      :required="true"
      :maxLength="2000"
      :showCounter="true"
      :autoResize="true"
      :minRows="4"
      :maxRows="12"
      :state="messageState"
      :message="message.length < 20 && message.length > 0 ? 'Minimum 20 caractères' : 'Décrivez votre demande en détail'"
      placeholder="Décrivez votre demande..."
      v-model="message"
    />
    
    <button 
      type="submit" 
      :disabled="!isValid"
      class="submit-button"
    >
      Envoyer le message
    </button>
  </form>
</template>

<style scoped>
.contact-form {
  max-width: 500px;
  margin: 0 auto;
  display: flex;
  flex-direction: column;
  gap: 1.5rem;
}

.submit-button {
  padding: 0.75rem 1.5rem;
  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>

Éditeur de texte simple

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

const content = ref('')
const wordCount = computed(() => {
  return content.value.trim().split(/\s+/).filter(word => word.length > 0).length
})

const characterCount = computed(() => content.value.length)
const estimatedReadingTime = computed(() => {
  const wordsPerMinute = 200
  return Math.ceil(wordCount.value / wordsPerMinute)
})
</script>

<template>
  <div class="editor">
    <div class="editor-header">
      <h3>Éditeur de texte</h3>
      <div class="editor-stats">
        <span>{{ wordCount }} mots</span>
        <span>{{ characterCount }} caractères</span>
        <span>~{{ estimatedReadingTime }} min de lecture</span>
      </div>
    </div>
    
    <SuTextarea 
      label="Contenu"
      :autoResize="true"
      :minRows="8"
      :maxRows="20"
      :maxLength="5000"
      :showCounter="true"
      placeholder="Commencez à écrire votre article..."
      message="Rédigez votre contenu. L'éditeur s'adapte automatiquement."
      :spellcheck="true"
      v-model:value="content"
    />
  </div>
</template>

<style scoped>
.editor {
  max-width: 800px;
  margin: 0 auto;
}

.editor-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-bottom: 1rem;
  padding-bottom: 1rem;
  border-bottom: 1px solid #e5e7eb;
}

.editor-stats {
  display: flex;
  gap: 1rem;
  font-size: 0.875rem;
  color: #6b7280;
}
</style>

Textarea avec validation en temps réel

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

const feedback = ref('')
const errors = ref<string[]>([])

const validateContent = (content: string) => {
  const newErrors: string[] = []
  
  if (content.length < 10) {
    newErrors.push('Le message doit contenir au moins 10 caractères')
  }
  
  if (content.length > 500) {
    newErrors.push('Le message ne peut pas dépasser 500 caractères')
  }
  
  const wordCount = content.trim().split(/\s+/).filter(word => word.length > 0).length
  if (wordCount < 3) {
    newErrors.push('Le message doit contenir au moins 3 mots')
  }
  
  return newErrors
}

const state = computed(() => {
  if (!feedback.value) return 'default'
  return errors.value.length > 0 ? 'error' : 'success'
})

const message = computed(() => {
  if (errors.value.length > 0) {
    return errors.value[0]
  }
  if (feedback.value && errors.value.length === 0) {
    return 'Message valide !'
  }
  return 'Rédigez votre feedback'
})

watch(feedback, (newValue) => {
  errors.value = validateContent(newValue)
}, { immediate: true })
</script>

<template>
  <div class="feedback-form">
    <h3>Feedback produit</h3>
    
    <SuTextarea 
      label="Votre avis"
      :required="true"
      :maxLength="500"
      :showCounter="true"
      :autoResize="true"
      :minRows="3"
      :maxRows="8"
      :state="state"
      :message="message"
      placeholder="Partagez votre expérience avec ce produit..."
      v-model:value="feedback"
    />
    
    <div class="form-actions">
      <button 
        type="submit" 
        :disabled="errors.length > 0 || !feedback.trim()"
        class="submit-button"
      >
        Envoyer le feedback
      </button>
    </div>
  </div>
</template>

<style scoped>
.feedback-form {
  max-width: 600px;
  margin: 0 auto;
  display: flex;
  flex-direction: column;
  gap: 1.5rem;
}

.form-actions {
  display: flex;
  justify-content: flex-end;
}

.submit-button {
  padding: 0.75rem 1.5rem;
  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.