UITextarea
UITextarea
A versatile textarea component that provides multi-line text input capabilities with features like auto-resize, character count, validation, and custom styling.
<template> <UITextarea v-model="text" :placeholder="'Enter your message...'" :rows="4" :maxlength="500" :autosize="true" :show-count="true" @change="handleChange" > <template #label> Message </template>
<template #hint> Maximum 500 characters </template> </UITextarea></template>
<script setup lang="ts">const text = ref('')
const handleChange = (value: string) => { console.log('Text changed:', value)}</script>Props
modelValue(string): Input valueplaceholder(string): Placeholder textrows(number): Initial number of rowsmaxlength(number): Maximum charactersminlength(number): Minimum charactersautosize(boolean): Auto-resize heightshowCount(boolean): Show character countdisabled(boolean): Disable inputreadonly(boolean): Read-only modeerror(boolean): Error staterequired(boolean): Required fieldname(string): Input namelabel(string): Input labelhint(string): Helper text
Events
update:modelValue: Value updatedchange: Value changedinput: Input eventfocus: Input focusedblur: Input blurredkeydown: Key pressedkeyup: Key released
Slots
label: Custom label contenthint: Custom hint contentprefix: Content before inputsuffix: Content after inputcount: Custom character count
Usage Examples
- Basic Textarea:
<template> <UITextarea v-model="comment" placeholder="Write a comment..." rows="3" /></template>
<script setup>const comment = ref('')</script>- Form Field with Validation:
<template> <form @submit.prevent="handleSubmit"> <UITextarea v-model="feedback" :error="errors.feedback" :required="true" :minlength="10" :maxlength="1000" :show-count="true" class="mb-4" > <template #label> <span class="flex items-center space-x-2"> <span>Feedback</span> <InfoIcon class="w-4 h-4 text-gray-400" v-tooltip="'Please provide detailed feedback'" /> </span> </template>
<template #hint> <span :class="[ 'text-sm', errors.feedback ? 'text-error-600' : 'text-gray-500' ]" > {{ errors.feedback || 'Minimum 10 characters' }} </span> </template>
<template #count="{ current, max }"> <span class="text-sm text-gray-500"> {{ current }}/{{ max }} characters </span> </template> </UITextarea>
<UIButton type="submit" :disabled="!isValid" > Submit Feedback </UIButton> </form></template>
<script setup>const feedback = ref('')const errors = reactive({ feedback: ''})
const isValid = computed(() => { return feedback.value.length >= 10 && feedback.value.length <= 1000 && !errors.feedback})
const validateFeedback = () => { if (!feedback.value) { errors.feedback = 'Feedback is required' } else if (feedback.value.length < 10) { errors.feedback = 'Feedback must be at least 10 characters' } else { errors.feedback = '' }}
const handleSubmit = () => { validateFeedback() if (isValid.value) { // Submit form }}</script>- Rich Text Input:
<template> <div class="space-y-4"> <UITextarea v-model="content" :autosize="true" :rows="6" class="editor" > <template #label> Content </template>
<template #prefix> <div class="flex items-center space-x-2 p-2 border-b"> <UIButton v-for="format in formats" :key="format.id" size="sm" :variant="isFormatActive(format.id) ? 'primary' : 'default'" @click="toggleFormat(format.id)" > <component :is="format.icon" class="w-4 h-4" /> </UIButton> </div> </template> </UITextarea>
<div class="flex justify-between items-center"> <UIButton size="sm" @click="clearContent" > Clear </UIButton>
<div class="text-sm text-gray-500"> {{ wordCount }} words </div> </div> </div></template>
<script setup>import { BoldIcon, ItalicIcon, UnderlineIcon, ListIcon} from '@gohighlevel/ghl-icons/24/outline'
const content = ref('')const activeFormats = ref(new Set())
const formats = [ { id: 'bold', icon: BoldIcon }, { id: 'italic', icon: ItalicIcon }, { id: 'underline', icon: UnderlineIcon }, { id: 'list', icon: ListIcon }]
const wordCount = computed(() => { return content.value.trim().split(/\s+/).length})
const isFormatActive = (format) => { return activeFormats.value.has(format)}
const toggleFormat = (format) => { if (activeFormats.value.has(format)) { activeFormats.value.delete(format) } else { activeFormats.value.add(format) }}
const clearContent = () => { content.value = '' activeFormats.value.clear()}</script>
<style>.editor { @apply font-mono;}</style>Best Practices
-
User Experience:
- Clear placeholder
- Appropriate sizing
- Visual feedback
- Error states
-
Accessibility:
- ARIA labels
- Focus states
- Error messages
- Keyboard support
-
Validation:
- Input constraints
- Error handling
- Clear feedback
- Real-time validation
-
Performance:
- Debounce updates
- Optimize resizing
- Memory management
- Event handling
Common Use Cases
- Comment Form:
<template> <div class="comment-form space-y-4"> <UITextarea v-model="comment" :placeholder="'Write a comment...'" :autosize="true" :minlength="2" :maxlength="500" :show-count="true" class="w-full" > <template #suffix> <div class="flex items-center space-x-2 p-2"> <UIButton size="sm" :disabled="!isValid" @click="submitComment" > Post </UIButton> </div> </template> </UITextarea> </div></template>- Bio Editor:
<template> <div class="bio-editor"> <UITextarea v-model="profile.bio" :placeholder="'Tell us about yourself...'" :maxlength="160" :show-count="true" :autosize="true" > <template #label> Bio </template>
<template #hint> <span class="text-sm text-gray-500"> This will appear on your public profile </span> </template>
<template #count="{ current, max }"> <span :class="[ 'text-sm', current > max * 0.9 ? 'text-warning-600' : 'text-gray-500' ]" > {{ current }}/{{ max }} </span> </template> </UITextarea> </div></template>- Message Composer:
<template> <div class="message-composer"> <UITextarea v-model="message" :placeholder="'Type your message...'" :autosize="true" :maxlength="2000" class="composer" @keydown.enter.prevent="sendMessage" > <template #prefix> <div class="flex items-center space-x-2 p-2"> <UIButton v-for="action in actions" :key="action.id" size="sm" variant="ghost" @click="action.handler" > <component :is="action.icon" class="w-5 h-5" /> </UIButton> </div> </template>
<template #suffix> <div class="flex items-center p-2"> <UIButton type="primary" :disabled="!message.trim()" @click="sendMessage" > Send </UIButton> </div> </template> </UITextarea> </div></template>
<script setup>import { ImageIcon, AttachmentIcon, EmojiIcon} from '@gohighlevel/ghl-icons/24/outline'
const message = ref('')
const actions = [ { id: 'image', icon: ImageIcon, handler: () => attachImage() }, { id: 'file', icon: AttachmentIcon, handler: () => attachFile() }, { id: 'emoji', icon: EmojiIcon, handler: () => toggleEmojiPicker() }]
const sendMessage = () => { if (!message.value.trim()) return // Send message logic message.value = ''}</script>
<style>.composer { @apply rounded-lg border-gray-200;}
.composer:focus-within { @apply border-primary-500 ring-1 ring-primary-500;}</style>