For the complete documentation index, see llms.txt. Markdown variants are available by appending .md to any URL or sending an Accept: text/markdown header. An agent skill is available at /.well-known/agent-skills/site-skill.md.
1
Support

TextArea

A native multiline text field with an optional associated label.

Playground

Live preview
Customize textarea
Customize
textarea-demo.tsx
"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:

Dependencies1 required packages
  • cn
$ pnpm add cn

Usage

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.

textarea-demo.tsx
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

Component props, types, defaults, and descriptions
PropType / DescriptionDefault
aria-invalidboolean

TextArea: exposes invalid state to assistive technology.

false
containerClassNamestring

TextArea: class applied to label/control wrapper.

undefined
defaultValuestring

TextArea: initial uncontrolled value.

undefined
disabledboolean

TextArea: prevents editing and focus.

false
labelstring

TextArea: visible label associated with the native field.

undefined
labelClassNamestring

TextArea: class applied to label.

undefined
namestring

TextArea: native form field name.

undefined
onChangeReact.ChangeEventHandler<HTMLTextAreaElement>

TextArea: native change callback.

undefined
placeholderstring

TextArea: native placeholder.

undefined
readOnlyboolean

TextArea: prevents editing while retaining focus.

false
rowsnumber

TextArea: native visible row count.

browser default
valuestring

TextArea: 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>
  );
};