Textarea

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.

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

Examples

Text area

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

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.

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.

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.

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.

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

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 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

NameTypeDefaultDescription
size"sm" | "md"the size of a ControlSizeProvider around it, else "md"sm for a toolbar and dense forms, md otherwise.

Shown in: Configurator, States

charsnumber—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.

Shown in: Configurator

invalidboolean—Marks the field as invalid. FormField sets it itself as soon as it carries an error – by hand only needed without FormField.

Shown in: Configurator, With an error

autoGrowbooleanfalseGrows with its content instead of scrolling.

Shown in: Configurator, Grow with the text, Resolve an incident (Scenarios) and 1 more

maxRowsnumber—Upper bound for autoGrow in rows; beyond it the field scrolls.

Shown in: Grow with the text, Resolve an incident (Scenarios), Approve spending against a budget (Scenarios)

showCountbooleanfalseShows at the bottom right how many characters still fit. Needs maxLength.

Shown in: Count the characters

resize"none" | "vertical""none" while the field grows by itself, else "vertical"Whether the browser's handle for dragging is offered.

Shown in: Configurator

refRef<HTMLTextAreaElement>—Receives the component's root element. from RefProp

Shown in: A row that holds still (Sizes), An anchored surface (Popover), Take the width of the trigger (Popover) and 2 more

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

Known limits

What umriss deliberately does not build, and why: ADR-0032.

Every page of @umriss-ui/core

Getting started

Customising

Layout

Typography

Actions

Forms

Choosing

Dates and times

Feedback

Overlays

Navigation

Data display

API index