Textarea
A styled multi-line text input with smooth focus transitions and shadow effects.
Basic
With label
With character count
Up to 160 characters.
Error state
Feedback is required.
Disabled
Installation
pnpm
pnpm dlx @hex-core/cli add textareaWith label
Textarea paired with a label
tsx
<div className="grid gap-1.5">
<Label htmlFor="message">Message</Label>
<Textarea id="message" placeholder="Type your message..." />
</div>With character counter
Controlled textarea with a live character count anchored beneath the field. Common for bio/description inputs with a hard cap.
tsx
import { useState } from "react";
const MAX_LENGTH = 280;
export function BioField() {
const [value, setValue] = useState("");
const remaining = MAX_LENGTH - value.length;
const counterId = "bio-counter";
return (
<div className="grid gap-1.5">
<Label htmlFor="bio">Bio</Label>
<Textarea
id="bio"
rows={4}
maxLength={MAX_LENGTH}
value={value}
onChange={(e) => setValue(e.target.value)}
placeholder="Tell us a little about yourself"
aria-describedby={counterId}
/>
<p
id={counterId}
className={`text-xs tabular-nums ${remaining < 20 ? "text-destructive" : "text-muted-foreground"}`}
aria-live="polite"
>
{value.length} / {MAX_LENGTH}
</p>
</div>
);
}API Reference
| Prop | Type | Default | Description |
|---|---|---|---|
placeholder | string | — | Placeholder text |
rows | number | 3 | Number of visible text rows |
disabled | boolean | false | Disable the textarea |
value | string | — | Controlled textarea value |
defaultValue | string | — | Default value for uncontrolled usage |
onChange | function | — | Change handler: (e: ChangeEvent<HTMLTextAreaElement>) => void |
className | string | — | Additional CSS classes |
AI Guidance
When to use
Use for multi-line text input: comments, descriptions, messages, notes. Always pair with a Label.
When not to use
Don't use for single-line input (use Input). Don't use for rich text editing.
Common mistakes
- Missing associated Label
- Not setting a reasonable min-height or rows
- Reaching for Textarea for short single-line input — Input is the right primitive when the content is ≤80 characters
Accessibility
Always pair with a Label using htmlFor/id. Consider aria-describedby for character limits.
Token budget: 250
Verified against @hex-core/components@1.12.0