- Accordion
- AppShell
- Autocomplete
- Badge
- Blobatar
- BoringAvatar
- Button
- Calendar
- Checkbox
- DataGrid
- Date Picker
- Date Range Picker
- DateTime
- Dialog
- Drawer
- Dropdown
- Empty
- Error State
- Frame
- Input
- Input Amount
- Input Group
- Input OTP
- Input Password
- Input Phone
- Input Search
- Input Secret
- Loading
- PageContainer
- PageHeader
- Popover
- Select
- StatsCard
- Switch
- Tabs
- TextArea
- Toast
- Tooltip
Playground
"use client";
import { TextArea } from "@/components/ui/textarea";
export function TextareaDemo() {
return (
<TextArea
label={"Message"}
placeholder={"Write a message"}
rows={4}
aria-invalid={false}
/>
);
}Installation
Initialize shadcn in your React project, then install this component from the Vandor UI registry:
$ pnpm dlx shadcn@latest add https://vandor-ui.vercel.app/r/textarea.json
The default installation target is components/ui/textarea.tsx. Review the CLI's overwrite prompt before replacing local customizations.
Dependencies
Installed automatically by the registry CLI. For manual setup:
- cn
$ pnpm add cnUsage
import { TextArea } from "@/components/ui/textarea";
<TextArea label="Message" placeholder="Write a message" rows={4} />;TextArea forwards native textarea props and ref. Native resizing remains enabled.
Examples
The example sets a native row count and preserves the multiline input semantics.
Keep this brief and specific.
import { TextArea } from "@/components/ui/textarea";
export const TextAreaDemo = () => (
<div className="grid max-w-sm gap-2">
<TextArea
label="Project summary"
name="summary"
rows={4}
placeholder="Describe the work"
aria-describedby="summary-hint"
/>
<p id="summary-hint" className="text-sm text-muted-foreground">
Keep this brief and specific.
</p>
</div>
);Optional Storybook Stories
Install TextArea first, then run npx shadcn@latest add https://vandor-ui.vercel.app/r/textarea-stories.json. Stories cover rows, controlled character counts, and disabled/read-only/invalid fields.
Stories install beside TextArea under your configured UI alias and use relative imports. Use your existing matching @storybook/react packages (verified with 10.6.1), discover **/*.stories.tsx, and load your global Tailwind/theme stylesheet. No addons, extra packages, core overwrites, or Storybook config edits are required. Default-value Controls remount uncontrolled examples; CharacterCount owns local state. Storybook UI is not verified because no server exists here.
Behavior & Accessibility
The optional static label is associated with the textarea. aria-invalid and aria-describedby can expose validation state and supporting text. Disabled and read-only states follow native semantics.
Props
| Prop | Type / Description | Default |
|---|---|---|
| aria-invalid | booleanTextArea: exposes invalid state to assistive technology. | false |
| containerClassName | stringTextArea: class applied to label/control wrapper. | undefined |
| defaultValue | stringTextArea: initial uncontrolled value. | undefined |
| disabled | booleanTextArea: prevents editing and focus. | false |
| label | stringTextArea: visible label associated with the native field. | undefined |
| labelClassName | stringTextArea: class applied to label. | undefined |
| name | stringTextArea: native form field name. | undefined |
| onChange | React.ChangeEventHandler<HTMLTextAreaElement>TextArea: native change callback. | undefined |
| placeholder | stringTextArea: native placeholder. | undefined |
| readOnly | booleanTextArea: prevents editing while retaining focus. | false |
| rows | numberTextArea: native visible row count. | browser default |
| value | stringTextArea: controlled native value. | uncontrolled |
The reference lists component-specific props and selected native props, not the complete React API. Other native attributes are forwarded to the rendered element.
Source
"use client";
import { cn } from "cn";
import type { ComponentProps } from "react";
import * as React from "react";
import { fieldSurfaceClassName } from "./input";
export type TextAreaProps = ComponentProps<"textarea"> & {
label?: string;
labelClassName?: string;
containerClassName?: string;
};
export const TextArea = ({
className,
containerClassName,
id,
label,
labelClassName,
...props
}: TextAreaProps) => {
const generatedId = React.useId();
const textareaId = id ?? generatedId;
const textarea = (
<textarea
{...props}
id={label ? textareaId : id}
data-slot="textarea"
className={cn(
"min-h-20 w-full min-w-0 rounded-md border border-input px-3 py-2 text-sm text-foreground shadow-xs outline-none transition-[color,box-shadow] placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 aria-invalid:border-destructive motion-reduce:transition-none",
fieldSurfaceClassName,
className
)}
/>
);
if (!label && !containerClassName) {
return textarea;
}
return (
<div className={cn("grid min-w-0 gap-1.5", containerClassName)}>
{label ? (
<label
htmlFor={textareaId}
className={cn("text-sm font-medium", labelClassName)}
>
{label}
</label>
) : null}
{textarea}
</div>
);
};