Textarea
A textarea is a multi-line text field. It has the same states as Input, and can be resized up and down.
Write at least 20 characters.
import { Label } from "@/components/ui/label";
import { Textarea } from "@/components/ui/textarea";
export default function TextareaDemo() {
return (
<div className="flex w-full max-w-xs flex-col gap-4">
<Textarea aria-label="Default" placeholder="Default" />
<div className="flex flex-col gap-2">
<Label htmlFor="bio">Bio</Label>
<Textarea id="bio" placeholder="A sentence or two" />
</div>
<Textarea aria-label="Disabled" placeholder="Disabled" disabled />
<div className="flex flex-col gap-2">
<Textarea
aria-label="Message"
aria-invalid
aria-describedby="message-error"
defaultValue="Hi"
/>
<p id="message-error" className="text-sm text-danger">
Write at least 20 characters.
</p>
</div>
</div>
);
}The code
The component is a single file, read from the repository when the site is built. Press Show code to see all of it.
import type { ComponentProps } from "react";
import { cx } from "@/lib/cx";
import { controlStyles } from "@/lib/styles";
// A multi-line text field. It is the native element with the same look as Input, and it can be
// resized up and down (not sideways, so it can't break the layout). Inside a Field, render it
// through FieldControl so the label, hint and error are connected to it.
export function Textarea({ className, ...props }: ComponentProps<"textarea">) {
return <textarea className={cx("min-h-24 resize-y py-2", controlStyles, className)} {...props} />;
}Props
| Prop | Type | Default | What it does |
|---|---|---|---|
rows | number | — | How many lines tall it starts, if you don't want the default minimum. |
placeholder | string | — | A hint shown while the field is empty. It is not a name for the field. |
disabled | boolean | false | Turns the field off. It can't be focused or edited. |
aria-invalid | boolean | — | Marks the value as wrong, and turns the border to the danger color. |
className | string | — | Extra classes, added after the textarea's own. |
Textarea is the native element, so every other textarea prop works.
The thinking
A textarea is an Input that grows. It should look and behave like one.
- The native element: There is no wrapper, so it behaves exactly like a normal textarea in forms and with the browser's spell check.
- One look for text fields: It shares its styles with Input, so a field of either kind has the same border, colors and states.
- Inside a Field: To connect a label, a hint and an error to it, render it through FieldControl. The Field page shows how.
Interface design
- Size: At least 96px tall, with 12px of side padding and 8px above and below, 16px text, and the md radius, 6px.
- Outline: A 1px border in the secondary text color, 4.2:1 against the page, the same as Input.
- Resizing: It can be dragged taller or shorter from the bottom corner. It can't be dragged wider, so it can't break the layout.
- States: Invalid turns the border to the danger color. Disabled is a surface fill with a hairline border and quiet text.
Interaction
- Hover: The border brightens, but only on a field that is valid and enabled.
- Focus: A 2px outline, 2px away from the field.
- Invalid: Keeps its danger border on hover and focus.
- Disabled: It can't be focused or edited, and shows a not-allowed cursor.
Empathy
- Name it: Use a Label with
htmlFor, or anaria-label. The bare fields in the demo have one. - Say what is wrong: Write the message next to the field and tie it in with
aria-describedby, as the invalid example does. - A placeholder is not a label: It disappears as soon as someone types, and its color is 4.2:1, just under the 4.5:1 that AA asks for.