Siteplane UI

Textarea

Enter and edit multiline text.

Markdown

Shown on the project overview.

"use client";

import {
  Field,
  FieldDescription,
  FieldLabel,
} from "@/siteplane/components/field";
import { Textarea } from "@/siteplane/components/textarea";

export default function Example() {
  return (
    <Field className="w-full max-w-sm">
      <FieldLabel>Description</FieldLabel>
      <Textarea placeholder="What is this project about?" />
      <FieldDescription>Shown on the project overview.</FieldDescription>
    </Field>
  );
}

Installation

pnpm dlx shadcn@4.18.0 add @siteplane/textarea

Complete the one-time setup first. Registry JSON lists source files and dependencies.

For this page’s examples, also install:

pnpm dlx shadcn@4.18.0 add @siteplane/input

Field is installed with Input.

Usage

import { Textarea } from "@/siteplane/components/textarea";

<Textarea aria-label="Description" />;

Variants

Choose the control surface without changing its behavior. elevated lifts the field with the hairline and soft shadow of an elevated Card, so it reads on a white surface too.

"use client";

import { Field, FieldLabel } from "@/siteplane/components/field";
import { Textarea } from "@/siteplane/components/textarea";

export default function Example() {
  return (
    <div className="grid w-full max-w-sm gap-4">
      {(["default", "elevated", "inset"] as const).map((variant) => (
        <Field key={variant}>
          <FieldLabel>Description, {variant}</FieldLabel>
          <Textarea
            variant={variant}
            placeholder="What is this project about?"
          />
        </Field>
      ))}
    </div>
  );
}

Height by lines

minLines sets the least height of the textarea and maxLines the most, in text lines plus the padding of the size. Both take fractions, such as 2.5 lines for a description. In between, the textarea grows with its content; past maxLines its text scrolls.

The customer gets your reply by email.

"use client";

import {
  Field,
  FieldDescription,
  FieldLabel,
} from "@/siteplane/components/field";
import { Textarea } from "@/siteplane/components/textarea";

export default function Example() {
  return (
    <Field className="w-full max-w-sm">
      <FieldLabel>Reply</FieldLabel>
      <Textarea maxLines={6} minLines={2} placeholder="Write a reply…" />
      <FieldDescription>
        The customer gets your reply by email.
      </FieldDescription>
    </Field>
  );
}

Sizes

Omit size to inherit the provider's control size. Each size sets its own minimum height. The text is 14px, or 16px at xl; textSize sets it at any size. Touch screens always use 16px text.

"use client";

import { Field, FieldLabel } from "@/siteplane/components/field";
import { Textarea } from "@/siteplane/components/textarea";

const sizes = [
  { size: "sm", label: "small" },
  { size: "md", label: "medium" },
  { size: "lg", label: "large" },
  { size: "xl", label: "extra large" },
] as const;

export default function Example() {
  return (
    <div className="grid w-full max-w-sm gap-4">
      {sizes.map(({ size, label }) => (
        <Field key={size}>
          <FieldLabel>Description, {label}</FieldLabel>
          <Textarea
            size={size}
            defaultValue="Redesign the marketing site and move it to the new CMS."
          />
        </Field>
      ))}
    </div>
  );
}

Disabled, read-only and invalid

disabled blocks editing; readOnly keeps the value focusable and submitted. Set invalid on Field and explain the problem with FieldError.

Write at least 20 characters.
"use client";

import { Field, FieldError, FieldLabel } from "@/siteplane/components/field";
import { Textarea } from "@/siteplane/components/textarea";

export default function Example() {
  return (
    <div className="grid w-full max-w-sm gap-4">
      <Field disabled>
        <FieldLabel>Internal notes</FieldLabel>
        <Textarea defaultValue="Waiting for the final copy from the content team." />
      </Field>
      <Field>
        <FieldLabel>Release notes</FieldLabel>
        <Textarea
          readOnly
          defaultValue="Version 2.4 adds shared views and faster exports."
        />
      </Field>
      <Field invalid>
        <FieldLabel>Summary</FieldLabel>
        <Textarea defaultValue="Fixes" />
        <FieldError match>Write at least 20 characters.</FieldError>
      </Field>
    </div>
  );
}

Character limit

Control the value with value and onChange to show the remaining characters. maxLength enforces the limit.

280 characters left

"use client";

import {
  Field,
  FieldDescription,
  FieldLabel,
} from "@/siteplane/components/field";
import { Textarea } from "@/siteplane/components/textarea";
import { useState } from "react";

const maxLength = 280;

export default function Example() {
  const [value, setValue] = useState("");

  return (
    <Field className="w-full max-w-sm">
      <FieldLabel>Bio</FieldLabel>
      <Textarea
        maxLength={maxLength}
        placeholder="Tell your team a little about yourself."
        value={value}
        onChange={(event) => setValue(event.target.value)}
      />
      <FieldDescription>
        <span className="tabular-nums">{maxLength - value.length}</span>{" "}
        characters left
      </FieldDescription>
    </Field>
  );
}

API reference

Component properties
PropTypeDefaultDescription
sizesm | md | lg | xlprovider / lgVisual control size.
textSizesm | baseunsetText size at any control size: sm is 14px, base is 16px. Unset uses 14px, or 16px at xl; touch screens use 16px.
minLinesnumberunsetLeast height in text lines, fractions allowed, plus the padding of the size. Replaces the minimum height of the size; the textarea still grows with its content.
maxLinesnumberunsetMost height in text lines, fractions allowed, plus the padding of the size. The textarea grows up to it, then scrolls; unset, it grows without a limit. minLines wins where the two disagree.
variantdefault | elevated | insetdefaultSurface treatment.
unstyledbooleanfalseRemoves the outer surface recipe; preserves control anatomy.
classNamestringunsetStyles the outer span, not the native control.

TextareaProps and TextareaProfile export the types above. FieldPrimitive is also exported. Native textarea props, including required, name, value, defaultValue, readOnly, onChange and ref, reach the actual textarea through Field Control. There is no onValueChange prop. Because the textarea sizes to its content, the native rows does not set its height: use minLines and maxLines.

Other native attributes follow HTML textarea.

Read the installed source.

Usage notes

Always provide a visible label or an accessible name. Placeholder text is not a label. Use Field for related help and errors. Where the browser supports field-sizing: content, the textarea grows with its content and shows no resize grip; in other browsers it keeps its height and a vertical resize grip. There is no autoGrow prop. The control does not persist data or implement request loading. A plain native form and the kit Form have different validation presentation; see Forms.