Skip to content

ComponentsForms

Textarea

A multi-line text input for comments, descriptions and messages. Comes as a complete field with label, help text, character count and validation, or as a bare styled textarea. Resizes vertically, stays fixed, or grows with its content.

React AriaSource
Your feedback goes to the product team.

Installation

pnpm dlx shadcn@latest add @desyne/textarea

The CLI installs dependencies and any other components this one uses.

Usage

tsx
import { Textarea, TextareaField } from "@/components/ui/textarea";
tsx
<TextareaField label="Description" name="description" placeholder="Describe the issue" />

The file exports two components:

  • TextareaField — a React Aria TextField with a label, description, character count and error message around a Textarea. Use it in forms.
  • Textarea — just the styled <textarea>. Use it when you lay out the label yourself, or inside a custom composer.

The two onChange signatures differ

TextareaField is a React Aria field, so onChange receives the new string: onChange={setValue}. A standalone Textarea is a plain textarea element, so onChange receives the event: onChange={(e) => setValue(e.target.value)}.

When to use

  • Textarea — free-form text that may span several lines: comments, bios, descriptions, messages.
  • Text Field — short, single-line values such as names, emails or titles.
  • Search Field — search queries.
  • A rich-text editor — when users need formatting, mentions or embeds. A textarea holds plain text only.

Anatomy

tsx
<TextareaField>      {/* root: React Aria TextField, owns value and validation */}
  <Label />          {/* from `label` */}
  <Textarea />       {/* the native <textarea> */}
  <div>              {/* only when there is a description or a count */}
    <Description />  {/* from `description` */}
    <span />         {/* `value.length / maxLength` when `showCount` */}
  </div>
  <FieldError />     {/* shown only while invalid */}
</TextareaField>
PartRendersNotes
TextareaField<div>Root. Carries data-slot="textarea-field", the group/field class and all state attributes.
Label<label>Rendered when label is set. Adds a red * when isRequired.
Textarea<textarea>The control itself, styled by textareaVariants. Carries data-slot="textarea". Receives placeholder, rows, variant and resize from the field.
Description<span slot="description">Rendered when description is set. Linked with aria-describedby.
Count<span>Rendered when showCount and maxLength are both set. Right-aligned, tabular numbers.
FieldError<span slot="errorMessage">Rendered only while the field is invalid.

Examples

Variants

outline for most forms, filled for dense layouts and tinted surfaces, underlined for minimal, inline editing. They match the variants of the other field components.

Resize behavior

resize="vertical" (default) lets users drag the corner. none keeps a fixed height and scrolls. auto grows the textarea with its content using CSS field-sizing: content; in browsers without support it falls back to a fixed height. Use rows to set the initial height; the minimum is 80px.

Drag the corner to resize.
Fixed height; content scrolls.
Grows with its content.

Character count

showCount shows value.length / maxLength under the field when maxLength is set. It works on controlled and uncontrolled fields: with value the count reads from it, otherwise the field tracks the length itself, starting from defaultValue. Your onChange is still called. maxLength also stops input at the limit.

Grows with its content.19/160

Disabled and read only

isDisabled dims the field and removes it from the tab order. isReadOnly keeps the text focusable and selectable, which suits quoting an original message.

Notes are locked once a ticket is closed.

Required and native validation

isRequired, minLength and maxLength use the browser's constraint validation. With the default validationBehavior="native", the form won't submit while the field is invalid and the browser's message appears on submit.

Custom validation

validate receives the current value and returns an error string, or null when valid. With validationBehavior="aria" on the Form, errors appear live as the user types.

At least 5 words, so we can act on it.
Add 3 more words.

Form submission

Give the field a name and its value is included in FormData, line breaks and all.

Standalone

Textarea on its own is a styled <textarea> with the same variant and resize options. It has no label, so pass aria-label or aria-labelledby.

Custom composition

Build your own field from React Aria's TextField, the Label, Description and FieldError parts in field.tsx, and Textarea. Here the description sits above the input and the label row carries an "Optional" hint. Add group/field to the root so the label reacts to the disabled and required states.

Optional
Gate codes, where to leave the parcel, pets.

Recipes

Comment composer

A borderless Textarea inside a bordered box with a toolbar, growing with its content. ⌘ + Enter posts the comment via onKeyDown.

AL

Feedback form

A Select for the topic, a counted TextareaField, a pending submit button and a success state.

Send feedback

Tell us what's working and what isn't.

Topic
0/1000

Accessibility

  • TextareaField links the label, description and error message to the textarea with aria-labelledby and aria-describedby.
  • A standalone Textarea needs aria-label or aria-labelledby. A placeholder is not a label.
  • Invalid fields get aria-invalid, and the error text is announced.
  • The character count is plain text and isn't linked to the textarea. Mention the limit in description if screen-reader users need it up front.
  • Enter inserts a new line. If you add a submit shortcut, use a modifier (⌘ / Ctrl + Enter) and show it visibly, as in the comment recipe.

Keyboard

KeyAction
TabMoves focus into and out of the textarea
EnterInserts a new line

Styling

Data attributes

On the TextareaField root. Children can react with group-data-*/field: variants.

AttributePresent when
data-disabledisDisabled is true
data-readonlyisReadOnly is true
data-requiredisRequired is true
data-invalidValidation failed, or isInvalid is true

On the Textarea element (the variants style these):

AttributePresent when
data-hoveredHovered with a mouse or pen
data-focusedThe textarea has focus
data-focus-visibleFocus came from the keyboard
data-invalidThe field is invalid
data-disabledThe field is disabled

Slots

data-slotElement
textarea-fieldRoot of TextareaField
textarea<textarea>
label, description, field-errorField text

textareaVariants

The tailwind-variants function behind Textarea, with variant (outline, filled, underlined) and resize (none, vertical, auto). Use it to style a textarea from another library the same way.

tsx
import { textareaVariants } from "@/components/ui/textarea";

<textarea className={textareaVariants({ variant: "filled", resize: "none" })} />;

className on TextareaField styles the root; on Textarea it styles the textarea and also accepts a function of its render state (isFocused, isHovered, isInvalid, isDisabled).

API Reference

TextareaField

Prop

Type

Also accepts every prop of React Aria's TextField.

Textarea

Prop

Type

Accepts every native <textarea> attribute, plus the props of React Aria's TextArea. Inside a TextField, the field supplies the value, id and ARIA attributes.

  • Text Field — the single-line version, and the shared field building blocks.
  • Search Field — for search queries.
  • Select — shares the same field variants.