Textarea

A multi-line text input component for forms with optional character count and validation styles.

Usage

Basic Textarea

import { Textarea } from "@prisma/eclipse";

export function MyComponent() {
  return <Textarea placeholder="Enter text..." />;
}

Live Example:

With Character Count

The Textarea component supports an optional character counter that displays the current and maximum character count:

import { Textarea } from "@prisma/eclipse";

export function TextareaWithCount() {
  return (
    <Textarea
      placeholder="Enter up to 200 characters..."
      maxLength={200}
      showCharCount
    />
  );
}

Live Example:

0/200

Character Count with Different Limits

The character counter changes color as you approach the limit:

import { Textarea } from "@prisma/eclipse";

export function TextareaLimits() {
  return (
    <div className="space-y-4 max-w-md">
      <Textarea
        placeholder="Short text (50 chars)"
        maxLength={50}
        showCharCount
      />
      <Textarea
        placeholder="Medium text (150 chars)"
        maxLength={150}
        showCharCount
      />
      <Textarea
        placeholder="Long text (500 chars)"
        maxLength={500}
        showCharCount
      />
    </div>
  );
}

Live Example:

0/50
0/150
0/500

Textarea Sizes

import { Textarea } from "@prisma/eclipse";

export function TextareaSizes() {
  return (
    <div className="space-y-4 max-w-md">
      <Textarea size="lg" placeholder="Large (default)" maxLength={150} showCharCount />
      <Textarea size="xl" placeholder="Extra Large" maxLength={150} showCharCount />
      <Textarea size="2xl" placeholder="2X Large" maxLength={150} showCharCount />
    </div>
  );
}

Live Example:

0/150
0/150
0/150

With Field Component

Use the Field component for proper form structure with labels and descriptions:

import { Textarea, Field, FieldLabel, FieldDescription } from "@prisma/eclipse";

export function TextareaWithField() {
  return (
    <Field>
      <FieldLabel htmlFor="bio">Bio</FieldLabel>
      <FieldDescription>Tell us a little bit about yourself.</FieldDescription>
      <Textarea id="bio" placeholder="Enter your bio..." />
    </Field>
  );
}

Live Example:

Tell us a little bit about yourself.

With Field and Character Count

Combine Field components with character count for a complete form experience:

import { Textarea, Field, FieldLabel, FieldDescription } from "@prisma/eclipse";

export function TextareaFieldWithCount() {
  return (
    <Field>
      <FieldLabel htmlFor="description">Description</FieldLabel>
      <Textarea
        id="description"
        placeholder="Enter description..."
        maxLength={300}
        showCharCount
      />
      <FieldDescription>Provide a brief description (max 300 characters).</FieldDescription>
    </Field>
  );
}

Live Example:

0/300

Provide a brief description (max 300 characters).

Disabled State

import { Textarea } from "@prisma/eclipse";

export function DisabledTextarea() {
  return (
    <div className="space-y-4 max-w-md">
      <Textarea placeholder="Disabled textarea" disabled />
      <Textarea defaultValue="Disabled with value" disabled />
    </div>
  );
}

Live Example:

Invalid State with Field

Use the Field component with FieldError for proper error handling:

import { Textarea, Field, FieldLabel, FieldDescription, FieldError } from "@prisma/eclipse";

export function InvalidTextareaWithField() {
  return (
    <Field>
      <FieldLabel htmlFor="feedback-invalid">Feedback</FieldLabel>
      <FieldDescription>We'd love to hear your thoughts.</FieldDescription>
      <Textarea
        id="feedback-invalid"
        defaultValue="Too short"
        aria-invalid="true"
      />
      <FieldError>Feedback must be at least 10 characters long</FieldError>
    </Field>
  );
}

Live Example:

We'd love to hear your thoughts.

Invalid State (Basic)

Use the aria-invalid attribute to mark a textarea as invalid for validation errors:

import { Textarea } from "@prisma/eclipse";

export function InvalidTextarea() {
  return (
    <div className="space-y-2 max-w-md">
      <label htmlFor="invalid-feedback" className="text-sm font-medium">
        Feedback
      </label>
      <Textarea
        id="invalid-feedback"
        defaultValue="Too short"
        aria-invalid="true"
      />
      <span className="text-sm text-foreground-error block">
        Feedback must be at least 10 characters long
      </span>
    </div>
  );
}

Live Example:

Feedback must be at least 10 characters long

Controlled Component

Use as a controlled component with React state:

import { Textarea } from "@prisma/eclipse";
import { useState } from "react";

export function ControlledTextarea() {
  const [value, setValue] = useState("");

  return (
    <div className="space-y-4 max-w-md">
      <Textarea
        value={value}
        onChange={(e) => setValue(e.target.value)}
        placeholder="Type something..."
        maxLength={200}
        showCharCount
      />
      <p className="text-sm text-muted-foreground">
        You typed: {value || "(nothing yet)"}
      </p>
    </div>
  );
}

Form Example

import { Textarea, Button, Field, FieldLabel } from "@prisma/eclipse";

export function FormExample() {
  return (
    <form className="space-y-4 max-w-md">
      <Field>
        <FieldLabel htmlFor="title">Title</FieldLabel>
        <input
          id="title"
          className="border rounded px-3 py-2 w-full"
          placeholder="Enter title"
          required
        />
      </Field>
      <Field>
        <FieldLabel htmlFor="message">Message</FieldLabel>
        <Textarea
          id="message"
          placeholder="Enter your message..."
          required
          maxLength={500}
          showCharCount
        />
      </Field>
      <Button type="submit">Submit</Button>
    </form>
  );
}

Live Example:

0/500

Different Row Heights

Control the height using the rows prop:

import { Textarea } from "@prisma/eclipse";

export function TextareaRows() {
  return (
    <div className="space-y-4 max-w-md">
      <Textarea placeholder="Small (3 rows)" rows={3} />
      <Textarea placeholder="Medium (5 rows)" rows={5} />
      <Textarea placeholder="Large (10 rows)" rows={10} />
    </div>
  );
}

Live Example:

Component Props

Textarea

The Textarea component uses class-variance-authority for size variants. It extends all native HTML textarea attributes and adds:

  • size - Size variant ("lg" | "xl" | "2xl", default: "lg")
  • showCharCount - Display character counter (boolean, default: false)
  • maxLength - Maximum number of characters allowed (number, optional)
  • className - Additional CSS classes (string, optional)
  • disabled - Disable the textarea (boolean, default: false)
  • placeholder - Placeholder text (string, optional)
  • defaultValue - Default value for uncontrolled textareas (string, optional)
  • value - Value for controlled textareas (string, optional)
  • onChange - Change event handler (function, optional)
  • onFocus - Focus event handler (function, optional)
  • onBlur - Blur event handler (function, optional)
  • rows - Number of visible text rows (number, optional)
  • cols - Number of visible text columns (number, optional)
  • aria-invalid - Mark textarea as invalid for validation errors (boolean, optional)
  • ref - Forward ref to textarea element (React.Ref, optional)
  • All other standard HTML textarea attributes

Features

  • ✅ Multi-line text input
  • ✅ Size variants via CVA (lg, xl, 2xl)
  • ✅ Optional character counter with color indicators
  • ✅ Character limit enforcement with maxLength
  • ✅ Visual feedback when approaching character limit (>90% = warning color)
  • ✅ Focus state with shadow and border highlight
  • ✅ Disabled state with reduced opacity
  • ✅ Invalid state with error styling (aria-invalid)
  • ✅ Placeholder styling
  • ✅ Auto-resizing with field-sizing-content
  • ✅ Accessible and keyboard navigable
  • ✅ Forward ref support for form libraries
  • ✅ Fully typed with TypeScript
  • ✅ Customizable with className prop

Character Count Behavior

The character counter provides visual feedback:

  • Normal state (0-90% of limit): Displays in muted text color
  • Warning state (>90% of limit): Changes to warning color to alert user
  • At limit (100% of limit): Changes to destructive/error color

The counter only appears when both showCharCount={true} and maxLength are provided.

Best Practices

  • Always pair textareas with labels for accessibility
  • Provide clear placeholder text that doesn't replace labels
  • Use the required attribute for required fields
  • Use aria-invalid to mark invalid textareas and provide error messages
  • Consider adding helper text below textareas for additional context
  • Use character counts for fields with strict length requirements
  • Set appropriate maxLength values based on your data requirements
  • Use disabled state only when necessary
  • For forms, consider using form validation libraries like React Hook Form or Formik
  • Test keyboard navigation and screen reader compatibility
  • Consider the rows attribute to set an appropriate initial height

Common Use Cases

The Textarea component is perfect for:

  • Comment fields - User comments and feedback
  • Message forms - Contact and support forms
  • Descriptions - Product or profile descriptions
  • Bio fields - User biographies in profiles
  • Notes - Taking notes or writing memos
  • Reviews - Product or service reviews
  • Feedback forms - Collecting detailed feedback
  • Long-form content - Blog posts or article drafts
  • Code snippets - Entering code or configuration
  • Multi-line settings - Configuration text fields

Accessibility

The Textarea component follows accessibility best practices:

  • Uses semantic HTML <textarea> element
  • Supports all ARIA attributes including aria-invalid
  • Keyboard navigable (Tab, Shift+Tab)
  • Focus indicators with shadow and border
  • Works with screen readers
  • Supports labels via id and htmlFor attributes
  • Error states announced to screen readers via aria-invalid
  • Character counter positioned to not interfere with text entry
  • Respects prefers-reduced-motion for animations
  • Proper contrast ratios for text and borders
  • Forward ref support for programmatic focus management

Styling

The Textarea component uses design tokens and can be customized:

  • Border: border-input with border-ring on focus
  • Invalid border: border-destructive when aria-invalid is true
  • Background: bg-transparent with dark mode support
  • Text: Base text size with responsive adjustments
  • Invalid ring: ring-destructive/20 when aria-invalid is true
  • Placeholder: text-muted-foreground
  • Radius: rounded-lg
  • Focus ring: ring-ring/50 with ring-3 width
  • Transition: Smooth color transitions
  • Character counter: Positioned absolutely at bottom-right

Customize by passing className props:

<Textarea className="min-h-32 font-mono" />

Integration with Form Libraries

The Textarea component works seamlessly with popular form libraries:

React Hook Form

import { useForm } from "react-hook-form";
import { Textarea } from "@prisma/eclipse";

export function FormWithValidation() {
  const { register, formState: { errors } } = useForm();

  return (
    <form>
      <Textarea
        {...register("description", {
          required: "Description is required",
          minLength: { value: 10, message: "Too short" }
        })}
        aria-invalid={errors.description ? "true" : "false"}
        maxLength={500}
        showCharCount
      />
      {errors.description && (
        <span className="text-sm text-foreground-error">
          {errors.description.message}
        </span>
      )}
    </form>
  );
}

On this page