These docs describe the main branch, including unreleased changes. Read the 0.6.0 docs.

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#

By default, Meta+Return and Meta+keypad Enter submit. To use another key, bind the 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. Use it to move the cursor, edit text, and control selections from your own key bindings or commands. Methods that accept { select: true } move the cursor and extend the selection to the new cursor position. Without that option, they clear the selection. If a selection exists, each of these methods except moveCursorUp() and moveCursorDown() then only moves the cursor to the selection start or end.

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 }) // At column 0, moves to the end of the previous line
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. If a selection exists, insertChar() and insertText() replace the selected text. deleteChar(), deleteCharBackward(), deleteWordForward(), and deleteWordBackward() delete it. Call clearSelection() when a command must clear the renderer’s 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 traits with a different capture, suspend, or status value emits the traits-changed event. The listener receives the new and previous traits. A new object with the same values emits nothing.

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, for example to dim borders or hide hints. While true, Textarea skips its key bindings
status string Optional short label that shows the 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.

Changes0.6.0, 0.5.7
0.6.0
native: Up and Down move the cursor to the start of a wide character or tab, not inside it. This applies to EditBuffer.moveCursorUp() and moveCursorDown(), EditorView.moveUpVisual() and moveDownVisual(), the Textarea arrow keys, and EditorView.setViewport() with moveCursor. See Textarea. (#1596)
native: Down does nothing in an empty Textarea while it shows a placeholder of more than one row. The next edit no longer fails with InvalidArgument. (#1619)
0.5.7
core: Selection occupancy is configurable with SelectionOccupancy ("cell" | "boundary"). "cell" includes both endpoint graphemes, as in Vim. "boundary" selects a half-open range between insertion points, as with a thin cursor. Set it with the selectionOccupancy option on EditBufferOptions and EditBufferRenderable. Read and write it with TextBufferView.getSelectionOccupancy(), TextBufferView.setSelectionOccupancy(), EditorView.getSelectionOccupancy() and EditorView.setSelectionOccupancy(). EditorView also adds convertSelectionToCell(), gotoVisualLineEnd(), setSelectionColors() and setSelectionInclusive(). See Textarea and Interaction, focus, and selection. (#1393)