Textarea
Multi-line text field with validation states and a character counter.
Introduction
Textareas collect free text — a grievance, an address, a reason for appeal. UX4G specifies the same validation states as the Input, plus a character counter beneath the right edge.
Installation
npx shadcn@latest add https://indiacn.in/r/textarea.jsonimport { Textarea } from '@/components/ui/textarea';Usage
<Label htmlFor="reason">Reason for appeal</Label>
<Textarea id="reason" showCount maxLength={500} />Examples
Default
import { Label } from '@/components/ui/label';
import { Textarea } from '@/components/ui/textarea';
export default function Component() {
return (
<div className='w-full max-w-[420px]'>
<Label htmlFor='grievance' className='mb-2'>
Describe your grievance
</Label>
<Textarea id='grievance' placeholder='What happened, and when?' />
</div>
);
}With a character counter
showCount renders the counter UX4G places under the right edge. It needs maxLength — a counter with no limit has nothing to count towards.
0/200
import { ChangeEvent, useCallback, useState } from 'react';
import { Label } from '@/components/ui/label';
import { Textarea } from '@/components/ui/textarea';
export default function Component() {
const [value, setValue] = useState('');
const handleChange = useCallback(
(e: ChangeEvent<HTMLTextAreaElement>) => setValue(e.target.value),
[],
);
return (
<div className='w-full max-w-[420px]'>
<Label htmlFor='counted' className='mb-2'>
Summary
</Label>
<Textarea
id='counted'
showCount
maxLength={200}
value={value}
onChange={handleChange}
placeholder='Up to 200 characters'
/>
</div>
);
}States
Give at least 50 characters of detail.
Draft saved.
import { InputMessage } from '@/components/ui/input';
import { Label } from '@/components/ui/label';
import { Textarea } from '@/components/ui/textarea';
export default function Component() {
return (
<div className='flex w-full max-w-[420px] flex-col gap-5'>
<div>
<Label htmlFor='ta-error' className='mb-2'>
Error
</Label>
<Textarea id='ta-error' state='error' defaultValue='Too short' />
<InputMessage state='error'>Give at least 50 characters of detail.</InputMessage>
</div>
<div>
<Label htmlFor='ta-success' className='mb-2'>
Success
</Label>
<Textarea id='ta-success' state='success' defaultValue='Saved as draft.' />
<InputMessage state='success'>Draft saved.</InputMessage>
</div>
</div>
);
}Guidelines
- Set
maxLengthwhen a limit is real. Truncating someone's grievance at an arbitrary number is worse than accepting it. - The counter is announced politely, so a screen-reader user hears the remaining count without being interrupted mid-word.
- Keep the field resizable. People writing a long explanation will want the room.
API Reference
Textarea
| Prop | Type | Default | Description |
|---|---|---|---|
state | default | error | success | warning | default | Border and focus-ring colour |
showCount | boolean | false | Shows used/maxLength beneath the field. Requires maxLength |
maxLength | number | — | Character limit |
containerClassName | string | — | Classes for the wrapper rather than the textarea |
Any other textarea prop is forwarded. Validation messages use InputMessage from the Input component.