Skip to content

Textarea

Flexible Textarea component with character counter, automatic height adjustment and compliance with W3C accessibility standards.

Usage examples

Basic Textarea

Simple textarea

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

<template>
  <SuTextarea 
    label="Description"
    placeholder="Enter your description..."
    message="Describe your project in a few sentences"
    v-model="description"
  />
</template>

With character counter

Character counter

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

<template>
  <SuTextarea 
    label="Comment"
    placeholder="Your comment..."
    :maxLength="200"
    :showCounter="true"
    message="Share your opinion about this product"
    v-model="comment"
  />
</template>

Automatic height adjustment

Auto-resize

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

<template>
  <SuTextarea 
    label="Message"
    placeholder="Type your message..."
    :autoResize="true"
    :minRows="2"
    :maxRows="8"
    message="Height adjusts automatically to content"
    v-model="message"
  />
</template>

Sizes

Available sizes

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

States and validation

Validation states

vue
<template>
  <SuTextarea 
    state="default"
    label="Default state"
    message="Help text to guide the user"
  />
  <SuTextarea 
    state="error"
    label="Error state"
    message="This field contains an error"
  />
  <SuTextarea 
    state="success"
    label="Success state"
    message="Valid content!"
  />
  <SuTextarea 
    state="warning"
    label="Warning state"
    message="Be careful with content"
  />
</template>

API

Props

PropTypeDefaultDescription
size'sm' | 'md' | 'lg''md'Textarea size
state'default' | 'error' | 'success' | 'warning''default'Textarea visual state
disabledbooleanfalseDisable the textarea
readonlybooleanfalseRead-only textarea
requiredbooleanfalseRequired field
placeholderstringundefinedPlaceholder text
valuestringundefinedTextarea value
rowsnumber3Default number of rows
minRowsnumber2Minimum number of rows (auto-resize)
maxRowsnumber10Maximum number of rows (auto-resize)
maxLengthnumberundefinedMaximum number of characters
showCounterbooleanfalseShow character counter
autoResizebooleanfalseAutomatic height adjustment
labelstringundefinedTextarea label
messagestringundefinedDisplayed message
spellcheckbooleantrueSpell checking
wrap'soft' | 'hard' | 'off''soft'Line wrapping mode

Events

EventTypeDescription
@update:modelValue(value: string) => voidEmitted when value changes (v-model)
@input(event: Event) => voidEmitted on input
@change(event: Event) => voidEmitted on change
@focus(event: FocusEvent) => voidEmitted on focus
@blur(event: FocusEvent) => voidEmitted on blur
@keydown(event: KeyboardEvent) => voidEmitted on key press
@keyup(event: KeyboardEvent) => voidEmitted on key release

Accessibility

The Textarea component follows WCAG 2.1 AA standards:

✅ Accessibility features

  • Associated labels: Each textarea has a properly associated label via for/id
  • ARIA attributes: Complete support for ARIA attributes
  • State messages: Error/success/warning messages with aria-live
  • Accessible counter: Voice announcements for character limits
  • Color contrast: WCAG AA compliant contrast ratios (4.5:1 minimum)
  • Visible focus: Clear and contrasted focus indicator
  • Minimum sizes: Respects minimum touch target sizes
  • Dark mode: Automatically adapted contrast
  • High contrast: Support for prefers-contrast: high
  • Reduced motion: Respects prefers-reduced-motion

🎯 Best practices

vue
<!-- Textarea with validation and accessibility -->
<SuTextarea 
  label="Project description"
  :required="true"
  :maxLength="500"
  :showCounter="true"
  :autoResize="true"
  placeholder="Describe your project..."
  message="Minimum 50 characters recommended"
  autocomplete="off"
  v-model="description"
/>

<!-- Textarea with error handling -->
<SuTextarea 
  label="Comment"
  :maxLength="1000"
  :showCounter="true"
  :state="hasError ? 'error' : 'default'"
  :message="hasError ? 'Comment cannot be empty' : 'Share your opinion'"
  v-model="comment"
/>

<!-- Auto-resize textarea with limits -->
<SuTextarea 
  label="Message"
  :autoResize="true"
  :minRows="3"
  :maxRows="12"
  :maxLength="2000"
  :showCounter="true"
  placeholder="Write your message..."
  v-model="message"
/>

Advanced features

🔄 Smart auto-resize

The textarea can automatically adjust its height based on content:

  • Minimum height: Defined by minRows
  • Maximum height: Defined by maxRows
  • Smooth adjustment: Smooth transition during resizing
  • Optimized performance: Efficient height calculation

📊 Character counter

The counter provides visual and voice feedback:

  • Dynamic display: characters_typed/max_limit
  • Visual states: Normal, near limit, over limit
  • Voice announcements: Messages for screen readers
  • Adapted colors: Green, orange, red based on state

🎨 Visual states

  • Normal: Gray border, black text
  • Near limit: Orange counter (≤10% remaining)
  • Over limit: Red border and text, red counter
  • Validation: Colored borders based on state (error, success, warning)

Keyboard navigation

KeyAction
TabNavigate to/from textarea
Shift + TabReverse navigation
Ctrl/Cmd + ASelect all text
Ctrl/Cmd + ZUndo last action
Ctrl/Cmd + YRedo action

Advanced usage examples

Contact form

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>Contact us</h2>
    
    <SuInput 
      label="Subject"
      :required="true"
      :state="subjectState"
      :message="subject.length < 5 && subject.length > 0 ? 'Minimum 5 characters' : undefined"
      placeholder="Message subject"
      v-model="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 characters' : 'Describe your request in detail'"
      placeholder="Describe your request..."
      v-model="message"
    />
    
    <button 
      type="submit" 
      :disabled="!isValid"
      class="submit-button"
    >
      Send 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>

Simple text editor

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>Text editor</h3>
      <div class="editor-stats">
        <span>{{ wordCount }} words</span>
        <span>{{ characterCount }} characters</span>
        <span>~{{ estimatedReadingTime }} min read</span>
      </div>
    </div>
    
    <SuTextarea 
      label="Content"
      :autoResize="true"
      :minRows="8"
      :maxRows="20"
      :maxLength="5000"
      :showCounter="true"
      placeholder="Start writing your article..."
      message="Write your content. The editor adapts automatically."
      :spellcheck="true"
      v-model="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>

Publié sous licence MIT.