Textarea

Textarea edits multiple lines with cursor movement, selection, and configurable key bindings. Use Input for a single line.

Availability#

Field Availability
Package @opentui/core
Core renderable TextareaRenderable
React <textarea> (automatic)
Solid <textarea> (automatic)
Status Built in

Basic usage#

Renderable API#

import { TextareaRenderable, createCliRenderer } from "@opentui/core"

const renderer = await createCliRenderer()

const textarea = new TextareaRenderable(renderer, {
  id: "notes",
  width: 50,
  height: 6,
  placeholder: "Type notes here...",
  backgroundColor: "#1a1a1a",
  focusedBackgroundColor: "#222222",
  textColor: "#FFFFFF",
  cursorColor: "#00FF88",
})

renderer.root.add(textarea)
textarea.focus()

At 30 columns, longer text wraps at word boundaries:

textarea.width = 30
textarea.setText("Long lines wrap at word boundaries.\nKeep paragraphs readable.")

Submit handling#

Bind a submit action and listen for onSubmit:

import { TextareaRenderable } from "@opentui/core"

const textarea = new TextareaRenderable(renderer, {
  width: 50,
  height: 6,
  onSubmit: () => {
    console.log("Submitted:", textarea.plainText)
  },
  keyBindings: [{ name: "return", ctrl: true, action: "submit" }],
})

Placeholder styling#

const textarea = new TextareaRenderable(renderer, {
  width: 40,
  height: 4,
  placeholder: "Type here",
  placeholderColor: "#666666",
})

Properties#

Property Type Default Description
width number or string - Width in terminal columns or percentage
height number or string - Height in rows or percentage
initialValue string "" Initial text content
placeholder string, StyledText, or null null Placeholder content
placeholderColor string or RGBA #666666 Placeholder color
backgroundColor string or RGBA transparent Background when unfocused
textColor string or RGBA #FFFFFF Text color when unfocused
focusedBackgroundColor string or RGBA initial base color Background when focused
focusedTextColor string or RGBA initial base color Text color when focused
wrapMode "none", "char", or "word" "word" Line wrapping mode
selectionBg string or RGBA - Selection background
selectionFg string or RGBA - Selection foreground
cursorColor string or RGBA #FFFFFF Cursor color
cursorStyle CursorStyleOptions - Cursor style and blinking
selectionOccupancy "cell" or "boundary" "cell" Which cells a selection occupies
keyBindings KeyBinding[] - Custom key bindings
keyAliasMap Record<string, string> - Key alias mapping
onSubmit (event: SubmitEvent) => void - Submit handler
onContentChange (event: ContentChangeEvent) => void - Fired on content changes
onCursorChange (event: CursorChangeEvent) => void - Fired on cursor movement

If you omit a focused color, the constructor copies the corresponding base color. If you omit both values, the focused background is transparent, and the focused text uses #FFFFFF.

Useful properties#

Property Type Description
plainText string Current text content
cursorOffset number Cursor offset in the buffer
cursorCharacterOffset number | undefined Best-effort UTF-16 index of the character under the cursor
logicalCursor { row, col } Logical line/column of the cursor
visualCursor VisualCursor Visual and logical cursor coordinates, plus the buffer offset
traits EditorTraits Editor traits published to hosting UI (see Traits)

cursorCharacterOffset uses a display-cell offset as a JavaScript string index. Its result is not reliable after wide graphemes, line breaks, or joined emoji. Use logicalCursor, visualCursor, and the editing-buffer APIs for Unicode-aware work.

Cursor and selection control#

TextareaRenderable and its base EditBufferRenderable expose a programmatic API. You can move the cursor, edit text, and drive selections from your own key bindings or commands. All selection-aware movement methods accept { select: true } to extend the current selection instead of moving the cursor.

Cursor movement#

textarea.setCursor(row, col)
textarea.moveCursorLeft()
textarea.moveCursorRight({ select: true })
textarea.moveCursorUp()
textarea.moveCursorDown()

textarea.moveWordForward({ select: true })
textarea.moveWordBackward()

textarea.gotoLine(0)
textarea.gotoLineStart()
textarea.gotoLineTextEnd()
textarea.gotoLineHome({ select: true }) // Emacs-style smart home
textarea.gotoLineEnd()
textarea.gotoVisualLineHome()
textarea.gotoVisualLineEnd()
textarea.gotoBufferHome()
textarea.gotoBufferEnd({ select: true })

Selection#

Textarea uses the repeated-click behavior from Text selection. After a double-click or triple-click, the cursor stays on the clicked grapheme. A later Shift+Arrow keeps the selected text and continues the selection by cells, not by words or lines.

textarea.setSelection(start, end) // half-open [start, end) in both occupancy modes
textarea.setSelectionInclusive(start, end) // also selects the grapheme at end in cell mode
textarea.selectAll()
textarea.clearSelection()
textarea.deleteSelection()

Selecting keyboard focus in a draft:

Editing#

textarea.insertChar("a")
textarea.insertText("\ninserted")
textarea.deleteChar() // forward delete
textarea.deleteCharBackward() // backspace
textarea.deleteWordForward()
textarea.deleteWordBackward()
textarea.deleteToLineEnd()
textarea.deleteToLineStart()
textarea.deleteLine()
textarea.newLine()
textarea.undo()
textarea.redo()

These methods update the editor and request a render as needed. Selection behavior depends on the method. Movement with { select: true } extends the selection. Call clearSelection() when a command must clear the global selection.

The default occupancy is cell: the selection covers both endpoint cells, so the first shift+right selects two cells. If you use a bar cursor (cursorStyle: { style: "line" }), also set selectionOccupancy: "boundary". The cursor style is visual only and never changes which text you select, copy, or delete.

Traits#

The traits property tells a host UI which built-in keys the editor wants to capture. It also supplies a visual-suspension hint and an optional status label. Assigning a different EditorTraits object emits the traits-changed event.

import { EditBufferRenderableEvents, type EditorTraits } from "@opentui/core"

textarea.traits = {
  capture: ["escape", "submit"], // consume these before host binds
  suspend: false,
  status: "Composing reply",
} satisfies EditorTraits

textarea.on(EditBufferRenderableEvents.TRAITS_CHANGED, (traits) => {
  updateFooter(traits.status ?? "")
})
Field Type Description
capture EditorCapture[] Keys the editor wants to capture: "escape", "navigate", "submit", "tab"
suspend boolean Hint to the host to suspend ambient UI (dim borders, hide hints, etc.)
status string Optional short label surfacing editor mode in a status bar

Traits reset to an empty object when you destroy the renderable. Use isEditBufferRenderable(renderable) if you need to distinguish editor renderables from plain text renderables in a generic tree.

Read Interaction, focus, and selection for focus and selection ownership. Read Text and terminal cells for the difference between buffer offsets, graphemes, and display cells.