affectUI › Components › Inputs

Text Area

The multi-line twin of the Text Field – grows with the text, a character count, a button for more room and ⌘ Enter to send.

Free and open source (MIT). Install: npx shadcn@latest add https://www.affectui.com/r/text-area.json · Import from @/components/ui/text-area

When to use

  • Anything longer than a line in a form: descriptions, notes, bios, feedback, instructions or context for an assistant
  • Settings and creation dialogs next to Text Fields – same label, hint, error and focus, so a form reads as one

When not to use

  • A single line – names, emails, URLs – use the Text Field
  • Writing to an assistant or a person with send, attachments or a model picker – use the Chat Composer

Usage

import { useState } from "react";
import { TextArea } from "@/components/ui/text-area";

export function CompanyContext({ onSave }: { onSave: (text: string) => void }) {
  const [text, setText] = useState("");

  return (
    <TextArea
      label="Company context"
      hint="Added to every chat in this workspace."
      value={text}
      onChange={(e) => setText(e.target.value)}
      maxLength={2000}
      counter
      minRows={3}
      maxRows={8}
      expandable
      onSubmitShortcut={onSave}
    />
  );
}

Props

NameTypeDefaultDescription
labelReactNodeShown above the field; clicking it focuses the textarea
hintReactNodeA quiet line under the field – the error takes its place while there is one
errorReactNodeMarks the field invalid (red edge and a faint red fill, aria-invalid) and shows the message where the hint was
optionalbooleanfalseAdds "Optional" next to the label
counterbooleanfalseShows "120/2,000" under the field, red once a prefilled value is over the limit; needs maxLength
minRowsnumber3Height when empty, in lines
maxRowsnumber10It grows with the text up to this many lines, then scrolls inside
expandablebooleanfalseA small button in the top corner that gives the field more room – and back
expandedRowsnumber16Lines while expanded
expandedbooleanControls the expanded state from outside; pair with onExpandedChange
onExpandedChange(expanded: boolean) => voidCalled when the button gives more or less room
onSubmitShortcut(value: string) => void⌘/Ctrl+Enter – save or send without reaching for the mouse; Enter alone still makes a new line
inputClassNamestringExtra class on the textarea itself, e.g. a monospace font for prompts
classNamestringExtra class on the wrapper
...textarea propsComponentProps<"textarea">Everything else – value, defaultValue, onChange, name, placeholder, required, disabled, readOnly, maxLength, ref – goes to the textarea

Keyboard

  • Enter: A new line
  • ⌘ / Enter: Calls onSubmitShortcut (Ctrl+Enter on Windows and Linux)
  • Tab: Moves on to the More room button, then out of the field

Accessibility

  • A real <textarea> with a <label> tied to it – works with forms, autofill and screen readers
  • The hint, the error and the count are linked with aria-describedby; an error sets aria-invalid and is announced politely
  • The More room button reports its state with aria-pressed and points at the field with aria-controls; the shortcut is listed in aria-keyshortcuts
  • Without a label, a text placeholder becomes the field's accessible name – better still, pass aria-label

Responsive

  • Fills its container; it measures again when its width changes, because the text wraps differently
  • On touch screens it uses 16px text, so iOS doesn't zoom in on focus, and the corner button grows to 32px
  • Dark mode built in: switches when an ancestor (usually <html>) has the class dark or data-theme="dark"

Requirements

  • React 19

Related

  • Text Field – One input for every form – label, hint and error in place, prefix and suffix, clear, show-password and copy buttons, a character count
  • Chat Composer – Prompt composer with model picker, voice input, app connections and approval request
  • Button – Primary, secondary, ghost and destructive buttons with icons, sizes and a shimmering loading state
  • Stepper Form – A multi-step flow for onboarding and setup – steps that fill and check off, sliding steps, a card that morphs to each step, inline validation and a success state