---
title: Textarea
description: Textareas capture multi-line text (descriptions, comments, and messages) with configurable row height.
---

import { meta as TextareaMeta, Default } from '@/content/stories/textarea';
import Canvas from '@/components/component-docs/canvas';
import Demo from '@/components/component-docs/demo';

export const config = {
	dependencies: {
		components: [Canvas, Demo],
		scripts: [
			'../../../components/component-docs/demo.script.tsx',
			'../../../components/component-docs/canvas.script.tsx',
			'../../../components/component-docs/controls.script.tsx',
		],
	},
};


# Textarea

<p class="docs-lede">Textareas capture multi-line text (descriptions, comments, and messages) with configurable row height.</p>

## Try it

<Demo of={Default} meta={TextareaMeta} />

## Usage

Wrap in `RuiField` with a label. Set `rows` for initial height.

```tsx
import { RuiTextarea } from '@ecopages/radiant-ui/textarea';
import { RuiField } from '@ecopages/radiant-ui/field';
import { RuiLabel } from '@ecopages/radiant-ui/label';

<RuiField name="bio">
  <RuiLabel>Bio</RuiLabel>
  <RuiTextarea rows={4} placeholder="Tell us about yourself" />
</RuiField>
```

## Set initial rows

<p>Match `rows` to the expected content length. Short notes need fewer rows than essay-length fields.</p>

## Theming

Textarea surfaces map to **semantic surface roles**, never Tailwind palette steps:

| Part | CSS roles |
| --- | --- |
| Surface (`.rui-textarea`) | `border`, `background`, `rounded-control`, `text-control` |
| Focus / invalid | `focus-ring` / `error` |
| Disabled | `opacity-disabled` |

Geometry shares control tokens (`--space-control-x`, `--space-control-y`, `--radius-control`, `--text-control`) with `RuiInput` and `RuiButton` so form rows align. Override at the theme layer, not in component CSS.

## API

`RuiTextarea` is a **presentational view**, no custom element. It renders a native `<textarea>` marked with `data-rui-control` so `<rui-field>` can wire labeling, `aria-*`, and validation.

### Props

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `value` | `string` | | Initial text. |
| `placeholder` | `string` | | Placeholder shown before input. |
| `disabled` | `boolean` | `false` | Disabled state. |
| `name` | `string` | | Form field name. |
| `rows` | `number` | `3` | Initial row height. |
| `id` | `string` | | Control id (used by `<rui-field>` for label/described-by). |
| `aria-label` | `string` | | Accessible name when there is no visible `RuiLabel`. |
| `on:input` / `on:change` / `on:blur` | `(event) => void` | | Native event handlers. |
| Native `<textarea>` attributes | | | `required`, `maxlength`, `autocomplete`, and other unprefixed native attributes type-check and render on the control. |

### CSS classes

Public BEM classes (documented via `@cssclass`):

| Class | Description |
| --- | --- |
| `.rui-textarea` | Bordered, resizable multi-line input. |

### Theme roles

| Part | CSS variables consumed |
| --- | --- |
| Surface | `--border`, `--background`, `--on-background`, `--radius-control`, `--space-control-x`, `--space-control-y`, `--text-control`, `--duration-normal` |
| Focus / invalid | `--focus-ring` / `--error` |
| Disabled | `--opacity-disabled` |

## Accessibility

- Every textarea needs a visible label via `RuiLabel`.
- Character limits should be announced and visible, not hidden until exceeded.
- Disabled textareas should explain why editing is unavailable.
