Skip to content

Textarea

A controlled multi-line text input widget for notes, descriptions, and free-form text.

Usage

ui.textarea({
  id: "notes",
  value: state.notes,
  rows: 5,
  onInput: (value) => app.update((s) => ({ ...s, notes: value })),
})

Props

Prop Type Default Description
id string required Unique identifier for focus and routing
value string required Current controlled textarea value
rows number 3 Visible line count
wordWrap boolean true Wrap long lines
accessibleLabel string - Optional semantic label for focus announcements/debugging
disabled boolean false Disable editing and dim appearance
readOnly boolean false Keep the textarea focusable while preventing edits
focusable boolean true Opt out of Tab order while keeping id-based routing available
placeholder string - Placeholder text shown when the value is empty
style TextStyle - Custom styling (merged with focus/disabled state)
onInput (value: string, cursor: number) => void - Callback when value changes
onBlur () => void - Callback when textarea loses focus
focusConfig FocusConfig - Control focus visuals; { indicator: "none" } suppresses focused textarea decoration
key string - Reconciliation key for dynamic lists

Behavior

When focused:

  • Typing inserts text at the cursor
  • Enter inserts a newline
  • Left/Right move by grapheme cluster
  • Up/Down move between logical lines
  • Home/End move to start/end of the current line
  • Shift + movement extends selection
  • Ctrl+A selects all
  • Ctrl+C copies active selection to system clipboard (OSC 52)
  • Ctrl+X cuts active selection to system clipboard (OSC 52)
  • Ctrl+Z undoes the last edit
  • Ctrl+Shift+Z or Ctrl+Y redoes the last undone edit
  • Paste preserves line breaks (CRLF normalized to \n)

Textarea is controlled: value is always the source of truth.

When wordWrap: false, long lines stay unwrapped and the focused viewport shifts horizontally to keep the active cursor column visible instead of clamping the cursor to the left-most window.

Runtime note: ui.textarea(...) is represented as VNode kind "input" with multiline: true. Use this mapping when writing low-level kind assertions.

  • Input - Single-line controlled input
  • Field - Label/error wrapper for form controls