Textarea

A multi-line text input with built-in label, description, error message, and character count support. Fully accessible with automatic ARIA wiring via useId.

Installation

npm install @designforge/ui

Usage

import { Textarea } from "@designforge/ui";
 
export default function Example() {
  return (
    <Textarea
      label="Message"
      description="Write anything you'd like to share."
      placeholder="Type here..."
    />
  );
}

Examples

Basic

A plain textarea with no extra wrappers or labels.

import { Textarea } from "@designforge/ui";
 
export default function BasicTextarea() {
  return <Textarea placeholder="Enter your message..." rows={4} />;
}

With Label and Description

Pass label and description to add a visible label and helper text beneath the field. IDs are generated automatically via useId so you never need to wire them up manually.

import { Textarea } from "@designforge/ui";
 
export default function LabeledTextarea() {
  return (
    <Textarea
      label="Bio"
      description="Tell us a little about yourself. Maximum 300 characters."
      placeholder="I'm a frontend engineer who..."
      rows={4}
    />
  );
}

Error State

Set error to display a red error message below the field. The component automatically sets aria-invalid="true" and links the error text via aria-describedby.

import { useState } from "react";
import { Textarea } from "@designforge/ui";
 
export default function ErrorTextarea() {
  const [value, setValue] = useState("");
 
  return (
    <Textarea
      label="Feedback"
      value={value}
      onChange={(e) => setValue(e.target.value)}
      error={value.trim() === "" ? "Feedback cannot be empty." : undefined}
      placeholder="Share your thoughts..."
      rows={4}
    />
  );
}

Character Count

Set showCount to display a live character count beneath the field. The count is announced to screen readers via aria-live="polite" as you type.

import { Textarea } from "@designforge/ui";
 
export default function CharacterCountTextarea() {
  return (
    <Textarea
      label="Tweet"
      showCount
      placeholder="What's happening?"
      rows={3}
    />
  );
}

Max Length with Count

Combine maxLength and showCount to enforce a hard character limit and display remaining characters. The count turns red when you approach or reach the limit.

import { Textarea } from "@designforge/ui";
 
export default function MaxLengthTextarea() {
  return (
    <Textarea
      label="Short Bio"
      description="Appears on your public profile."
      maxLength={160}
      showCount
      placeholder="Keep it concise..."
      rows={3}
    />
  );
}

Disabled

Pass the standard HTML disabled attribute to prevent interaction. The field is visually dimmed and removed from the tab order.

import { Textarea } from "@designforge/ui";
 
export default function DisabledTextarea() {
  return (
    <Textarea
      label="Notes"
      description="This field is currently locked."
      defaultValue="Read-only content that cannot be edited."
      disabled
      rows={3}
    />
  );
}

Resizable Height

By default the textarea is not resizable. Add a className to allow vertical resizing via Tailwind's resize-y utility.

import { Textarea } from "@designforge/ui";
 
export default function ResizableTextarea() {
  return (
    <Textarea
      label="Notes"
      description="Drag the bottom edge to resize."
      placeholder="Type your notes here..."
      className="resize-y min-h-[80px]"
    />
  );
}

Feedback Form

A realistic example combining label, description, character limit, error validation, and a submit action.

import { useState } from "react";
import { Textarea } from "@designforge/ui";
import { Button } from "@designforge/ui";
 
export default function FeedbackForm() {
  const [value, setValue] = useState("");
  const [submitted, setSubmitted] = useState(false);
 
  const error =
    submitted && value.trim().length < 20
      ? "Please provide at least 20 characters of feedback."
      : undefined;
 
  const handleSubmit = (e: React.FormEvent) => {
    e.preventDefault();
    setSubmitted(true);
    if (value.trim().length >= 20) {
      alert("Feedback submitted!");
    }
  };
 
  return (
    <form onSubmit={handleSubmit} className="space-y-4 max-w-md">
      <Textarea
        label="Your Feedback"
        description="Help us improve by sharing your experience."
        placeholder="What could we do better?"
        maxLength={500}
        showCount
        rows={5}
        value={value}
        onChange={(e) => setValue(e.target.value)}
        error={error}
      />
      <Button type="submit">Submit Feedback</Button>
    </form>
  );
}

API Reference

Textarea

Extends all standard HTML <textarea> attributes plus the following DesignForge-specific props.

PropTypeDefaultDescription
labelstringVisible label rendered above the textarea. Auto-linked via generated id.
descriptionstringHelper text rendered below the textarea. Wired to aria-describedby.
errorstringError message rendered below the field. Sets aria-invalid="true" when present.
showCountbooleanfalseDisplays a live character count. Announced via aria-live="polite".
maxLengthnumberNative HTML maxlength. When combined with showCount, shows current / max count format.
wrapperClassNamestringClass names applied to the outer wrapper div rather than the <textarea> itself.
classNamestringClass names applied directly to the <textarea> element.

All standard <textarea> HTML attributes (rows, cols, disabled, readOnly, placeholder, value, onChange, etc.) are also supported and forwarded to the underlying element.

Accessibility

  • A unique id is generated with useId and applied to both the <textarea> and its associated <label>, eliminating the need for manual ID management.
  • When description or error is present, their IDs are collected into aria-describedby so screen readers announce them after the field label.
  • When error is non-empty, aria-invalid="true" is set on the <textarea> automatically.
  • Character count (when showCount is enabled) is wrapped in a <span aria-live="polite"> so updates are announced to screen reader users without interrupting their flow.
  • disabled state is fully forwarded and the field is removed from the tab order by the browser natively.

Live View

Open in Storybook ↗