IndiaCN UI

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.json
import { Textarea } from '@/components/ui/textarea';

Usage

<Label htmlFor="reason">Reason for appeal</Label>
<Textarea id="reason" showCount maxLength={500} />

Examples

Default

TextareaDefault
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

TextareaWithCount
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.

TextareaStates
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 maxLength when 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

PropTypeDefaultDescription
statedefault | error | success | warningdefaultBorder and focus-ring colour
showCountbooleanfalseShows used/maxLength beneath the field. Requires maxLength
maxLengthnumberCharacter limit
containerClassNamestringClasses for the wrapper rather than the textarea

Any other textarea prop is forwarded. Validation messages use InputMessage from the Input component.

On this page