Textarea
A multi-line text input component with 3 variants, 4 validation states, 3 sizes, label and helper text support, resize control, and character counting. Built with forwardRef for seamless form library integration.
Live Preview
Interact with the Textarea component in real time. Adjust variant, state, and size to see changes instantly.
This is helper text
1<Textarea2variant="default"3state="default"4inputSize="md"5label="Label"6placeholder="Type something..."7rows={3}8 helperText="This is helper text"9/>Installation
Install the Aidash Components package using your preferred package manager.
$ pnpm add @aidash/componentsImport
Import the Textarea component into your file.
import { Textarea } from '@/components/aidash/textarea';Usage
The simplest way to use Textarea with default props.
1<Textarea placeholder="Write your message..." />2<Textarea label="Description" placeholder="Add a description for your project" />Variants
Three visual variants for different design contexts and surface treatments.
1<Textarea variant="default" label="Default" placeholder="Surface background with border" />2<Textarea variant="filled" label="Filled" placeholder="Sunken background, no visible border" />3<Textarea variant="outlined" label="Outlined" placeholder="Transparent background, 2px border" />default for standard forms, filled for textareas on elevated surfaces where a visible border would be too heavy, and outlined for textareas that need stronger visual emphasis.Sizes
Three sizes that control font size and padding to fit different layout contexts.
1<Textarea inputSize="sm" label="Small" placeholder="Compact font and padding" />2<Textarea inputSize="md" label="Medium" placeholder="Default font and padding" />3<Textarea inputSize="lg" label="Large" placeholder="Spacious font and padding" />States
Four validation states with color-coded borders and feedback messages.
Default
Optional field
Error
Description is required and must be at least 20 characters
Success
Warning
Your message contains special characters
1{/* Default with helper text */}2<Textarea label="Notes" helperText="Optional field" />3 4{/* Error state */}5<Textarea label="Description" state="error" errorText="Description is required" />6 7{/* Success state */}8<Textarea label="Bio" state="success" />9 10{/* Warning state */}11<Textarea label="Message" state="warning" helperText="Contains special characters" />Features
Advanced features including labels, rows, character counting, resize control, and more.
Label & Helper Text
Provide context for collaborators
1<Textarea2label="Project Description"3placeholder="Describe your project in detail..."4helperText="Provide context for collaborators"5rows={3}6/>Custom Rows
1<Textarea label="Short Input" placeholder="2 rows..." rows={2} />2<Textarea label="Default" placeholder="4 rows (default)..." rows={4} />3<Textarea label="Tall Input" placeholder="8 rows..." rows={8} />Character Count
Keep it concise
0/280
1<Textarea2label="Tweet"3placeholder="What's happening?"4showCharCount5maxLength={280}6helperText="Keep it concise"7rows={3}8/>Resize Control
1<Textarea label="Vertical" resize="vertical" /> {/* default */}2<Textarea label="No Resize" resize="none" />3<Textarea label="Both Directions" resize="both" />Full Width & Disabled
1{/* Full width is the default */}2<Textarea label="Full Width" placeholder="Stretches to fill container" />3 4{/* Inline mode */}5<Textarea label="Inline" placeholder="Inline textarea" fullWidth={false} />6 7{/* Disabled state */}8<Textarea label="Disabled" disabled defaultValue="Read-only content" />resize="none" with a JavaScript onInput handler that adjusts style.height based on scrollHeight. The ref forwarding makes this straightforward.Accessibility
Built-in accessibility features for label association, error announcements, and keyboard navigation.
Label Association
When a label prop is provided, it is automatically linked to the textarea via htmlFor/id. A unique ID is generated if none is supplied.
Error Announcements
Error and helper text are rendered below the textarea, providing visual feedback. Pair with aria-describedby for screen reader announcements in your form wrapper.
Keyboard Navigation
Standard Tab navigation to enter the textarea. The textarea shows a brand-colored focus ring on focus. Disabled textareas are skipped in the tab order via the native disabled attribute.
Ref Forwarding
Supports forwardRef for programmatic focus, selection, and integration with form libraries like React Hook Form.
1{/* Label is automatically linked via htmlFor/id */}2<Textarea label="Description" placeholder="Enter description..." />3 4{/* Custom ID for manual aria-describedby */}5<Textarea id="bio" label="Bio" aria-describedby="bio-hint" />6<p id="bio-hint">Write a short biography (max 500 characters)</p>7 8{/* Ref forwarding for programmatic focus */}9const textareaRef = useRef<HTMLTextAreaElement>(null);10<Textarea ref={textareaRef} label="Focus Me" />API Reference
Complete list of props accepted by Textarea.
| Prop | Type | Default | Description |
|---|---|---|---|
| variant | 'default' | 'filled' | 'outlined' | 'default' | Visual style of the textarea |
| state | 'default' | 'error' | 'success' | 'warning' | 'default' | Validation state with corresponding border color |
| inputSize | 'sm' | 'md' | 'lg' | 'md' | Size of the textarea (font, padding) |
| label | string | — | Label displayed above the textarea |
| helperText | string | — | Helper text shown below the textarea |
| errorText | string | — | Error message (sets state to error style) |
| successText | string | — | Success message (sets state to success style) |
| warningText | string | — | Warning message (sets state to warning style) |
| showCharCount | boolean | false | Show a character count below the textarea |
| maxLength | number | — | Maximum character limit (HTML + counter) |
| fullWidth | boolean | true | Stretch textarea to fill container width |
| resize | 'none' | 'vertical' | 'horizontal' | 'both' | 'vertical' | Resize behavior of the textarea |
| containerClassName | string | '' | Additional CSS classes on the wrapper div |
| placeholder | string | — | Placeholder text (standard HTML) |
| rows | number | 4 | Number of visible text rows |
| disabled | boolean | false | Disable the textarea |
| onChange | (e: ChangeEvent) => void | — | Change handler |
| value | string | — | Controlled value |
| className | string | '' | Additional CSS classes on the textarea element |
TextareaHTMLAttributes<HTMLTextAreaElement> (with size omitted in favor of inputSize), so it also accepts all standard HTML textarea attributes such as placeholder, rows, cols, wrap, and aria-* props.Examples
Realistic production examples showing the Textarea in context.
Comment Form
0/500
1<Input label="Name" placeholder="Your name" />2<Textarea3label="Comment"4placeholder="Write your comment..."5rows={4}6showCharCount7maxLength={500}8/>9<Button>Post Comment</Button>Feedback Form
Your feedback helps us improve
0/1000
1<Select label="Rating" options={ratingOptions} />2<Textarea3variant="filled"4label="Your Feedback"5placeholder="Tell us what you think..."6rows={5}7showCharCount8maxLength={1000}9helperText="Your feedback helps us improve"10/>11<Button>Submit Feedback</Button>Bio Editor
Displayed on your public profile
70/160
1<Textarea2variant="outlined"3label="Bio"4placeholder="Write a short bio about yourself..."5rows={4}6showCharCount7maxLength={160}8helperText="Displayed on your public profile"9/>Best Practices
Guidelines for using textareas effectively in your interface.
- Always provide a label for screen readers and usability
- Set appropriate rows to hint at expected content length
- Use showCharCount with maxLength for length-limited content
- Show validation feedback inline using errorText
- Use resize="none" when the layout should not shift
- Use placeholder text that shows expected format or tone
- Use a Textarea for single-line input (use Input instead)
- Use placeholder text as a substitute for labels
- Set rows=1 when the expected content is multi-line
- Rely only on color to convey validation state
- Allow horizontal resize in a single-column form layout
- Validate on every keystroke without debouncing
Source Code
Full source code for the Textarea component.
'use client';
import { useState, useId, forwardRef } from 'react';
import {
type InputVariant,
type InputState,
type InputSize,
variantStyles,
sizeStyles,
stateColors,
} from './input-shared';
export interface TextareaProps extends Omit<React.TextareaHTMLAttributes<HTMLTextAreaElement>, 'size'> {
variant?: InputVariant;
state?: InputState;
inputSize?: InputSize;
label?: string;
helperText?: string;
errorText?: string;
successText?: string;
warningText?: string;
showCharCount?: boolean;
maxLength?: number;
fullWidth?: boolean;
resize?: 'none' | 'vertical' | 'horizontal' | 'both';
containerClassName?: string;
}
const resizeStyles: Record<string, string> = {
none: 'resize-none',
vertical: 'resize-y',
horizontal: 'resize-x',
both: 'resize',
};
export const Textarea = forwardRef<HTMLTextAreaElement, TextareaProps>(function Textarea(
{
variant = 'default',
state = 'default',
inputSize = 'md',
label,
helperText,
errorText,
successText,
warningText,
showCharCount = false,
maxLength,
fullWidth = true,
resize = 'vertical',
containerClassName = '',
className = '',
disabled,
value,
defaultValue,
onChange,
id,
rows = 4,
...rest
},
ref
) {
const [internalValue, setInternalValue] = useState(defaultValue?.toString() || '');
const currentValue = value !== undefined ? value.toString() : internalValue;
const charCount = currentValue.length;
const autoId = useId();
const inputId = id || `textarea-${autoId}`;
const feedbackId = `${inputId}-feedback`;
const activeState = disabled ? 'default' : state;
const feedbackText = errorText || successText || warningText || helperText;
const feedbackState = errorText ? 'error' : successText ? 'success' : warningText ? 'warning' : 'default';
const isInvalid = feedbackState === 'error' || activeState === 'error';
const handleChange = (e: React.ChangeEvent<HTMLTextAreaElement>) => {
if (value === undefined) {
setInternalValue(e.target.value);
}
onChange?.(e);
};
return (
<div className={`${fullWidth ? 'w-full' : 'inline-flex flex-col'} ${containerClassName}`}>
{label && (
<label
htmlFor={inputId}
className={`block font-medium text-(--text) mb-1.5 ${sizeStyles[inputSize].label}`}
>
{label}
</label>
)}
<textarea
ref={ref}
id={inputId}
value={value}
defaultValue={value === undefined ? defaultValue : undefined}
onChange={handleChange}
maxLength={maxLength}
disabled={disabled}
rows={rows}
aria-invalid={isInvalid || undefined}
aria-describedby={feedbackText ? feedbackId : undefined}
className={[
'w-full rounded-lg outline-none transition-colors py-2 text-(--text)',
variantStyles[variant][activeState],
sizeStyles[inputSize].input,
resizeStyles[resize],
disabled ? 'opacity-50 cursor-not-allowed' : '',
className,
].filter(Boolean).join(' ')}
{...rest}
/>
<div className="flex items-center justify-between mt-1.5 gap-2">
{feedbackText && (
<p
id={feedbackId}
role={feedbackState === 'error' ? 'alert' : undefined}
className={`${sizeStyles[inputSize].helper} ${stateColors[feedbackState]}`}
>
{feedbackText}
</p>
)}
{showCharCount && maxLength && (
<p className={`${sizeStyles[inputSize].helper} text-(--text-muted) ml-auto tabular-nums`}>
{charCount}/{maxLength}
</p>
)}
</div>
</div>
);
});Related
Other components that work well alongside Textarea.