# Textarea

> Package `@umriss-ui/core`, version 0.40.0. Demo page: <https://romanhaendler.github.io/umriss-ui/core/textarea/>. Every page in one line: [llms.txt](https://romanhaendler.github.io/umriss-ui/core/llms.txt); every page in full: [llms-full.txt](https://romanhaendler.github.io/umriss-ui/core/llms-full.txt).

Text over several lines (multi-line text field): a comment, a summary, a note to the driver. It can grow with its text and count the characters left.

```ts
import { Textarea } from "@umriss-ui/core";
```

Demo page: https://romanhaendler.github.io/umriss-ui/core/textarea/

## Examples

### Text area

Set `rows` for the height the text usually needs; the user can drag it taller.

```tsx
import { FormField, Textarea } from "@umriss-ui/core";

export default function TextArea() {
  return (
    <FormField label="Incident summary" style={{ maxWidth: 480 }}>
      <Textarea rows={3} placeholder="What happened, what customers saw, what is being done" />
    </FormField>
  );
}
```

### States

Errors come from the `FormField`; `size="sm"` suits dense forms, and `disabled` text stays readable but fixed.

```tsx
import { FormField, Grid, Textarea } from "@umriss-ui/core";

export default function States() {
  return (
    <Grid minItemWidth="260px" gap={4}>
      <FormField label="Customer impact" hint="Fixed height, draggable downwards.">
        <Textarea
          rows={4}
          defaultValue={"Checkout answered slowly from 09:40 to 10:10.\nAbout one payment in twelve timed out and was retried by the customer."}
        />
      </FormField>
      <FormField label="Next update" hint="Small size.">
        <Textarea size="sm" rows={2} placeholder="When and where the next update goes out" />
      </FormField>
      <FormField label="Root cause" error="Give the root cause in at least ten characters.">
        <Textarea rows={2} defaultValue="Unknown" />
      </FormField>
      <FormField label="Timeline" hint="Written by the incident bot.">
        <Textarea rows={2} disabled defaultValue="09:41 Alert fired: Checkout p95 above 300 ms." />
      </FormField>
    </Grid>
  );
}
```

### With an error

A `FormField` with an `error` marks the field invalid by itself. Without one, set `invalid` and tie your message to the field with `aria-describedby`.

```tsx
import { useId, useState } from "react";
import { Stack, Text, Textarea } from "@umriss-ui/core";

export default function WithAnError() {
  const [rootCause, setRootCause] = useState("Unknown");
  const messageId = useId();
  const error = rootCause.trim().length < 10 ? "Give the root cause in at least ten characters." : undefined;

  return (
    <Stack gap={1} style={{ maxWidth: 420 }}>
      <Textarea
        aria-label="Root cause"
        rows={3}
        value={rootCause}
        onChange={(event) => setRootCause(event.target.value)}
        invalid={error !== undefined}
        aria-describedby={error ? messageId : undefined}
      />
      {error && (
        <Text id={messageId} size="xs" style={{ color: "var(--u-color-danger-text)" }}>
          {error}
        </Text>
      )}
    </Stack>
  );
}
```

### Grow with the text

Set `autoGrow` so the field grows with its text, and `maxRows` where it should start to scroll instead.

```tsx
import { useState } from "react";
import { Button, FormField, Stack, Textarea } from "@umriss-ui/core";

const LONG = Array.from({ length: 12 }, (_, i) => `10:${String(10 + i * 2).padStart(2, "0")} Update ${i + 1}: error rate back under 1 %.`).join("\n");

export default function GrowWithTheText() {
  const [notes, setNotes] = useState("09:41 Alert fired.\n09:44 Priya Raman acknowledged.\n09:52 Rolled back release 4.18.");

  return (
    <FormField label="Responder notes" hint="Grows to at most eight lines." style={{ maxWidth: 520 }}>
      <Stack gap={2}>
        <Textarea autoGrow maxRows={8} value={notes} onChange={(event) => setNotes(event.target.value)} />
        <Stack direction="row" gap={2}>
          <Button size="sm" onClick={() => setNotes(LONG)}>
            Paste a long log
          </Button>
          <Button size="sm" variant="ghost" onClick={() => setNotes("")}>
            Clear
          </Button>
        </Stack>
      </Stack>
    </FormField>
  );
}
```

### Count the characters

Set `showCount` with a `maxLength` where a text goes somewhere short, such as a status page or a text message.

```tsx
import { useState } from "react";
import { FormField, Grid, Textarea } from "@umriss-ui/core";

export default function CountTheCharacters() {
  const [status, setStatus] = useState("Payments are slower than usual. We are on it.");
  const [sms, setSms] = useState(
    "Checkout is degraded since 09:40; release 4.18 rolled back, error rate falling, next update at 11:00 on the status page.",
  );

  return (
    <Grid minItemWidth="260px" gap={4}>
      <FormField label="Status page headline">
        <Textarea showCount maxLength={80} rows={2} value={status} onChange={(event) => setStatus(event.target.value)} />
      </FormField>
      <FormField label="Text message to on-call" hint="Set from outside, past the limit.">
        <Textarea showCount maxLength={100} rows={3} value={sms} onChange={(event) => setSms(event.target.value)} />
      </FormField>
    </Grid>
  );
}
```

## When to use something else

- A short answer on one line → Input

## Keyboard

| Key | Action |
|---|---|
| `Tab` | Moves into the field and on; it is never typed into the text. |
| `Enter` | Starts a new line; it does not send the form. |
| `Typing, arrows, Home / End` | The browser's own editing keys; the field adds none. |

## Accessibility

A native `textarea`. Inside a [FormField](https://romanhaendler.github.io/umriss-ui/core/formfield/) it takes the field's label, hint, required and invalid state; without one, give it an `aria-label`.

With `showCount` the characters left stand in a polite live region: a screen reader reads the count when it is free, not after every letter typed.

## API

### `TextareaProps`

| Prop | Type | Default | Description |
|---|---|---|---|
| `size` | `"sm" \| "md"` | the size of a `ControlSizeProvider` around it, else `"md"` | `sm` for a toolbar and dense forms, `md` otherwise.<br>Shown in: [Configurator](https://romanhaendler.github.io/umriss-ui/core/textarea/#configurator-textarea), [States](https://romanhaendler.github.io/umriss-ui/core/textarea/#states) |
| `chars` | `number` | — | The width in characters - the length of a line; the field adds its own padding. Given, the field is that wide wherever it stands, and never wider than its place. Without it the field fills its place, and is 40 characters wide where the place asks - in a row.<br>Shown in: [Configurator](https://romanhaendler.github.io/umriss-ui/core/textarea/#configurator-textarea) |
| `invalid` | `boolean` | — | Marks the field as invalid. `FormField` sets it itself as soon as it carries an `error` – by hand only needed without `FormField`.<br>Shown in: [Configurator](https://romanhaendler.github.io/umriss-ui/core/textarea/#configurator-textarea), [With an error](https://romanhaendler.github.io/umriss-ui/core/textarea/#with-an-error) |
| `autoGrow` | `boolean` | `false` | Grows with its content instead of scrolling.<br>Shown in: [Configurator](https://romanhaendler.github.io/umriss-ui/core/textarea/#configurator-textarea), [Grow with the text](https://romanhaendler.github.io/umriss-ui/core/textarea/#grow-with-the-text), [Resolve an incident (Scenarios)](https://romanhaendler.github.io/umriss-ui/core/#resolve-an-incident) and 1 more |
| `maxRows` | `number` | — | Upper bound for `autoGrow` in rows; beyond it the field scrolls.<br>Shown in: [Grow with the text](https://romanhaendler.github.io/umriss-ui/core/textarea/#grow-with-the-text), [Resolve an incident (Scenarios)](https://romanhaendler.github.io/umriss-ui/core/#resolve-an-incident), [Approve spending against a budget (Scenarios)](https://romanhaendler.github.io/umriss-ui/core/#approve-spending) |
| `showCount` | `boolean` | `false` | Shows at the bottom right how many characters still fit. Needs `maxLength`.<br>Shown in: [Count the characters](https://romanhaendler.github.io/umriss-ui/core/textarea/#count-the-characters) |
| `resize` | `"none" \| "vertical"` | `"none"` while the field grows by itself, else `"vertical"` | Whether the browser's handle for dragging is offered.<br>Shown in: [Configurator](https://romanhaendler.github.io/umriss-ui/core/textarea/#configurator-textarea) |
| `ref` | `Ref<HTMLTextAreaElement>` | — | Receives the component's root element. From `RefProp`.<br>Shown in: [A row that holds still (Sizes)](https://romanhaendler.github.io/umriss-ui/core/sizes/#a-row-that-holds-still), [An anchored surface (Popover)](https://romanhaendler.github.io/umriss-ui/core/popover/#anchored-surface), [Take the width of the trigger (Popover)](https://romanhaendler.github.io/umriss-ui/core/popover/#width-from-anchor) and 2 more |

Also takes every attribute of `<textarea>` – without `cols`.

## Known limits

- Plain text only: no formatting, mentions or rich text.

What umriss deliberately does not build, and why: [ADR-0032](https://romanhaendler.github.io/umriss-ui/what-umriss-ui-is-not/).
