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
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
| 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:
Draft
Plan the release
Review keyboard focus
Ship the updateEditing
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.
Related concepts
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.