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

Textarea

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

Availability

FieldAvailability
Package@opentui/core
Core renderableTextareaRenderable
React<textarea> (automatic)
Solid<textarea> (automatic)
StatusBuilt in

Basic usage

Renderable API

OpenTUI preview

Run this example to open an interactive terminal.

Source code · typescript
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.")
Notes
Long lines wrap at word
boundaries.
Keep 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

PropertyTypeDefaultDescription
widthnumber or string-Width in terminal columns or percentage
heightnumber or string-Height in rows or percentage
initialValuestring""Initial text content
placeholderstring, StyledText, or nullnullPlaceholder content
placeholderColorstring or RGBA#666666Placeholder color
backgroundColorstring or RGBAtransparentBackground when unfocused
textColorstring or RGBA#FFFFFFText color when unfocused
focusedBackgroundColorstring or RGBAinitial base colorBackground when focused
focusedTextColorstring or RGBAinitial base colorText color when focused
wrapMode"none", "char", or "word""word"Line wrapping mode
selectionBgstring or RGBA-Selection background
selectionFgstring or RGBA-Selection foreground
cursorColorstring or RGBA#FFFFFFCursor color
cursorStyleCursorStyleOptions-Cursor style and blinking
selectionOccupancy"cell" or "boundary""cell"Which cells a selection occupies
keyBindingsKeyBinding[]-Custom key bindings
keyAliasMapRecord<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

PropertyTypeDescription
plainTextstringCurrent text content
cursorOffsetnumberCursor offset in the buffer
cursorCharacterOffsetnumber | undefinedBest-effort UTF-16 index of the character under the cursor
logicalCursor{ row, col }Logical line/column of the cursor
visualCursorVisualCursorVisual and logical cursor coordinates, plus the buffer offset
traitsEditorTraitsEditor 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:

Draft
Plan the release
Review keyboard focus
Ship the update

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 ?? "")
})
FieldTypeDescription
captureEditorCapture[]Keys the editor wants to capture: "escape", "navigate", "submit", "tab"
suspendbooleanHint to the host to suspend ambient UI, for example to dim borders or hide hints. While true, Textarea skips its key bindings
statusstringOptional 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.7.0
0.7.0
core: With wrapMode: "none", a line that is scrolled right skips the text to the left of the viewport by chunk and cluster. An idle editor with one 256 KiB line no longer walks the line on each frame. (#1777)
core: Undo and redo in a Textarea scroll to the cursor in the restored text. Before, they used the layout from before the undo and could leave blank rows below the text. (#1751)
core: Undo and redo in a Textarea or Input emit content-changed, so onContentChange and line-info-change run. Before, an app that synced on content changes kept the text from before the undo, and the line-number gutter kept its old width. (#1790)