Skip to main content

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

tsx
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/components

Import

Import the Textarea component into your file.

tsx
import { Textarea } from '@/components/aidash/textarea';

Usage

The simplest way to use Textarea with default props.

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

tsx
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" />
Tip
Use 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.

tsx
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

Success

Warning

Your message contains special characters

tsx
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

tsx
1<Textarea2label="Project Description"3placeholder="Describe your project in detail..."4helperText="Provide context for collaborators"5rows={3}6/>

Custom Rows

tsx
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

tsx
1<Textarea2label="Tweet"3placeholder="What's happening?"4showCharCount5maxLength={280}6helperText="Keep it concise"7rows={3}8/>

Resize Control

tsx
1<Textarea label="Vertical" resize="vertical" />   {/* default */}2<Textarea label="No Resize" resize="none" />3<Textarea label="Both Directions" resize="both" />

Full Width & Disabled

tsx
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" />
Auto-Resize
For auto-growing textareas, combine 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.

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

PropTypeDefaultDescription
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)
labelstringLabel displayed above the textarea
helperTextstringHelper text shown below the textarea
errorTextstringError message (sets state to error style)
successTextstringSuccess message (sets state to success style)
warningTextstringWarning message (sets state to warning style)
showCharCountbooleanfalseShow a character count below the textarea
maxLengthnumberMaximum character limit (HTML + counter)
fullWidthbooleantrueStretch textarea to fill container width
resize'none' | 'vertical' | 'horizontal' | 'both''vertical'Resize behavior of the textarea
containerClassNamestring''Additional CSS classes on the wrapper div
placeholderstringPlaceholder text (standard HTML)
rowsnumber4Number of visible text rows
disabledbooleanfalseDisable the textarea
onChange(e: ChangeEvent) => voidChange handler
valuestringControlled value
classNamestring''Additional CSS classes on the textarea element
Note
Textarea extends 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

tsx
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

tsx
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

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

Do
  • 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
Don't
  • 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.

src/components/aidash/textarea.tsx
tsx
'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>
  );
});

Other components that work well alongside Textarea.