stream/ui
Browse the library

Textarea

A multi-line text field in the same frame as Input, with an invalid state and vertical resizing.

stableTextarea.tsx

Examples

Basic

Markdown is supported.

textarea/basic.tsx
import { Textarea } from "@streamuilib/react";

export default function TextareaBasic() {
  return (
    <div className="w-full max-w-sm">
      <label htmlFor="textarea-feedback" className="mb-2 block font-bold">
        Feedback
      </label>
      <Textarea
        id="textarea-feedback"
        placeholder="What should we build next?"
        aria-describedby="textarea-feedback-hint"
      />
      <p id="textarea-feedback-hint" className="mt-2 text-sm text-ink-muted">
        Markdown is supported.
      </p>
    </div>
  );
}

Invalid and disabled

Write at least 20 characters.

textarea/states.tsx
import { Textarea } from "@streamuilib/react";

export default function TextareaStates() {
  return (
    <div className="flex w-full max-w-sm flex-col gap-6">
      <div>
        <label htmlFor="textarea-bio" className="mb-2 block font-bold">
          Bio
        </label>
        <Textarea id="textarea-bio" rows={3} defaultValue="Hi" invalid aria-describedby="textarea-bio-error" />
        <p id="textarea-bio-error" className="mt-2 text-sm font-semibold">
          Write at least 20 characters.
        </p>
      </div>
      <div>
        <label htmlFor="textarea-locked" className="mb-2 block font-bold">
          Admin notes
        </label>
        <Textarea id="textarea-locked" rows={2} defaultValue="Only admins can edit this." disabled />
      </div>
    </div>
  );
}

Installation

bun add @streamuilib/react
usage.tsx
import { Textarea } from "@streamuilib/react";

<label htmlFor="msg">Message</label>
<Textarea id="msg" rows={4} />

API

PropTypeDefaultDescription
invalidbooleanfalseSets aria-invalid and tints the field pink.
rowsnumber4Visible lines before scrolling.
frameClassNamestring—Classes for the outer frame.
classNamestring—Classes for the <textarea> itself.
...propsComponentProps<"textarea">—Every native textarea attribute.

Accessibility

  • Pair it with a <label>, and link hints or errors with aria-describedby.
  • Shares Input's frame, so focus, invalid, and disabled states look the same across a form.

Source

packages/react/src/Textarea.tsx
import type { ComponentProps } from "react";
import { cn } from "./cn";
import { fieldFrame } from "./field";

export type TextareaProps = ComponentProps<"textarea"> & {
  /** Marks the field invalid (sets `aria-invalid`) and tints it. */
  invalid?: boolean;
  /** Classes for the outer frame. `className` goes to the `<textarea>`. */
  frameClassName?: string;
};

export function Textarea({ invalid, frameClassName, className, rows = 4, ...props }: TextareaProps) {
  return (
    <div className={fieldFrame({ invalid, className: frameClassName })}>
      <textarea
        rows={rows}
        aria-invalid={invalid || undefined}
        className={cn(
          "w-full min-w-0 resize-y bg-transparent px-3 py-2.5 leading-relaxed placeholder:text-ink-muted focus:outline-none disabled:cursor-not-allowed",
          className,
        )}
        {...props}
      />
    </div>
  );
}