---
outline: deep
---

# Textarea

Textareas let users enter and edit multi-line text.

**`.l-textarea`** — Native HTML Element

```html
<l-form-field>
  <label>Message</label>
  <textarea
    rows="4"
    placeholder="Tell us what's on your mind…"
  ></textarea>
  <p class="l-hint">We usually reply within a day.</p>
</l-form-field>
```

[`l-form-field`](/elements/form-field) auto-styles a bare `<textarea>` and wires the accessibility (label, hint, error, `aria-*`). Standalone, apply `.l-textarea` to the textarea yourself.

## Options

### Rows

The native `rows` attribute sets the initial height; the textarea never shrinks below one control line.

```html
<l-form-field class="max-w-sm">
  <label>Message</label>
  <textarea
    rows="4"
    placeholder="Tell us what's on your mind…"
  ></textarea>
  <p class="l-hint">We usually reply within a day.</p>
</l-form-field>
```

### Resize

`data-resize` controls the resize handle: `vertical` (default), `none`, `both`, or `auto`. `auto` grows the box with its content and hides the handle — where `field-sizing` is unsupported it keeps the `rows` height.

```html
<div class="grid gap-4 max-w-sm">
  <l-form-field>
    <label>Vertical (default)</label>
    <textarea
      rows="3"
      placeholder="Drag the bottom edge to resize…"
    ></textarea>
  </l-form-field>
  <l-form-field>
    <label>None</label>
    <textarea
      data-resize="none"
      rows="3"
      placeholder="Fixed height"
    ></textarea>
  </l-form-field>
  <l-form-field>
    <label>Auto</label>
    <textarea
      data-resize="auto"
      rows="2"
      placeholder="Grows as you type…"
    ></textarea>
  </l-form-field>
</div>
```

### States

Native `disabled` and `readonly`. Invalid is styled via `:user-invalid` (after interaction) or by setting `aria-invalid="true"` — inside `l-form-field` this is managed for you.

```html
<div class="grid gap-4 max-w-sm">
  <l-form-field>
    <label>Default</label>
    <textarea
      rows="3"
      placeholder="Placeholder text"
    ></textarea>
  </l-form-field>
  <l-form-field>
    <label>Read-only</label>
    <textarea
      rows="3"
      readonly
    >
Read-only value</textarea
    >
  </l-form-field>
  <l-form-field>
    <label>Disabled</label>
    <textarea
      rows="3"
      disabled
    >
Disabled value</textarea
    >
  </l-form-field>
  <l-form-field>
    <label>Invalid</label>
    <textarea
      rows="3"
      required
      aria-invalid="true"
      placeholder="Placeholder text"
    ></textarea>
    <p class="l-error">Please enter a message.</p>
  </l-form-field>
</div>
```

### Size

`data-size` maps the single-line min-height to the shared control scale (`xs`–`xl`, default `md`); the padding scales with it.

```html
<div class="grid gap-4 max-w-sm">
  <textarea
    class="l-textarea"
    data-size="xs"
    rows="2"
    placeholder="Extra small"
  ></textarea>
  <textarea
    class="l-textarea"
    data-size="sm"
    rows="2"
    placeholder="Small"
  ></textarea>
  <textarea
    class="l-textarea"
    data-size="md"
    rows="2"
    placeholder="Medium"
  ></textarea>
  <textarea
    class="l-textarea"
    data-size="lg"
    rows="2"
    placeholder="Large"
  ></textarea>
  <textarea
    class="l-textarea"
    data-size="xl"
    rows="2"
    placeholder="Extra large"
  ></textarea>
</div>
```

### Radius

Override `--border-radius` for the corners.

## Accessibility

### Criteria

- **Accessible name** — Must have an associated `<label>` (wrap the textarea or use `for`/`id`)
- **Focus visible** — Keyboard focus shows a 2px outline via `:focus-visible`
- **Errors identified** — `aria-invalid` marks the field; pair it with a visible message linked via `aria-describedby`
- **Required state** — Native `required` communicates a mandatory field to assistive tech

### Rules

- Always pair the textarea with a `<label>` — wrap the textarea or link with `for`/`id`
- Never rely on `placeholder` as the label — it disappears on input and fails contrast
- Pair `maxlength` with a visible, `aria-describedby`-linked character counter rather than silently truncating input

### Keyboard interactions

- `Tab` — Moves focus to the textarea
- `Enter` — Inserts a line break (does not submit the form)

## API reference

### Importing

```css
@import 'luxen-ui/css/textarea';
```

The textarea is CSS-only — no JavaScript import.

### Attributes & Properties

- **rows** — Number of visible text lines — sets the initial height.
- **data-size**: `xs | sm | md | lg | xl` — Single-line min-height on the shared `--l-size-control-*` scale (default `md`). Affects only the control, not the label or hint/error.
- **data-resize**: `vertical | none | both | auto` — User resize handle. Defaults to `vertical`. `auto` grows the box with its content via `field-sizing` (progressive: falls back to the `rows` height where unsupported).
- **placeholder** — Native placeholder text.
- **disabled** — Disables the textarea.
- **required** — Marks the textarea as required for form submission.
- **readonly** — Makes the textarea read-only.
- **maxlength** — Native maximum character count.
- **aria-invalid** — Set to `true` to force the invalid style (otherwise applied via `:user-invalid`). `l-form-field` manages this automatically.

### CSS classes

- `.l-textarea` — Base textarea style, applied to a `<textarea>`. Inside `l-form-field` a bare textarea is auto-styled, so the class is optional there.

### CSS custom properties

- `--height` (default: `var(--l-form-control-height)`) — Single-line min-height; `rows` grows the control from here.
- `--border-radius` (default: `var(--l-form-control-border-radius)`) — Control border radius.
