Textarea
A textarea that grows with its content, with an optional character counter.
$
pnpm dlx shadcn@latest add https://lodeui.com/r/textarea.json36/280
components/examples/textarea-demo.tsx
"use client";
import { Textarea } from "@/components/ui/textarea";
export default function TextareaDemo() {
return (
<div className="w-full max-w-md">
<label htmlFor="demo-ta" className="mb-1.5 block text-sm font-medium">Release notes</label>
<Textarea id="demo-ta" maxLength={280} showCount maxRows={8} placeholder="It grows as you type…" defaultValue={"Faster dashboards.\nTeams with roles."} />
</div>
);
}When to use
- Comments, descriptions and messages that span more than one line.
When not to use
- Chat composers with attachments: use the prompt input.
- Single-line values: use an input.
Installation
$
pnpm dlx shadcn@latest add https://lodeui.com/r/textarea.json1Copy the source into your project
components/ui/textarea.tsx"use client"; import * as React from "react"; import { cn } from "@/lib/utils"; export interface TextareaProps extends React.TextareaHTMLAttributes<HTMLTextAreaElement> { /** Grow with content up to this many rows. */ maxRows?: number; invalid?: boolean; /** Show a character counter against maxLength. */ showCount?: boolean; } /** A textarea that grows with its content. */ export function Textarea({ maxRows = 10, invalid, showCount, className, onChange, rows = 3, ...props }: TextareaProps) { const ref = React.useRef<HTMLTextAreaElement>(null); const [count, setCount] = React.useState(String(props.value ?? props.defaultValue ?? "").length); const resize = React.useCallback(() => { const el = ref.current; if (!el) return; el.style.height = "auto"; const line = parseFloat(getComputedStyle(el).lineHeight) || 20; const pad = el.offsetHeight - el.clientHeight + parseFloat(getComputedStyle(el).paddingTop) * 2; el.style.height = `${Math.min(el.scrollHeight, line * maxRows + pad)}px`; }, [maxRows]); React.useLayoutEffect(resize, [resize, props.value]); return ( <div className="relative"> <textarea ref={ref} rows={rows} aria-invalid={invalid || undefined} onChange={(e) => { setCount(e.target.value.length); resize(); onChange?.(e); }} className={cn( "block w-full resize-none rounded-md border border-input bg-transparent px-3 py-2.5 text-sm leading-relaxed outline-none transition-[border-color,box-shadow]", "placeholder:text-muted-foreground/70 focus:border-foreground/40 focus:ring-2 focus:ring-ring/40", invalid && "border-destructive", showCount && "pb-7", className, )} {...props} /> {showCount && props.maxLength && ( <span className={cn("pointer-events-none absolute bottom-2 right-3 text-[11px] tabular-nums", count > props.maxLength * 0.9 ? "text-destructive" : "text-muted-foreground")}> {count}/{props.maxLength} </span> )} </div> ); }
Usage
Import it from @/components/ui/textarea after installing.
example.tsx
import { Textarea } from "@/components/ui/textarea";
export function Feedback() {
return <Textarea aria-label="Feedback" placeholder="What could be better?" maxLength={500} showCount maxRows={8} />;
}API reference
Textarea
A textarea that grows with its content up to a limit, with an optional character counter.
| Prop | Type | Default | Description |
|---|---|---|---|
| rows | number | 3 | Starting height. |
| maxRows | number | 10 | Stops growing here and scrolls. |
| showCount | boolean | – | Shows used / maxLength. |
| invalid | boolean | – | Red border and aria-invalid. |
| ...props | TextareaHTMLAttributes | – | value, onChange, maxLength and the rest. |
Keyboard interactions
| Keys | Action |
|---|---|
| Enter | New line |
| Tab | Leave the field |
Accessibility
- Label it with Field, a <label> or aria-label.
- The counter is plain text next to the field.
Motion
- Height changes instantly as you type, so the caret never lags.
Responsive behavior
- Fills its container width.
Performance
- Resizes by measuring scrollHeight on input only.
Notes for AI
- Use maxLength with showCount rather than validating length afterwards.
This page as Markdown, for assistants and coding agents:
/components/textarea/markdownThe full library index for assistants is at /llms.txt.
Lode222 doc pagesllms.txt