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:
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:
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:
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:
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:
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
requiredattribute for required fields - Use
aria-invalidto 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
maxLengthvalues 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
rowsattribute 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
idandhtmlForattributes - 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-inputwithborder-ringon focus - Invalid border:
border-destructivewhenaria-invalidis true - Background:
bg-transparentwith dark mode support - Text: Base text size with responsive adjustments
- Invalid ring:
ring-destructive/20whenaria-invalidis true - Placeholder:
text-muted-foreground - Radius:
rounded-lg - Focus ring:
ring-ring/50withring-3width - 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>
);
}