---
title: Textarea
description: "A multi-line text input for longer content with built-in label, description, and error support."
order: 33
tags: [components]
---

import { Preview } from "$lib/components/preview";
import { Textarea } from "areia";

# Textarea

A styled multi-line text input for longer content. When you pass `label`, `description`, or `error`, `Textarea` delegates to `Field` for accessible labeling, descriptions, validation messaging, and field state attributes. Supports vertical resizing by default.

[Source Code](https://github.com/ilhajs/areia/blob/main/packages/areia/src/components/textarea/index.ts)

<Preview
  code={
    'import ilha from "ilha";\nimport { Textarea } from "areia";\n\nexport default ilha.render(() => (\n  <Textarea\n    label="Bio"\n    description="Tell us a little about yourself. Max 500 characters."\n    placeholder="I design and build design systems..."\n    rows={4}\n  />\n));'
  }
  lang="tsx"
>
  <Textarea
    label="Bio"
    description="Tell us a little about yourself. Max 500 characters."
    placeholder="I design and build design systems..."
    rows={4}
  />
</Preview>

## Import

```ts
import { Textarea } from "areia";
```

## Usage

### With Built-in Field

Use the `label` prop to enable the `Field` wrapper with label, description, and error support.

<Preview
  code={
    'import ilha from "ilha";\nimport { Textarea } from "areia";\n\nexport default ilha.render(() => (\n  <Textarea\n    id="notes"\n    label="Notes"\n    description="Add any additional context for the reviewer."\n    placeholder="Write your notes here..."\n    rows={3}\n  />\n));'
  }
  lang="tsx"
>
  <Textarea
    id="notes"
    label="Notes"
    description="Add any additional context for the reviewer."
    placeholder="Write your notes here..."
    rows={3}
  />
</Preview>

### Bare Textarea

For custom form layouts, use `Textarea` without `label`. Provide `aria-label` or `aria-labelledby` for accessibility. Use `Textarea.Static(...)` when composing a raw textarea inside `Field` manually.

<Preview
  code={
    'import ilha from "ilha";\nimport { Textarea } from "areia";\n\nexport default ilha.render(() => (\n  <Textarea\n    aria-label="Message"\n    placeholder="Type your message..."\n    rows={4}\n  />\n));'
  }
  lang="tsx"
>
  <Textarea
    aria-label="Message"
    placeholder="Type your message..."
    rows={4}
  />
</Preview>

## Examples

### With Label and Description

The `label` prop enables the `Field` wrapper with automatic vertical layout.

<Preview
  code={
    'import ilha from "ilha";\nimport { Textarea } from "areia";\n\nexport default ilha.render(() => (\n  <Textarea\n    id="bio"\n    label="Bio"\n    description="A short description that appears on your public profile."\n    placeholder="I design and build design systems..."\n    rows={4}\n  />\n));'
  }
  lang="tsx"
>
  <Textarea
    id="bio"
    label="Bio"
    description="A short description that appears on your public profile."
    placeholder="I design and build design systems..."
    rows={4}
  />
</Preview>

### With Error

Pass `error` as a string for simple error messages. Error styling is automatically applied when the `error` prop is truthy.

<Preview
  code={
    'import ilha from "ilha";\nimport { Textarea } from "areia";\n\nexport default ilha.render(() => (\n  <Textarea\n    id="message-error"\n    label="Message"\n    placeholder="Type your message..."\n    value="This message is way too long for the field limit..."\n    error="Message must be less than 500 characters."\n  />\n));'
  }
  lang="tsx"
>
  <Textarea
    id="message-error"
    label="Message"
    placeholder="Type your message..."
    value="This message is way too long for the field limit..."
    error="Message must be less than 500 characters."
  />
</Preview>

### With Validation Object

Pass `error` as an object with a `message` property when you want to keep validation metadata near the field.

<Preview
  code={
    'import ilha from "ilha";\nimport { Textarea } from "areia";\n\nexport default ilha.render(() => (\n  <Textarea\n    id="comment"\n    label="Comment"\n    required\n    placeholder="Share your thoughts..."\n    error={{\n      message: "Comment is required.",\n      match: "valueMissing",\n    }}\n  />\n));'
  }
  lang="tsx"
>
  <Textarea
    id="comment"
    label="Comment"
    required
    placeholder="Share your thoughts..."
    error={{
      message: "Comment is required.",
      match: "valueMissing",
    }}
  />
</Preview>

### Sizes

Four sizes are available: `xs`, `sm`, `base`, and `lg`. Sizes control text size, padding, and border radius — the height is determined by `rows` and content.

<Preview
  code={
    'import ilha from "ilha";\nimport { Textarea } from "areia";\n\nexport default ilha.render(() => (\n  <div class="flex w-full max-w-sm flex-col gap-3">\n    <Textarea\n      size="xs"\n      aria-label="Extra small textarea"\n      placeholder="Extra small"\n      rows={2}\n    />\n    <Textarea\n      size="sm"\n      aria-label="Small textarea"\n      placeholder="Small"\n      rows={2}\n    />\n    <Textarea\n      size="base"\n      aria-label="Base textarea"\n      placeholder="Base"\n      rows={2}\n    />\n    <Textarea\n      size="lg"\n      aria-label="Large textarea"\n      placeholder="Large"\n      rows={2}\n    />\n  </div>\n));'
  }
  lang="tsx"
>
  <div class="flex w-full max-w-sm flex-col gap-3">
    <Textarea
      size="xs"
      aria-label="Extra small textarea"
      placeholder="Extra small"
      rows={2}
    />
    <Textarea
      size="sm"
      aria-label="Small textarea"
      placeholder="Small"
      rows={2}
    />
    <Textarea
      size="base"
      aria-label="Base textarea"
      placeholder="Base"
      rows={2}
    />
    <Textarea
      size="lg"
      aria-label="Large textarea"
      placeholder="Large"
      rows={2}
    />
  </div>
</Preview>

### Custom Row Count

Use the `rows` prop to control the initial visible height before the content grows.

<Preview
  code={
    'import ilha from "ilha";\nimport { Textarea } from "areia";\n\nexport default ilha.render(() => (\n  <div class="flex w-full max-w-sm flex-col gap-3">\n    <Textarea\n      label="Short response"\n      placeholder="A couple of lines..."\n      rows={2}\n    />\n    <Textarea\n      label="Detailed response"\n      placeholder="Take your time to explain..."\n      rows={5}\n    />\n  </div>\n));'
  }
  lang="tsx"
>
  <div class="flex w-full max-w-sm flex-col gap-3">
    <Textarea
      label="Short response"
      placeholder="A couple of lines..."
      rows={2}
    />
    <Textarea
      label="Detailed response"
      placeholder="Take your time to explain..."
      rows={5}
    />
  </div>
</Preview>

### Disabled

<Preview
  code={
    'import ilha from "ilha";\nimport { Textarea } from "areia";\n\nexport default ilha.render(() => (\n  <Textarea\n    label="Notes"\n    placeholder="Write your notes here..."\n    disabled\n  />\n));'
  }
  lang="tsx"
>
  <Textarea
    label="Notes"
    placeholder="Write your notes here..."
    disabled
  />
</Preview>

### Optional Field

Set `required={false}` to show optional text after the label.

<Preview
  code={
    'import ilha from "ilha";\nimport { Textarea } from "areia";\n\nexport default ilha.render(() => (\n  <Textarea\n    id="comments"\n    label="Additional comments"\n    required={false}\n    placeholder="Anything else we should know..."\n    rows={3}\n  />\n));'
  }
  lang="tsx"
>
  <Textarea
    id="comments"
    label="Additional comments"
    required={false}
    placeholder="Anything else we should know..."
    rows={3}
  />
</Preview>

### With Label Tooltip

Use `labelTooltip` to add additional context via a native `title` attribute on the label.

<Preview
  code={
    'import ilha from "ilha";\nimport { Textarea } from "areia";\n\nexport default ilha.render(() => (\n  <Textarea\n    id="release-notes"\n    label="Release notes"\n    labelTooltip="These notes are published with the release and visible to all users."\n    placeholder="What changed in this version?"\n    rows={4}\n  />\n));'
  }
  lang="tsx"
>
  <Textarea
    id="release-notes"
    label="Release notes"
    labelTooltip="These notes are published with the release and visible to all users."
    placeholder="What changed in this version?"
    rows={4}
  />
</Preview>

### Rich Label

The `label` prop accepts markup for richer formatting.

<Preview
  code={
    'import ilha from "ilha";\nimport { Textarea } from "areia";\n\nexport default ilha.render(() => (\n  <Textarea\n    id="review-notes"\n    label={\n      <>\n        Notes for <strong>review</strong>\n      </>\n    }\n    required\n    placeholder="Add notes for the reviewer..."\n    rows={3}\n  />\n));'
  }
  lang="tsx"
>
  <Textarea
    id="review-notes"
    label={
      <>
        Notes for <strong>review</strong>
      </>
    }
    required
    placeholder="Add notes for the reviewer..."
    rows={3}
  />
</Preview>

### Bare Textarea with Error

Error messages and descriptions can render without a visible label. Use `aria-label` to keep the textarea accessible.

<Preview
  code={
    'import ilha from "ilha";\nimport { Textarea } from "areia";\n\nexport default ilha.render(() => (\n  <Textarea\n    id="feedback-error"\n    aria-label="Feedback"\n    placeholder="Share your feedback..."\n    description="Your feedback helps us improve the product."\n    error="Feedback is currently unavailable."\n  />\n));'
  }
  lang="tsx"
>
  <Textarea
    id="feedback-error"
    aria-label="Feedback"
    placeholder="Share your feedback..."
    description="Your feedback helps us improve the product."
    error="Feedback is currently unavailable."
  />
</Preview>

### Custom Resize Behavior

The textarea defaults to vertical-only resize (`resize-vertical`). Override it by passing a Tailwind resize utility via `class`.

<Preview
  code={
    'import ilha from "ilha";\nimport { Textarea } from "areia";\n\nexport default ilha.render(() => (\n  <div class="flex w-full max-w-sm flex-col gap-3">\n    <Textarea\n      label="Vertical resize"\n      placeholder="Resize vertically only..."\n      rows={3}\n    />\n    <Textarea\n      label="Horizontal resize"\n      placeholder="Resize horizontally only..."\n      class="resize-horizontal"\n      rows={3}\n    />\n    <Textarea\n      label="No resize"\n      placeholder="Cannot be resized..."\n      class="resize-none"\n      rows={3}\n    />\n  </div>\n));'
  }
  lang="tsx"
>
  <div class="flex w-full max-w-sm flex-col gap-3">
    <Textarea
      label="Vertical resize"
      placeholder="Resize vertically only..."
      rows={3}
    />
    <Textarea
      label="Horizontal resize"
      placeholder="Resize horizontally only..."
      class="resize-horizontal"
      rows={3}
    />
    <Textarea
      label="No resize"
      placeholder="Cannot be resized..."
      class="resize-none"
      rows={3}
    />
  </div>
</Preview>

## API Reference

`Textarea` accepts standard HTML textarea attributes plus the following props.

| Prop           | Type                                               | Default     | Description                                                                       |
| -------------- | -------------------------------------------------- | ----------- | --------------------------------------------------------------------------------- |
| `label`        | `unknown`                                          | -           | Label content for the textarea. Enables the field wrapper.                        |
| `labelTooltip` | `string`                                           | -           | Tooltip text rendered as a native title on the label text.                        |
| `description`  | `unknown`                                          | -           | Helper text displayed below the textarea.                                         |
| `error`        | `unknown \| { message: unknown; match?: unknown }` | -           | Error message. When truthy, error styling is automatically applied.               |
| `rows`         | `number`                                           | `3`         | Number of visible text rows controlling the initial height.                       |
| `size`         | `"xs" \| "sm" \| "base" \| "lg"`                   | `"base"`    | Textarea size controlling text size, padding, and border radius.                  |
| `variant`      | `"default" \| "error"`                             | `"default"` | Visual variant. Explicit `variant` takes precedence over automatic error styling. |
| `class`        | `string`                                           | -           | Additional CSS classes applied to the textarea.                                   |
| `className`    | `string`                                           | -           | Alias for `class`.                                                                |

### Value Resolution

The initial textarea content is resolved in this order of precedence:

1. `value` — controlled value
2. `defaultValue` — uncontrolled default
3. `children` — content passed between tags

The resolved value is rendered as the textarea's text content, not as a `value` HTML attribute.

### Validation Error Types

When using `error` as an object, the `match` property can mirror HTML5 `ValidityState` keys.

| Match          | Description                                               |
| -------------- | --------------------------------------------------------- |
| `valueMissing` | Required field is empty.                                  |
| `tooShort`     | Value is shorter than `minLength`.                        |
| `tooLong`      | Value is longer than `maxLength`.                         |
| `true`         | Always show the error, useful for server-side validation. |

## Accessibility

### Label Requirement

Textareas should have an accessible name via one of:

- `label` prop
- `placeholder` plus `aria-label` for bare textareas
- `aria-labelledby` for custom label association

### Error Association

When an `id` is provided, descriptions and error messages are automatically associated with the textarea using `aria-describedby`.
