apps/eclipse/content/design-system/components/textarea.mdx
import { Textarea, textareaVariants, Button, Field, FieldLabel, FieldDescription, FieldError } from "@prisma/eclipse";
Basic Textarea
import { Textarea } from "@prisma/eclipse";
export function MyComponent() {
return <Textarea placeholder="Enter text..." />;
}
Live Example:
<div className="max-w-md my-4"> <Textarea placeholder="Enter text..." /> </div>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:
<div className="max-w-md my-4"> <Textarea placeholder="Enter up to 200 characters..." maxLength={200} showCharCount /> </div>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:
<div className="space-y-4 max-w-md my-4"> <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>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:
<div className="space-y-4 max-w-md my-4"> <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>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:
<div className="max-w-md my-4"> <Field> <FieldLabel htmlFor="bio-field">Bio</FieldLabel> <Textarea id="bio-field" placeholder="Enter your bio..." /> <FieldDescription>Tell us a little bit about yourself.</FieldDescription> </Field> </div>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:
<div className="max-w-md my-4"> <Field> <FieldLabel htmlFor="description-field">Description</FieldLabel> <Textarea id="description-field" placeholder="Enter description..." maxLength={300} showCharCount /> <FieldDescription>Provide a brief description (max 300 characters).</FieldDescription> </Field> </div>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:
<div className="space-y-4 max-w-md my-4"> <Textarea placeholder="Disabled textarea" disabled /> <Textarea defaultValue="Disabled with value" disabled /> </div>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:
<div className="max-w-md my-4"> <Field> <FieldLabel htmlFor="feedback-invalid-field">Feedback</FieldLabel> <FieldDescription>We'd love to hear your thoughts.</FieldDescription> <Textarea id="feedback-invalid-field" defaultValue="Too short" aria-invalid="true" /> <FieldError>Feedback must be at least 10 characters long</FieldError> </Field> </div>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:
<div className="space-y-2 max-w-md my-4"> <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"> Feedback must be at least 10 characters long </span> </div>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:
<form className="space-y-4 max-w-md my-4"> <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>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:
<div className="space-y-4 max-w-md my-4"> <Textarea placeholder="Small (3 rows)" rows={3} /> <Textarea placeholder="Medium (5 rows)" rows={5} /> <Textarea placeholder="Large (10 rows)" rows={10} /> </div>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)The character counter provides visual feedback:
The counter only appears when both showCharCount={true} and maxLength are provided.
required attribute for required fieldsaria-invalid to mark invalid textareas and provide error messagesmaxLength values based on your data requirementsrows attribute to set an appropriate initial heightThe Textarea component is perfect for:
The Textarea component follows accessibility best practices:
<textarea> elementaria-invalidid and htmlFor attributesaria-invalidThe Textarea component uses design tokens and can be customized:
border-input with border-ring on focusborder-destructive when aria-invalid is truebg-transparent with dark mode supportring-destructive/20 when aria-invalid is truetext-muted-foregroundrounded-lgring-ring/50 with ring-3 widthCustomize by passing className props:
<Textarea className="min-h-32 font-mono" />
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>
);
}