Textarea
A textarea component for multi-line text input with automatic resizing support. Built with accessibility and user experience in mind, featuring modern styling and seamless integration with forms.
Installation
pnpm add @helgadigitals/vera-uinpm install @helgadigitals/vera-uiyarn add @helgadigitals/vera-uiUsage
import { Textarea } from "@helgadigitals/vera-ui"export default function Example() {return ( <Textarea placeholder="Type your message here..." className="w-[300px]" />)}Examples
With Label
import { Textarea, Label } from "@helgadigitals/vera-ui"export default function BasicExample() {return ( <div className="space-y-2 w-[300px]"> <Label htmlFor="message">Message</Label> <Textarea id="message" placeholder="Enter your message..." rows={4} className="w-[300px]" /> </div>)}Disabled State
import { Textarea } from "@helgadigitals/vera-ui"export default function DisabledExample() {return ( <div className="space-y-4 w-[300px]"> <Textarea placeholder="Disabled textarea" disabled className="w-[300px]" /> <Textarea placeholder="Readonly textarea" readOnly defaultValue="This is read-only content" className="w-[300px]" /> </div>)}Controlled State
Character count: 0
import { useState } from "react"import { Textarea, Label } from "@helgadigitals/vera-ui"export default function ControlledExample() {const [value, setValue] = useState("")return ( <div className="space-y-4 w-full max-w-md"> <div className="space-y-2"> <Label htmlFor="controlled">Description</Label> <Textarea id="controlled" value={value} onChange={(e) => setValue(e.target.value)} placeholder="Enter a description..." className="w-full" /> </div> <p className="text-sm text-muted-foreground"> Character count: {value.length} </p> </div>)}With Character Limit
Keep it concise and engaging
0/200
import { useState } from "react"import { Textarea, Label } from "@helgadigitals/vera-ui"export default function CharacterLimitExample() {const [value, setValue] = useState("")const maxLength = 200return ( <div className="space-y-2 w-[300px]"> <Label htmlFor="limited">Bio (max 200 characters)</Label> <Textarea id="limited" value={value} onChange={(e) => setValue(e.target.value)} placeholder="Tell us about yourself..." maxLength={maxLength} className={`w-[300px] ${value.length >= maxLength ? "border-yellow-500" : ""}`} /> <div className="flex justify-between text-sm"> <span className="text-muted-foreground"> Keep it concise and engaging </span> <span className={`${ value.length >= maxLength ? "text-yellow-600" : "text-muted-foreground" }`}> {value.length}/{maxLength} </span> </div> </div>)}Different Sizes
import { Textarea, Label } from "@helgadigitals/vera-ui"export default function SizesExample() {return ( <div className="space-y-6 w-full max-w-md"> {/* Small textarea */} <div className="space-y-2"> <Label htmlFor="small">Small Textarea</Label> <Textarea id="small" placeholder="Small textarea..." className="min-h-[80px] w-full" /> </div> {/* Default textarea */} <div className="space-y-2"> <Label htmlFor="default">Default Textarea</Label> <Textarea id="default" placeholder="Default textarea..." className="w-full" /> </div> {/* Large textarea */} <div className="space-y-2"> <Label htmlFor="large">Large Textarea</Label> <Textarea id="large" placeholder="Large textarea..." className="min-h-[150px] w-full" /> </div> </div>)}Disabled and Read-only States
import { Textarea, Label } from "@helgadigitals/vera-ui"export default function StatesExample() {return ( <div className="space-y-6 w-full max-w-md"> {/* Disabled textarea */} <div className="space-y-2"> <Label htmlFor="disabled">Disabled Textarea</Label> <Textarea id="disabled" placeholder="This textarea is disabled" disabled className="w-full" /> </div> {/* Read-only textarea */} <div className="space-y-2"> <Label htmlFor="readonly">Read-only Textarea</Label> <Textarea id="readonly" value="This textarea is read-only and cannot be edited." readOnly className="w-full" /> </div> </div>)}Form Integration
import { useForm, Controller } from "react-hook-form"import { Textarea, Label, Button } from "@helgadigitals/vera-ui"export default function FormExample() {const { control, handleSubmit, formState: { errors }, watch } = useForm()const watchedValues = watch()const onSubmit = (data) => { console.log(data)}return ( <form onSubmit={handleSubmit(onSubmit)} className="space-y-6 w-full max-w-md"> <div className="space-y-2"> <Label htmlFor="feedback">Feedback *</Label> <Controller name="feedback" control={control} rules={{ required: "Feedback is required", minLength: { value: 10, message: "Feedback must be at least 10 characters" } }} render={({ field }) => ( <Textarea id="feedback" placeholder="Please share your feedback..." className={`w-full ${errors.feedback ? "border-destructive" : ""}`} {...field} /> )} /> {errors.feedback && ( <p className="text-sm text-destructive"> {errors.feedback.message} </p> )} </div> <div className="space-y-2"> <Label htmlFor="comments">Additional Comments</Label> <Controller name="comments" control={control} render={({ field }) => ( <Textarea id="comments" placeholder="Any additional comments (optional)..." className="w-full" {...field} /> )} /> </div> <Button type="submit" className="w-full">Submit Feedback</Button> </form>)}Auto-resizing Textarea
Lines: 1 | Characters: 0
import { useState, useRef, useEffect } from "react"import { Textarea, Label } from "@helgadigitals/vera-ui"function AutoResizeTextarea({ value, onChange, ...props }) {const textareaRef = useRef(null)useEffect(() => { if (textareaRef.current) { // Reset height to get accurate scrollHeight textareaRef.current.style.height = 'auto' // Set height to scrollHeight textareaRef.current.style.height = `${textareaRef.current.scrollHeight}px` }}, [value])return ( <Textarea ref={textareaRef} value={value} onChange={onChange} className="resize-none overflow-hidden w-full" {...props} />)}export default function AutoResizeExample() {const [value, setValue] = useState("")return ( <div className="space-y-2 w-full max-w-md"> <Label htmlFor="auto-resize">Auto-resizing Textarea</Label> <AutoResizeTextarea id="auto-resize" value={value} onChange={(e) => setValue(e.target.value)} placeholder="Start typing and watch the textarea grow..." /> </div>)}With Helper Text and Error
Provide a detailed description of your product's features and benefits.
import { useState } from "react"import { Textarea, Label } from "@helgadigitals/vera-ui"export default function WithHelperTextExample() {const [value, setValue] = useState("")const [error, setError] = useState("")const validateInput = (text) => { if (text.length > 0 && text.length < 5) { setError("Description must be at least 5 characters long") } else if (text.length > 500) { setError("Description cannot exceed 500 characters") } else { setError("") }}const handleChange = (e) => { const newValue = e.target.value setValue(newValue) validateInput(newValue)}return ( <div className="space-y-2 w-full max-w-md"> <Label htmlFor="description">Product Description</Label> <Textarea id="description" value={value} onChange={handleChange} placeholder="Describe your product..." className={`w-full ${error ? "border-destructive focus-visible:ring-destructive/20" : ""}`} /> <div className="flex justify-between text-sm"> <div className="space-y-1"> <p className="text-muted-foreground"> Provide a detailed description of your product's features and benefits. </p> {error && ( <p className="text-destructive">{error}</p> )} </div> <span className="text-muted-foreground shrink-0"> {value.length}/500 </span> </div> </div>)}Message Composer
import { useState } from "react"import { Send } from "lucide-react"import { Textarea, Button } from "@helgadigitals/vera-ui"export default function MessageComposerExample() {const [message, setMessage] = useState("")const handleSend = () => { if (message.trim()) { console.log("Sending message:", message) setMessage("") }}const handleKeyDown = (e) => { if (e.key === "Enter" && (e.metaKey || e.ctrlKey)) { e.preventDefault() handleSend() }}return ( <div className="border rounded-lg p-4 space-y-3 w-full max-w-md"> <Textarea value={message} onChange={(e) => setMessage(e.target.value)} onKeyDown={handleKeyDown} placeholder="Type your message... (Cmd/Ctrl + Enter to send)" className="border-0 shadow-none resize-none focus-visible:ring-0 p-0 w-full" rows={3} /> <div className="flex justify-between items-center"> <p className="text-xs text-muted-foreground"> {message.length > 0 && `${message.length} characters`} </p> <Button onClick={handleSend} disabled={!message.trim()} size="sm" className="gap-2" > <Send className="h-4 w-4" /> Send </Button> </div> </div>)}Code Editor Style
Lines: 5 | Characters: 82
import { useState } from "react"import { Textarea, Label } from "@helgadigitals/vera-ui"export default function CodeEditorExample() {const [code, setCode] = useState(`function greet(name) {return \`Hello, \${name}!\`;}console.log(greet("World"));`)return ( <div className="space-y-2 w-full max-w-md"> <Label htmlFor="code">JavaScript Code</Label> <Textarea id="code" value={code} onChange={(e) => setCode(e.target.value)} placeholder="Enter your code here..." className="font-mono text-sm min-h-[200px] bg-muted/30 w-full" spellCheck={false} /> </div>)}API Reference
Props
The Textarea component accepts all standard HTML textarea attributes:
Prop
Type
Accessibility
The Textarea component provides full accessibility support:
- Keyboard Navigation: Standard textarea keyboard interactions
- Screen Readers: Proper semantic textarea element with labels
- Focus Management: Clear focus indicators and proper focus handling
- Validation: Supports HTML5 validation attributes and custom validation
Labeling: Always provide proper labels for textareas using the label element with htmlFor attribute or aria-label for screen reader accessibility.
// Good: Properly labeled textarea
<div className="space-y-2">
<Label htmlFor="description">Description</Label>
<Textarea id="description" />
</div>
// Also good: Using aria-label
<Textarea aria-label="Enter your comments" />Field Sizing
The textarea component includes modern CSS field-sizing: content support for automatic content-based sizing where supported by browsers:
// The textarea will automatically adjust height based on content
<Textarea placeholder="This will auto-resize..." />Best Practices
Provide Clear Labels
Always include descriptive labels and helpful placeholder text:
// Good: Clear label and helpful placeholder
<div className="space-y-2">
<Label htmlFor="feedback">Customer Feedback</Label>
<Textarea
id="feedback"
placeholder="Please share your experience with our product..."
/>
</div>Set Appropriate Size
Use appropriate sizing for the expected content:
// Short content
<Textarea className="min-h-[80px]" placeholder="Brief comment..." />
// Long content
<Textarea className="min-h-[200px]" placeholder="Detailed description..." />Include Character Limits
Show character counts for limited inputs:
<div className="space-y-2">
<Textarea maxLength={500} />
<div className="text-sm text-muted-foreground text-right">
{value.length}/500
</div>
</div>Handle Validation
Provide clear validation feedback:
<Textarea
className={error ? "border-destructive" : ""}
aria-invalid={!!error}
aria-describedby={error ? "error-message" : undefined}
/>
{error && (
<p id="error-message" className="text-sm text-destructive">
{error}
</p>
)}Styling
The Textarea component supports extensive customization:
// Custom height
<Textarea className="min-h-[100px]" />
// Code editor style
<Textarea className="font-mono bg-muted/30" />
// Rounded corners
<Textarea className="rounded-xl" />
// No resize
<Textarea className="resize-none" />Common Patterns
Auto-save with Debouncing
import { useState, useEffect } from "react"
import { useDebounce } from "use-debounce"
function AutoSaveTextarea() {
const [value, setValue] = useState("")
const [debouncedValue] = useDebounce(value, 500)
useEffect(() => {
if (debouncedValue) {
// Auto-save logic here
console.log("Auto-saving:", debouncedValue)
}
}, [debouncedValue])
return (
<Textarea
value={value}
onChange={(e) => setValue(e.target.value)}
placeholder="Your content will be auto-saved..."
/>
)
}Switch
A switch component built on Radix UI's Switch primitive that allows users to toggle between on and off states. Perfect for boolean settings, preferences, and feature toggles.
Navigation Components
Essential components for helping users navigate through your application. From simple breadcrumbs to complex sidebar systems, these components provide flexible navigation patterns.