Free shadcn/ui textarea component for React — auto-growing (field-sizing), character counter, resize control and validation examples. Copy or install in one command.

Installation

1 Configure registry — once per project
{
  "registries": {
    "@designrevision": "https://registry.designrevision.com/r/{name}.json"
  }
}

Add to your existing components.json. Requires shadcn/ui v2.3+.

2 Install the component

Packages

utils

Props

No props documented yet.

Copied!
components/ui/textarea.tsx
import * as React from "react"

import { cn } from "@/lib/utils"

function Textarea({ className, ...props }: React.ComponentProps<"textarea">) {
  return (
    <textarea
      data-slot="textarea"
      className={cn(
        "border-input placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:bg-input/30 flex field-sizing-content min-h-16 w-full rounded-md border bg-transparent px-3 py-2 text-base shadow-xs transition-[color,box-shadow] outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50 md:text-sm",
        className
      )}
      {...props}
    />
  )
}

export { Textarea }

Examples

With label

The canonical multi-line field — a `Label` bound by `htmlFor`, with muted helper text below.

Packages

textarea
label

Props

No props documented yet.

Copied!
components/ui/textarea-label-01.tsx
import { Label } from "@/components/ui/label"
import { Textarea } from "@/components/ui/textarea"

export default function TextareaLabel01() {
  return (
    <div className="grid w-full max-w-sm gap-2">
      <Label htmlFor="message">Your message</Label>
      <Textarea id="message" placeholder="Type your message here…" />
      <p className="text-sm text-muted-foreground">
        We typically reply within a business day.
      </p>
    </div>
  )
}

Autosize

Grows with its content up to a `max-h` and then scrolls — powered by the CSS `field-sizing-content` baked into the primitive, with no autosize library.

Packages

textarea
label

Props

No props documented yet.

Copied!
components/ui/textarea-autosize-01.tsx
import { Label } from "@/components/ui/label"
import { Textarea } from "@/components/ui/textarea"

export default function TextareaAutosize01() {
  return (
    <div className="grid w-full max-w-sm gap-2">
      <Label htmlFor="bio">Bio</Label>
      <Textarea
        id="bio"
        placeholder="Start typing — the field grows with your text…"
        defaultValue="The height tracks the content via field-sizing-content. It grows as you type, stops at the max height, then scrolls."
        className="max-h-40 resize-none"
      />
      <p className="text-sm text-muted-foreground">
        Grows with content up to a max height — no JavaScript.
      </p>
    </div>
  )
}

Character counter

A live `current/max` count in `tabular-nums`, backed by the textarea’s `maxLength`.

Packages

textarea
label

Props

No props documented yet.

Copied!
components/ui/textarea-counter-01.tsx
"use client"

import * as React from "react"

import { Label } from "@/components/ui/label"
import { Textarea } from "@/components/ui/textarea"

export default function TextareaCounter01() {
  const maxLength = 180
  const [value, setValue] = React.useState("")

  return (
    <div className="grid w-full max-w-sm gap-2">
      <Label htmlFor="about">About you</Label>
      <Textarea
        id="about"
        value={value}
        maxLength={maxLength}
        onChange={(e) => setValue(e.target.value)}
        placeholder="A short bio…"
      />
      <p className="text-right text-sm tabular-nums text-muted-foreground">
        {value.length}/{maxLength}
      </p>
    </div>
  )
}

With button

A comment field paired with a submit `Button` — the shape behind comment boxes and reply forms.

Packages

textarea
label
button

Props

No props documented yet.

Copied!
components/ui/textarea-button-01.tsx
import { Button } from "@/components/ui/button"
import { Label } from "@/components/ui/label"
import { Textarea } from "@/components/ui/textarea"

export default function TextareaButton01() {
  return (
    <form className="grid w-full max-w-sm gap-2">
      <Label htmlFor="comment">Add a comment</Label>
      <Textarea id="comment" placeholder="Share your thoughts…" />
      <div className="flex justify-end">
        <Button type="submit">Comment</Button>
      </div>
    </form>
  )
}

States

Invalid (`aria-invalid` switches on the destructive ring and border, paired with `aria-describedby`), disabled, and read-only fields.

Packages

textarea
label

Props

No props documented yet.

Copied!
components/ui/textarea-states-01.tsx
import { Label } from "@/components/ui/label"
import { Textarea } from "@/components/ui/textarea"

export default function TextareaStates01() {
  return (
    <div className="grid w-full max-w-sm gap-6">
      <div className="grid gap-2">
        <Label htmlFor="message-invalid">Message</Label>
        <Textarea
          id="message-invalid"
          defaultValue="Too short"
          aria-invalid="true"
          aria-describedby="message-invalid-error"
        />
        <p id="message-invalid-error" className="text-sm text-destructive">
          Message must be at least 20 characters.
        </p>
      </div>
      <div className="grid gap-2">
        <Label htmlFor="message-disabled">Disabled</Label>
        <Textarea id="message-disabled" placeholder="Unavailable…" disabled />
      </div>
      <div className="grid gap-2">
        <Label htmlFor="message-readonly">Read only</Label>
        <Textarea
          id="message-readonly"
          readOnly
          defaultValue="This content can be selected and copied, but not edited."
        />
      </div>
    </div>
  )
}

Resize control

Constrain the drag handle with `resize-y` for vertical-only resizing, or `resize-none` to remove it entirely.

Packages

textarea
label

Props

No props documented yet.

Copied!
components/ui/textarea-resize-01.tsx
import { Label } from "@/components/ui/label"
import { Textarea } from "@/components/ui/textarea"

export default function TextareaResize01() {
  return (
    <div className="grid w-full max-w-sm gap-6">
      <div className="grid gap-2">
        <Label htmlFor="ta-resize-y">Vertical resize handle</Label>
        <Textarea
          id="ta-resize-y"
          className="resize-y"
          placeholder="Drag the bottom-right corner to resize…"
        />
      </div>
      <div className="grid gap-2">
        <Label htmlFor="ta-resize-none">No resize handle</Label>
        <Textarea
          id="ta-resize-none"
          className="resize-none"
          placeholder="Manual resizing disabled…"
        />
      </div>
    </div>
  )
}

Frequently asked questions

Yes. It is the canonical shadcn/ui textarea — a native textarea element styled with the same CSS variable tokens and aria-invalid error handling — so it drops into any shadcn/ui project. On top of it we ship ready-made examples for autosize, counters, resize control, and validation.
It already does. The primitive includes the CSS field-sizing-content property, so the box height tracks the text as you type — no autosize library or JavaScript needed. Add a max-h class to cap the growth and it will scroll past that point.
Add the resize-none class to the textarea to remove the manual drag handle, or resize-y to allow vertical resizing only. The Resize control example shows both.
Track the value in state, set maxLength on the textarea, and render value.length together with the limit below the field. Use tabular-nums so the counter does not shift width as the number changes.
Set aria-invalid on the textarea — that switches on the destructive border and ring automatically, no extra class needed. Link the error message with aria-describedby so assistive tech reads it alongside the field.
Yes. It is styled with CSS variable tokens like --input, --ring, and --destructive, so it follows your theme automatically, including dark mode and any palette from a shadcn-compatible theme generator.