Skip to content
Drivn
4 min read

React Textarea Component Examples

Copy-paste React Textarea examples: a basic field, a controlled textarea with a character counter, a labelled form field, and a disabled state.

A textarea is the multi-line cousin of the text input — the right control for a comment box, a feedback form, a bio field, or any message that runs past a single line. The Textarea component in Drivn is a single React.forwardRef wrapper around the native <textarea> element, with every class held in one const styles object. There is no Radix, no cva, no runtime UI dependency — just React and cn() from @/utils/cn, so it forwards its ref straight to the DOM node and accepts every native textarea attribute.

Because the props type is React.TextareaHTMLAttributes<HTMLTextAreaElement>, anything you would pass to a plain <textarea>placeholder, rows, maxLength, value, onChange, disabled — passes straight through. Each example below is TypeScript, because Drivn ships TypeScript-only .tsx source installed by the Drivn CLI under @/components/ui/textarea.

The examples cover the basic uncontrolled field, a controlled textarea wired to React.useState with a live character counter, a labelled form field you can drop beside an Input, and the disabled state. The component resizes vertically by default (resize-y) from a min-h-[80px] starting height, so the user can drag it taller when they need more room.

A basic textarea

The simplest use is an uncontrolled Textarea with a placeholder. It renders a full-width box with a min-h-[80px] starting height, a rounded border-input border, and a muted placeholder, and the user can drag the bottom edge to make it taller because the base styles include resize-y. You write no state and no handlers — the browser owns the value until you read it from the form on submit.

This is the right default for most cases: a contact form message, a quick note field, a description box. Because the component spreads {...props} onto the underlying <textarea>, you can add rows={5} to set the initial visible height or name="message" so the value is included when the surrounding <form> submits. See the full prop list on the Textarea reference.

1import { Textarea } from "@/components/ui/textarea"
2
3export default function Page() {
4 return (
5 <Textarea placeholder="Type your message..." />
6 )
7}

Controlled textarea with a character counter

To read or limit the value as the user types, switch to controlled mode by holding the text in React.useState and passing value and onChange. Because the Textarea forwards every native attribute, you add maxLength to cap the length and read value.length to render a live counter beneath the field. The wrapper is marked 'use client' for the useState hook.

This is the pattern for a tweet-style box, a review with a length limit, or any field where you want to show remaining characters — drop a Button below it to submit. The onChange handler receives the standard React change event, so you read e.target.value and store it — the same signature you would use with a native textarea, no adapter needed. Pair the counter text with a muted colour so it sits quietly under the field.

1"use client"
2
3import * as React from "react"
4import { Textarea } from "@/components/ui/textarea"
5
6export default function CounterTextarea() {
7 const [value, setValue] = React.useState("")
8
9 return (
10 <div>
11 <Textarea
12 value={value}
13 onChange={(e) => setValue(e.target.value)}
14 maxLength={200}
15 placeholder="Write a short bio..."
16 />
17 <p className="mt-2 text-sm text-muted-foreground">
18 {value.length}/200
19 </p>
20 </div>
21 )
22}

A labelled form field

In a real form you want a label tied to the field for accessibility. Wrap a native <label> and the Textarea together, link them with a shared htmlFor/id, and give the textarea a name so its value is captured on submit. The Textarea spreads these attributes straight onto the <textarea>, so there is nothing special to wire — it behaves like the element it wraps.

This is how the Textarea sits next to an Input in a contact or feedback form: matching widths, matching border tokens, one labelled field per row. Because the ref forwards to the DOM node, the same field works with react-hook-form's register — spread register("message") onto the Textarea and it registers exactly like a native control, with no Controller wrapper required.

1import { Textarea } from "@/components/ui/textarea"
2
3export default function FeedbackField() {
4 return (
5 <div className="grid gap-2">
6 <label htmlFor="feedback" className="text-sm font-medium">
7 Your feedback
8 </label>
9 <Textarea
10 id="feedback"
11 name="feedback"
12 rows={5}
13 placeholder="Tell us what you think..."
14 />
15 </div>
16 )
17}

Disabled textarea

Pass the native disabled attribute to lock the field. The base styles include disabled:opacity-50 disabled:cursor-default, so a disabled Textarea dims to half opacity and drops the text cursor, signalling clearly that it cannot be edited. This is the state for a read-only message during submission, a field gated behind a toggle, or content awaiting a permission.

Because disabled is a real DOM attribute spread onto the <textarea>, the browser also excludes the field from form submission and skips it in tab order — you get the full native disabled behaviour, not just the visual dimming. To show non-editable content that should still be selectable and submitted, use readOnly instead of disabled; both pass through the same way since the props type is the native React.TextareaHTMLAttributes.

1import { Textarea } from "@/components/ui/textarea"
2
3export default function DisabledTextarea() {
4 return (
5 <Textarea
6 disabled
7 placeholder="This field is disabled"
8 />
9 )
10}
Get started

Install Drivn in one command

Copy the source into your project and own every line. Zero runtime dependencies, pure React + Tailwind.

npx drivn@latest create

Requires Node 18+. Works with npm, pnpm, and yarn.

Enjoying Drivn?
Star the repo on GitHub to follow new component releases.
Star →

Frequently asked questions

Import { Textarea } from "@/components/ui/textarea" and render it with a placeholder. It is a forwardRef wrapper around the native <textarea>, so you can leave it uncontrolled and read the value from the form on submit, or pass value and onChange for controlled mode. No state or handlers are required for the basic case — the browser owns the value until you read it.

Pass the native maxLength attribute, which spreads straight onto the underlying textarea. For a live counter, hold the value in React.useState, pass value and onChange, and render value.length next to the field. Because Drivn types its props as React.TextareaHTMLAttributes, maxLength behaves exactly as it would on a plain textarea.

Yes, vertically. The base styles include resize-y, so the user can drag the bottom edge to make the box taller, starting from a min-h-[80px] height. It does not auto-grow with content or resize horizontally. To set a different initial height, pass the rows attribute, which controls how many lines are visible before scrolling.

Yes. The Textarea is wrapped in React.forwardRef, so its ref forwards to the DOM node. Spread register("fieldName") from react-hook-form onto the Textarea and it registers like a native control — no Controller wrapper needed. The same forwarding lets you focus the field imperatively or read it through a ref.

Pass the native disabled attribute. The base styles include disabled:opacity-50 disabled:cursor-default, so the field dims and drops the text cursor, and the browser excludes it from submission and tab order. If you want non-editable content that is still selectable and submitted, use readOnly instead — both pass through because the props type is the native textarea attributes.