OperantAI Workflow Design System
ComfortableCompact

Textarea is a multi-line text input. For freeform AI instructions (the prompt box a user types into to direct an agent), prefer .ods-composer instead — per the AGENTS.md decision table, a bare Textarea is for general multi-line form fields, not the primary AI input surface.

Installation

HTML — already included if you loaded the 3 stylesheets — see the Installation guide.

React

npm install @operant/ui

@operant/ui is currently a monorepo workspace package, not yet published to the public npm registry.

Usage

HTML

<textarea class="ods-textarea" placeholder="Describe what you want the agent to do…"></textarea>

React

import { Textarea } from "@operant/ui";

export default function Example() {
  return <Textarea placeholder="Describe what you want the agent to do…" />;
}

Examples

Basic

HTML

<textarea class="ods-textarea" placeholder="Describe what you want the agent to do…"></textarea>

React

<Textarea placeholder="Describe what you want the agent to do…" />

Invalid

Pass invalid (React) or add the .is-invalid class directly (HTML) to tint the border --color-danger. In React, aria-invalid is set automatically from invalid unless you explicitly override it.

HTML

<textarea class="ods-textarea is-invalid" aria-invalid="true"></textarea>

React

<Textarea invalid defaultValue="" />

As a labeled field

Pair with Field / FieldLabel / FieldHint from the Input component set for a complete labeled form field with supplementary guidance text.

HTML

<div class="ods-field">
  <label class="ods-label" for="prompt">Prompt</label>
  <textarea class="ods-textarea" id="prompt"
    placeholder="Summarize the last 24h of incidents and cite the sources you used."></textarea>
  <span class="ods-hint">Markdown and <code>{variable}</code> interpolation are supported.</span>
</div>

React

import { Field, FieldLabel, FieldHint } from "@operant/ui";
import { Textarea } from "@operant/ui";

<Field style={{ width: 420 }}>
  <FieldLabel htmlFor="prompt">Prompt</FieldLabel>
  <Textarea
    id="prompt"
    placeholder="Summarize the last 24h of incidents and cite the sources you used."
  />
  <FieldHint>Markdown and `{"{"}variable{"}"}` interpolation are supported.</FieldHint>
</Field>

Invalid field with an error message

FieldError renders role="alert" so the validation message is announced immediately, per AGENTS.md #8 ("errors via role=alert").

HTML

<div class="ods-field">
  <label class="ods-label" for="prompt-invalid">Prompt</label>
  <textarea class="ods-textarea is-invalid" id="prompt-invalid" aria-invalid="true"></textarea>
  <span class="ods-error-text" role="alert">A prompt is required to start a run.</span>
</div>

React

import { Field, FieldLabel, FieldError } from "@operant/ui";
import { Textarea } from "@operant/ui";

<Field style={{ width: 420 }}>
  <FieldLabel htmlFor="prompt-invalid">Prompt</FieldLabel>
  <Textarea id="prompt-invalid" invalid defaultValue="" />
  <FieldError>A prompt is required to start a run.</FieldError>
</Field>

API Reference

<Textarea>

Extends React.TextareaHTMLAttributes<HTMLTextAreaElement> — every native <textarea> attribute (rows, maxLength, onChange, disabled, etc.) is forwarded to the underlying element.

PropTypeDefaultDescription
invalidbooleanfalseApplies .is-invalid and, unless aria-invalid is explicitly passed, sets aria-invalid={true} automatically.
classNamestring—Merged with .ods-textarea.
refRef<HTMLTextAreaElement>—Forwarded to the underlying <textarea>.

HTML-side classes: .ods-textarea (shares base sizing/border/focus rules with .ods-input/.ods-select; adds resize: vertical and a min-height: 84px floor), .is-invalid (tints the border to --color-danger). Pair with .ods-field / .ods-label / .ods-hint / .ods-error-text for a complete field (see the Input component docs).

Accessibility

  • invalid wires aria-invalid automatically — screen readers announce the invalid state even if you forget to set the ARIA attribute yourself. Pass aria-invalid explicitly only if you need to override this behavior.
  • Focus is visible via :focus → --shadow-focus (textarea uses :focus, not :focus-visible, since typing directly into a field is itself an intentional keyboard/pointer action).
  • Always associate a <label> (FieldLabel htmlFor= / <label for=>) with the textarea's id — a placeholder alone is not an accessible label and disappears once text is entered.
  • Pair an invalid textarea with a FieldError/.ods-error-text[role="alert"] describing what's wrong — the red border alone is not sufficient (AGENTS.md #4: color alone is never the only signal).
  • The resize: vertical behavior lets users expand the control to see more of what they've typed — don't override it to resize: none for freeform content.