---
title: API Reference
url: "https://wterm.dev/api-reference"
docs_index: /llms.txt
lastUpdated: 2026-11-06
navTitle: "API Reference"
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

Complete reference for all wterm options, methods, types, and transport APIs.

## Cell Data

`TerminalCore.getCell()` and `getScrollbackCell()` return `CellData`. Alongside glyph, color, style, and width fields, a core may provide resolved OSC 8 metadata:

<table>
  <thead>
    <tr>
      <th>
        Field
      </th>

      <th>
        Type
      </th>

      <th>
        Description
      </th>
    </tr>
  </thead>

  <tbody>
    <tr>
      <td>
        <code>linkUri</code>
      </td>

      <td>
        <code>string?</code>
      </td>

      <td>
        The resolved URI attached to this cell
      </td>
    </tr>

    <tr>
      <td>
        <code>linkId</code>
      </td>

      <td>
        <code>string?</code>
      </td>

      <td>
        The explicit OSC 8

        <code>id=</code>

        parameter, when provided
      </td>
    </tr>

    <tr>
      <td>
        <code>linkKey</code>
      </td>

      <td>
        <code>string?</code>
      </td>

      <td>
        An opaque semantic identity used by renderers to group linked cells. Consumers must not interpret its contents.
      </td>
    </tr>
  </tbody>
</table>

These fields are optional so custom `TerminalCore` implementations remain source-compatible.

## Application Color Overrides

`TerminalCore.getColorOverrides?()` returns a caller-owned
`TerminalColorOverrides` snapshot with optional `foreground`, `background`,
and `cursor` fields, each a 24-bit `0xRRGGBB` value. Zero is black. Omit a field
to use the host's current CSS theme. These are application overrides, not
configured theme colors, and reading them does not consume them. A missing
method is equivalent to an empty snapshot.

Ghostty supplies OSC 10/11/12 overrides and clears each with OSC 110/111/112.
Its native color state survives resize, screen switches, SGR 0, and RIS;
reinitialization clears it. Older WASM binaries and uninitialized/disposed
Ghostty cores return an empty snapshot. The built-in core omits this method.

See [Application color changes](/themes#application-color-changes) for CSS
themes and rendering behavior.

## Row Metadata

`TerminalCore` has two optional methods for reading soft-wrap relationships.
The Ghostty core implements both; the lightweight core omits them.

<table>
  <thead>
    <tr>
      <th>Method</th>

      <th>Description</th>
    </tr>
  </thead>

  <tbody>
    <tr>
      <td>
        <code>getRowMetadata?(row)</code>
      </td>

      <td>
        Read a zero-based live row in the active screen.
      </td>
    </tr>

    <tr>
      <td>
        <code>getScrollbackRowMetadata?(offset)</code>
      </td>

      <td>
        Read retained history; offset zero is the newest row, directly above the live screen.
      </td>
    </tr>
  </tbody>
</table>

Each returns a caller-owned `TerminalRowMetadata` object or `null` when the
row or metadata is unavailable. Ghostty returns `null` for negative,
fractional, non-finite, or out-of-range indexes, before initialization, after
disposal, and when an older WASM binary lacks the exports.

<table>
  <thead>
    <tr>
      <th>Field</th>

      <th>Type</th>

      <th>Description</th>
    </tr>
  </thead>

  <tbody>
    <tr>
      <td>
        <code>wrapsToNext</code>
      </td>

      <td>
        <code>boolean</code>
      </td>

      <td>
        The following row continues this logical line without an explicit newline. Merely filling the last column does not set this flag.
      </td>
    </tr>

    <tr>
      <td>
        <code>continuesPrevious</code>
      </td>

      <td>
        <code>boolean</code>
      </td>

      <td>
        This row continues the preceding row, which may already have been discarded from history.
      </td>
    </tr>
  </tbody>
</table>

Metadata describes the current physical rows, not stable logical-line IDs.
Re-read after output, resize/reflow, history pruning, reset, or screen changes;
row indexes and scrollback offsets can change. Missing methods or `null`
mean unknown, not a hard newline. Reads do not consume dirty flags or require
a DOM render.

## Tracked Cell Positions

`TerminalCore.trackPosition?({ row, col })` returns a `TrackedTerminalPosition` or `null`. Coordinates are zero-based cells; row zero is the oldest retained row. Its `resolve()` method returns the current `{ row, col }` after scrolling or reflow, or `null` after pruning, reset, screen switching, reinitialization, or disposal. Call `dispose()` when finished; repeated disposal is safe. Invalidated handles never resolve to a new allocation.

Ghostty supports up to 64 simultaneous handles. Invalid coordinates, exhausted capacity, or older WASM binaries return `null`. The built-in core omits this optional method. Pins follow cell locations; callers must separately check for overwritten text.

## Terminal Graphics

Graphics methods are optional. Cores that support them return a snapshot for
the active primary or alternate screen and copied image data:

<table>
  <thead>
    <tr>
      <th>Type</th>

      <th>Key fields</th>

      <th>Meaning</th>
    </tr>
  </thead>

  <tbody>
    <tr>
      <td>
        <code>TerminalImageDescriptor</code>
      </td>

      <td>
        <code>imageId</code>

        ,

        <code>version</code>

        ,

        <code>width</code>

        ,

        <code>height</code>
      </td>

      <td>
        Intrinsic image metadata. A new version replaces the prior pixels.
      </td>
    </tr>

    <tr>
      <td>
        <code>TerminalImagePlacement</code>
      </td>

      <td>
        <code>row</code>

        ,

        <code>col</code>

        , offsets, source rectangle, cell size,

        <code>z</code>
      </td>

      <td>
        Pinned placement in the retained active screen. Row zero is the oldest retained row; coordinates are top-left based.
      </td>
    </tr>

    <tr>
      <td>
        <code>TerminalImageData</code>
      </td>

      <td>
        <code>rgba: Uint8Array</code>
      </td>

      <td>
        Copied row-major RGBA pixels. Invalid or unavailable data returns

        <code>null</code>

        .
      </td>
    </tr>
  </tbody>
</table>

`getGraphicsState()` and `getGraphicsImage(imageId, version)` are optional and
must not be assumed by custom renderers. Returned snapshots and byte arrays
are caller-owned copies. A state generation changes when image content,
placements, screen, scrollback, or size changes.

The built-in core consumes unsupported 7-bit Kitty APC strings safely but does
not provide graphics. The Ghostty implementation currently supports direct
Kitty PNG/RGB/RGBA with a 32 MiB decoded per-screen default; non-direct media,
Sixel, iTerm2, animation, virtual placements, and persistence are unsupported.
The DOM overlay also bounds each destination canvas to the terminal pixel area
and limits all visible canvas backing stores to 32 MiB; placements that exceed
those browser limits are skipped.

## Terminal Options

The React, Vue, and Svelte `<Terminal>` components and the vanilla `WTerm` constructor all accept these options:

<table>
  <thead>
    <tr>
      <th>
        Option
      </th>

      <th>
        Type
      </th>

      <th>
        Default
      </th>

      <th>
        Description
      </th>
    </tr>
  </thead>

  <tbody>
    <tr>
      <td>
        <code>cols</code>
      </td>

      <td>
        <code>number</code>
      </td>

      <td>
        <code>80</code>
      </td>

      <td>
        Initial column count. Vanilla auto-resizing measures omitted dimensions before initialization; 80 is the fallback.
      </td>
    </tr>

    <tr>
      <td>
        <code>rows</code>
      </td>

      <td>
        <code>number</code>
      </td>

      <td>
        <code>24</code>
      </td>

      <td>
        Initial row count. Vanilla auto-resizing measures omitted dimensions before initialization; 24 is the fallback.
      </td>
    </tr>

    <tr>
      <td>
        <code>core</code>
      </td>

      <td>
        <code>TerminalCore</code>
      </td>

      <td>
        —
      </td>

      <td>
        A pre-constructed terminal core instance. When provided,

        <code>wasmUrl</code>

        is ignored and this core is used instead of loading the built-in Zig WASM binary. See

        <a href="/ghostty">Ghostty Core</a>

        for an example.
      </td>
    </tr>

    <tr>
      <td>
        <code>wasmUrl</code>
      </td>

      <td>
        <code>string</code>
      </td>

      <td>
        —
      </td>

      <td>
        URL to serve the WASM binary separately. When omitted, the \~49 KB binary is decoded from an inlined base64 string. Ignored when

        <code>core</code>

        is provided.
      </td>
    </tr>

    <tr>
      <td>
        <code>autoResize</code>
      </td>

      <td>
        <code>boolean</code>
      </td>

      <td>
        <code>true</code>

        (vanilla) /

        <code>false</code>

        (React, Vue, Svelte)
      </td>

      <td>
        Automatically resize the terminal to fit its container using a

        <code>ResizeObserver</code>
      </td>
    </tr>

    <tr>
      <td>
        <code>maxImageWidth</code>
      </td>

      <td>
        <code>number</code>
      </td>

      <td>
        —
      </td>

      <td>
        Maximum rendered Kitty image width in CSS pixels. Images are scaled down proportionally and are never enlarged.
      </td>
    </tr>

    <tr>
      <td>
        <code>maxImageHeight</code>
      </td>

      <td>
        <code>number</code>
      </td>

      <td>
        —
      </td>

      <td>
        Maximum rendered Kitty image height in CSS pixels. Images are scaled down proportionally and are never enlarged.
      </td>
    </tr>

    <tr>
      <td>
        <code>cursorBlink</code>
      </td>

      <td>
        <code>boolean</code>
      </td>

      <td>
        Application-controlled
      </td>

      <td>
        Force blinking on (

        <code>true</code>

        ) or off (

        <code>false</code>

        ); omit to follow the terminal (initially steady).
      </td>
    </tr>

    <tr>
      <td>
        <code>announceOutput</code>
      </td>

      <td>
        <code>boolean</code>
      </td>

      <td>
        <code>false</code>
      </td>

      <td>
        Politely announce bounded text changes while terminal input has focus. Mutable in all framework bindings.
      </td>
    </tr>

    <tr>
      <td>
        <code>debug</code>
      </td>

      <td>
        <code>boolean</code>
      </td>

      <td>
        <code>false</code>
      </td>

      <td>
        Enable debug mode. Exposes a

        <code>DebugAdapter</code>

        on the

        <code>WTerm</code>

        instance (

        <code>wt.debug</code>

        ) for inspecting escape sequences, cell data, render performance, and unhandled CSI sequences.
      </td>
    </tr>

    <tr>
      <td>
        <code>scrollbackLines</code>
      </td>

      <td>
        <code>number</code>
      </td>

      <td>
        core default
      </td>

      <td>
        Lines of history the core keeps; older lines are dropped. The built-in and Ghostty cores keep 1,000 by default. Mutable in all framework bindings.
      </td>
    </tr>

    <tr>
      <td>
        <code>renderingPaused</code>
      </td>

      <td>
        <code>boolean</code>
      </td>

      <td>
        <code>false</code>
      </td>

      <td>
        Suspend painting for an inactive pane. Parsing, replies, titles, bells, and core resizing continue. Mutable in all framework bindings; see

        <a href="/configuration#inactive-panes">Inactive panes</a>

        .
      </td>
    </tr>

    <tr>
      <td>
        <code>renderer</code>
      </td>

      <td>
        <code>"dom" | "webgl"</code>
      </td>

      <td>
        <code>"dom"</code>
      </td>

      <td>
        Paint live output with WebGL2. DOM text stays current for selection, search, and accessibility. Falls back to

        <code>"dom"</code>

        without WebGL2. Fixed at mount; see

        <a href="/configuration#webgl-renderer">WebGL renderer</a>

        .
      </td>

      <td>
        <code>webmcp</code>
      </td>

      <td>
        <code />
      </td>

      <td>
        <code>false</code>
      </td>

      <td>
        Let in-page agents list, read, and type into the terminal through WebMCP. Mutable in all framework bindings; see

        <a href="/webmcp">WebMCP</a>

        .
      </td>
    </tr>

    <tr>
      <td>
        <code>onData</code>
      </td>

      <td>
        <code>(data: string) => void</code>
      </td>

      <td>
        —
      </td>

      <td>
        Called when the terminal produces data (user input or host response). When omitted, input is echoed back automatically.
      </td>
    </tr>

    <tr>
      <td>
        <code>onBinary</code>
      </td>

      <td>
        <code>(data: Uint8Array) => void</code>
      </td>

      <td>
        —
      </td>

      <td>
        Called with raw X10 mouse reports when supplied. Forward the bytes unchanged to a binary-capable transport; without it, only ASCII-safe X10 reports reach

        <code>onData</code>

        .
      </td>
    </tr>

    <tr>
      <td>
        <code>onTitle</code>
      </td>

      <td>
        <code>(title: string) => void</code>
      </td>

      <td>
        —
      </td>

      <td>
        Called when the terminal title changes via an escape sequence
      </td>
    </tr>

    <tr>
      <td>
        <code>onBell</code>
      </td>

      <td>
        <code>(count: number) => void</code>
      </td>

      <td>
        —
      </td>

      <td>
        Called as output is written with the number of pending BEL controls. The host decides whether to play sound or show a visual alert.
      </td>
    </tr>

    <tr>
      <td>
        <code>onClipboardWrite</code>
      </td>

      <td>
        <code>(text: string) => void</code>
      </td>

      <td>
        —
      </td>

      <td>
        Application clipboard-write request from Ghostty. No clipboard access is automatic; see

        <a href="/configuration#clipboard-requests">Clipboard requests</a>

        .
      </td>
    </tr>

    <tr>
      <td>
        <code>onShellIntegration</code>
      </td>

      <td>
        <code>(state: ShellIntegrationState) => void</code>
      </td>

      <td>
        —
      </td>

      <td>
        Latest shell-reported prompt, input, running, or completion state. Ghostty with OSC 133; see

        <a href="/configuration#shell-integration">Shell integration</a>

        .
      </td>
    </tr>

    <tr>
      <td>
        <code>onWorkingDirectory</code>
      </td>

      <td>
        <code>(uri: string) => void</code>
      </td>

      <td>
        —
      </td>

      <td>
        Raw shell-reported OSC 7 URI from Ghostty; empty clears it. Treat as untrusted metadata. See

        <a href="/configuration#working-directories">Working directories</a>

        .
      </td>
    </tr>

    <tr>
      <td>
        <code>onResize</code>
      </td>

      <td>
        <code>(cols: number, rows: number) => void</code>
      </td>

      <td>
        —
      </td>

      <td>
        Called with the grid dimensions applied by the core after resize or when initialization changes the requested size. Use these values when resizing a connected PTY.
      </td>
    </tr>
  </tbody>
</table>

## React-Only Props

The React `<Terminal>` component adds these props on top of the shared options above. It also spreads standard HTML attributes (`className`, `style`, `id`, etc.) onto the root `div`.

<table>
  <thead>
    <tr>
      <th>
        Prop
      </th>

      <th>
        Type
      </th>

      <th>
        Description
      </th>
    </tr>
  </thead>

  <tbody>
    <tr>
      <td>
        <code>theme</code>
      </td>

      <td>
        <code>string</code>
      </td>

      <td>
        Name of a built-in or custom theme (see

        <a href="/themes">Themes</a>

        )
      </td>
    </tr>

    <tr>
      <td>
        <code>onReady</code>
      </td>

      <td>
        <code>(wt: WTerm) => void</code>
      </td>

      <td>
        Called with the underlying

        <code>WTerm</code>

        instance after WASM loads and initialization completes
      </td>
    </tr>

    <tr>
      <td>
        <code>onError</code>
      </td>

      <td>
        <code>(error: unknown) => void</code>
      </td>

      <td>
        Called if WASM loading or initialization fails. When omitted, errors are logged to the console.
      </td>
    </tr>
  </tbody>
</table>

## Vue-Only Props

The Vue `<Terminal>` component adds these props on top of the shared options above. Vue exposes input callbacks as events (see [Vue Events](#vue-events) below) instead of `onXxx` props. Standard HTML attributes (`class`, `style`, `id`, etc.) are forwarded to the root `div` via the default `inheritAttrs` behavior.

<table>
  <thead>
    <tr>
      <th>
        Prop
      </th>

      <th>
        Type
      </th>

      <th>
        Description
      </th>
    </tr>
  </thead>

  <tbody>
    <tr>
      <td>
        <code>theme</code>
      </td>

      <td>
        <code>string</code>
      </td>

      <td>
        Name of a built-in or custom theme (see

        <a href="/themes">Themes</a>

        ). Applied as a

        <code />

        class on the root element.
      </td>
    </tr>
  </tbody>
</table>

## Vue Events

<table>
  <thead>
    <tr>
      <th>
        Event
      </th>

      <th>
        Payload
      </th>

      <th>
        Description
      </th>
    </tr>
  </thead>

  <tbody>
    <tr>
      <td>
        <code>data</code>
      </td>

      <td>
        <code>(data: string)</code>
      </td>

      <td>
        Emitted when the terminal produces data (user input or host response). When no listener is attached, input is echoed back automatically.
      </td>
    </tr>

    <tr>
      <td>
        <code>binary</code>
      </td>

      <td>
        <code>(data: Uint8Array)</code>
      </td>

      <td>
        Emitted with raw X10 mouse reports for a binary-capable transport.
      </td>
    </tr>

    <tr>
      <td>
        <code>title</code>
      </td>

      <td>
        <code>(title: string)</code>
      </td>

      <td>
        Emitted when the terminal title changes via an escape sequence.
      </td>
    </tr>

    <tr>
      <td>
        <code>bell</code>
      </td>

      <td>
        <code>(count: number)</code>
      </td>

      <td>
        Emitted as output is written with the number of pending BEL controls.
      </td>
    </tr>

    <tr>
      <td>
        <code>clipboardWrite</code>
      </td>

      <td>
        <code>(text: string)</code>
      </td>

      <td>
        Application clipboard-write request from Ghostty. No clipboard access is automatic; see

        <a href="/configuration#clipboard-requests">Clipboard requests</a>

        .
      </td>
    </tr>

    <tr>
      <td>
        <code>shellIntegration</code>
      </td>

      <td>
        <code>(state: ShellIntegrationState)</code>
      </td>

      <td>
        Latest shell state from Ghostty. Use

        <code>@shell-integration</code>

        ; see

        <a href="/configuration#shell-integration">Shell integration</a>

        .
      </td>
    </tr>

    <tr>
      <td>
        <code>workingDirectory</code>
      </td>

      <td>
        <code>(uri: string)</code>
      </td>

      <td>
        Raw OSC 7 URI from Ghostty; empty clears it. Use

        <code>@working-directory</code>

        ; see

        <a href="/configuration#working-directories">Working directories</a>

        .
      </td>
    </tr>

    <tr>
      <td>
        <code>resize</code>
      </td>

      <td>
        <code>(cols: number, rows: number)</code>
      </td>

      <td>
        Emitted after the terminal is resized.
      </td>
    </tr>

    <tr>
      <td>
        <code>ready</code>
      </td>

      <td>
        <code>(wt: WTerm)</code>
      </td>

      <td>
        Emitted once after

        <code>WTerm.init()</code>

        resolves, carrying the underlying

        <code>WTerm</code>

        instance.
      </td>
    </tr>

    <tr>
      <td>
        <code>error</code>
      </td>

      <td>
        <code>(err: unknown)</code>
      </td>

      <td>
        Emitted if WASM loading or initialization fails.
      </td>
    </tr>
  </tbody>
</table>

## Svelte-only Props

The Svelte `<Terminal>` component adds `theme` and forwards standard HTML
attributes such as `class`, `style`, `id`, and ARIA attributes to its root
`div`. Svelte 5 callback props are listed below.

<table>
  <thead>
    <tr>
      <th>
        Prop
      </th>

      <th>
        Type
      </th>

      <th>
        Description
      </th>
    </tr>
  </thead>

  <tbody>
    <tr>
      <td>
        <code>theme</code>
      </td>

      <td>
        <code>string</code>
      </td>

      <td>
        Name of a built-in or custom theme (see

        <a href="/themes">Themes</a>

        ). Applied as a

        <code />

        class.
      </td>
    </tr>

    <tr>
      <td>
        <code>onData</code>
      </td>

      <td>
        <code>(data: string) => void</code>
      </td>

      <td>
        Called for terminal input and host responses. When omitted, input is echoed automatically.
      </td>
    </tr>

    <tr>
      <td>
        <code>onBinary</code>
      </td>

      <td>
        <code>(data: Uint8Array) => void</code>
      </td>

      <td>
        Called with raw X10 mouse reports for a binary-capable transport.
      </td>
    </tr>

    <tr>
      <td>
        <code>onTitle</code>
      </td>

      <td>
        <code>(title: string) => void</code>
      </td>

      <td>
        Called when the terminal title changes.
      </td>
    </tr>

    <tr>
      <td>
        <code>onBell</code>
      </td>

      <td>
        <code>(count: number) => void</code>
      </td>

      <td>
        Called with the pending BEL count as output is written.
      </td>
    </tr>

    <tr>
      <td>
        <code>onClipboardWrite</code>
      </td>

      <td>
        <code>(text: string) => void</code>
      </td>

      <td>
        Application clipboard-write request from Ghostty. No clipboard access is automatic; see

        <a href="/configuration#clipboard-requests">Clipboard requests</a>

        .
      </td>
    </tr>

    <tr>
      <td>
        <code>onShellIntegration</code>
      </td>

      <td>
        <code>(state: ShellIntegrationState) => void</code>
      </td>

      <td>
        Latest shell-reported prompt, input, running, or completion state. Ghostty with OSC 133; see

        <a href="/configuration#shell-integration">Shell integration</a>

        .
      </td>
    </tr>

    <tr>
      <td>
        <code>onWorkingDirectory</code>
      </td>

      <td>
        <code>(uri: string) => void</code>
      </td>

      <td>
        Raw OSC 7 URI from Ghostty; empty clears it. Also accepts

        <code>onworkingdirectory</code>

        ; see

        <a href="/configuration#working-directories">Working directories</a>

        .
      </td>
    </tr>

    <tr>
      <td>
        <code>onResize</code>
      </td>

      <td>
        <code>(cols: number, rows: number) => void</code>
      </td>

      <td>
        Called after the terminal is resized.
      </td>
    </tr>

    <tr>
      <td>
        <code>onReady</code>
      </td>

      <td>
        <code>(wt: WTerm) => void</code>
      </td>

      <td>
        Called after WASM loads and initialization completes.
      </td>
    </tr>

    <tr>
      <td>
        <code>onError</code>
      </td>

      <td>
        <code>(error: unknown) => void</code>
      </td>

      <td>
        Called if WASM loading or initialization fails.
      </td>
    </tr>
  </tbody>
</table>

## Input Accessibility

The actual terminal input is a native multiline textbox named **Terminal** by default. Set `aria-label`, `aria-labelledby`, `aria-describedby`, or `aria-description` on the host element, or pass them to the React, Vue, or Svelte component. WTerm keeps those attributes synchronized with the input, including removals. Referenced labels and descriptions follow normal browser ARIA precedence.

```html
<h2 id="shell-heading">Build shell</h2>
<p id="shell-help">Commands run in the selected session.</p>
<div id="terminal" aria-labelledby="shell-heading" aria-describedby="shell-help"></div>
```

The host defaults to `role="group"` after initialization; framework wrappers also render a group before initialization. The textarea is the editable control, and mounted output remains readable separately. If you previously set `role="textbox"` and `aria-multiline` on the host yourself, remove them or use `role="group"`. Other explicit host roles remain under your control.

Host `tabindex` (React `tabIndex`) controls the input's tab stop: `0` for normal page entry, `-1` to exclude it. When supplied, WTerm normalizes the host itself to `-1` to avoid a duplicate stop, then restores the latest requested value on destruction. Omitting `tabindex` gives input a tab index of `0`. Use `wt.focus()` to focus input programmatically. Label and tab-order changes do not move focus. Tab and Shift+Tab inside input continue to reach the terminal application for completion/navigation.

**Keyboard exit:** Press **Escape**, then **Tab** to move focus to the next page control, or **Escape**, then **Shift+Tab** to move backward. There is no timeout between the keys. Any other key except Shift, a pointer press, paste, text input, composition, or loss of focus cancels the sequence. Escape still reaches the application unless it clears Select All. Ordinary Tab and Shift+Tab keep their terminal behavior. The browser determines the next focus target; WTerm does not send the exit Tab or a release from a key pressed outside the terminal to the application.

The input's accessible description includes the exit instructions after any host-provided description. Host `aria-describedby` references take precedence over `aria-description`.

The live terminal exposes input semantics and mounted output. Use `readText()` to present retained history in an accessible reader; live output announcements are off by default. Ancestor `aria-hidden` and `inert` remain authoritative for hidden terminals.

## Output announcements

Set `announceOutput: true` in vanilla JavaScript, or pass the `announceOutput`
prop in React, Vue, or Svelte, to opt into polite screen-reader announcements.
It defaults to `false`. Change it without restarting the terminal using
`term.setOutputAnnouncements(enabled)` or the reactive component prop. The
local workspace provides an **Announce output** checkbox for each session.

Only the focused terminal in the active browser document announces output.
Leaving terminal input, opening the output reader, hiding the document, or
turning the option off clears pending announcements. Returning starts from
current text without replaying output received in the background.

Changes are sampled after painting at most once every 500 ms. Announcements
contain whole changed physical rows, with Unicode cells intact and trailing
padding removed; they can include shell echo or redrawn text. Recently scrolled
rows are included within the capture bounds. Unchanged text, styling changes,
and erased blank rows stay silent. Resizing and switching screens establish a
new baseline. Intermediate redraws can be coalesced, so this is a summary of
screen changes, not a lossless transcript. Use `readText()` to inspect retained
output explicitly.

Each burst allows at most 20 changed rows or 4,000 UTF-16 code units. When the
limit is exceeded, one notice replaces the text and announcements pause until
new terminal input, focus reentry, or toggling the option off and on. The DOM
retains only the latest batch. Captures are also bounded to 32,768 cells and
65,536 UTF-16 code units; oversized captures or output pruned before it can be
read produce the same pause notice without announcing partial text. Custom
cores should expose `getScrollbackDiscardedCount()` to avoid rereading shifted
rows when their history limit is reached. Route writes and resizes through
WTerm so announcements follow completed paints.

## Reading Terminal Output

`readText()` returns a `Promise<string>` containing a plain-text snapshot of all retained history and the active screen, including unmounted rows. It preserves complete Unicode cells, joins confirmed soft wraps, keeps hard line breaks and blank rows, and trims hard-line padding. Cores without wrap metadata keep physical row breaks.

```ts
const controller = new AbortController();
const text = await term.readText({ signal: controller.signal });
// Display text in a labelled, read-only textarea or another accessible reader.
// controller.abort() cancels a pending capture.
```

Capture runs in small batches after the current frame paints. It does not focus the terminal, change selection, write to the clipboard, or mount extra history. A completed string stays unchanged when output arrives, history is pruned, or the terminal is resized or destroyed.

A write, resize, destruction, or newer `readText()` request rejects a pending capture with `AbortError`; an aborted signal rejects with its reason. Retry when output settles. Uninitialized terminals reject with an error. Capture is capped at 16,777,216 UTF-16 code units; larger output rejects with `RangeError` and never returns a partial result. Keep any previous snapshot until a replacement succeeds. Route core writes and resizes through WTerm so pending captures are invalidated correctly.

Framework users call `readText()` on their WTerm instance. The local workspace's **Read output** button opens a native read-only text area for keyboard navigation and copying, with **Refresh** and **Close** controls. Closing releases the snapshot and cancels pending work. This is an explicitly requested snapshot; it does not announce live output automatically.

## WTerm Methods

Instance methods on the vanilla `WTerm` class:

After `init()` or `resize()`, `wt.cols` and `wt.rows` report the applied grid
dimensions. A core may cap the requested size; the built-in core currently
supports up to 1024 columns and 512 rows. Use the applied dimensions for a
connected PTY so its output wraps at the same columns as the browser terminal.

WTerm renders synchronized output blocks (CSI `?2026`) atomically when the mode closes. Each block can hold rendering for at most one second from its opening sequence. Ordinary payload does not extend that deadline. If the deadline expires, WTerm resumes painting until a fresh block begins.

Outside synchronized output, writes request the next animation frame directly. Writes received before that frame share one render.

<table>
  <thead>
    <tr>
      <th>
        Method
      </th>

      <th>
        Description
      </th>
    </tr>
  </thead>

  <tbody>
    <tr>
      <td>
        <code />
      </td>

      <td>
        Load WASM and start rendering
      </td>
    </tr>

    <tr>
      <td>
        <code>setThemeColors(colors: TerminalThemeColors | null): void</code>
      </td>

      <td>
        Update host CSS and supported core defaults without parsing input;

        <code>null</code>

        returns to the CSS theme. See

        <a href="/themes#updating-host-colors">Updating host colors</a>
      </td>
    </tr>

    <tr>
      <td>
        <code>write(data: string | Uint8Array)</code>
      </td>

      <td>
        Write data to the terminal
      </td>
    </tr>

    <tr>
      <td>
        <code>resize(cols, rows)</code>
      </td>

      <td>
        Request a grid size; read

        <code>wt.cols</code>

        and

        <code>wt.rows</code>

        afterward for the applied size
      </td>
    </tr>

    <tr>
      <td>
        <code>focus()</code>
      </td>

      <td>
        Focus the terminal input
      </td>
    </tr>

    <tr>
      <td>
        <code>setOutputAnnouncements(enabled)</code>
      </td>

      <td>
        Enable or stop polite output announcements without changing focus
      </td>
    </tr>

    <tr>
      <td>
        <code>setScrollbackLines(lines)</code>
      </td>

      <td>
        Change how many lines of history the core keeps, dropping the oldest. Throws when the core cannot change it.
      </td>
    </tr>

    <tr>
      <td>
        <code>setRenderingPaused(paused)</code>
      </td>

      <td>
        Pause pane painting or schedule the latest state when resuming. Browser document visibility also gates painting.
      </td>
    </tr>

    <tr>
      <td>
        <code>setWebMCP(options)</code>
      </td>

      <td>
        Expose the terminal to in-page agents with

        <code>true</code>

        or

        <a href="/webmcp">WebMCP options</a>

        , or withdraw it with

        <code>false</code>

        . Unchanged options are a no-op.
      </td>
    </tr>

    <tr>
      <td>
        <code>destroy()</code>
      </td>

      <td>
        Clean up event listeners, observers, and DOM
      </td>
    </tr>

    <tr>
      <td>
        <code>search(query, options?)</code>
      </td>

      <td>
        Start cancellable plain-text search across retained history and the active screen
      </td>
    </tr>

    <tr>
      <td>
        <code>findNext()</code>

        /

        <code>findPrevious()</code>
      </td>

      <td>
        Reveal a match with wraparound; return false if there are no matches
      </td>
    </tr>

    <tr>
      <td>
        <code>fit(): void</code>
      </td>

      <td>
        Resize the grid to the current element content box and font metrics. Hidden or uninitialized terminals stay unchanged. Uses the usual resize callback.
      </td>
    </tr>

    <tr>
      <td>
        <code>scrollToPrompt(direction: -1 | 1): boolean</code>
      </td>

      <td>
        Scroll from the viewport top to the previous or next shell prompt without wrapping. Returns whether scrolling moved. Requires Ghostty with OSC 133; see

        <a href="/configuration#shell-integration">Shell integration</a>

        for limits and inactive states.
      </td>
    </tr>

    <tr>
      <td>
        <code>getSearchState()</code>

        /

        <code>clearSearch()</code>
      </td>

      <td>
        Read a state snapshot, or cancel search and remove highlights
      </td>
    </tr>

    <tr>
      <td>
        <code>getSelectionText(): string | null</code>
      </td>

      <td>
        Read selected text with terminal line and cell semantics, or null when unavailable
      </td>
    </tr>

    <tr>
      <td>
        <code />
      </td>

      <td>
        Capture retained history and the active screen without changing selection. Accepts an optional

        <code>signal</code>

        ; see

        <a href="#reading-terminal-output">Reading Terminal Output</a>

        for cancellation and limits.
      </td>
    </tr>

    <tr>
      <td>
        <code />
      </td>

      <td>
        Select retained history and the active screen; resolve true when the complete text is ready
      </td>
    </tr>

    <tr>
      <td>
        <code>clearSelection(): void</code>
      </td>

      <td>
        Clear custom selection and a native selection wholly inside this terminal
      </td>
    </tr>

    <tr>
      <td>
        <code>selectWord(position): boolean</code>
      </td>

      <td>
        Select the word at a retained-buffer cell, including confirmed soft wraps
      </td>
    </tr>

    <tr>
      <td>
        <code>selectLine(row): boolean</code>
      </td>

      <td>
        Select the complete logical line containing a retained-buffer row
      </td>
    </tr>

    <tr>
      <td>
        <code>selectRectangle(start, end): boolean</code>
      </td>

      <td>
        Select inclusive column and row bounds, preserving spaces and physical line breaks
      </td>
    </tr>
  </tbody>
</table>

## Selection and Copy

WTerm handles the browser's normal Copy action for a native selection wholly inside contiguous mounted terminal rows. It writes plain text: Ghostty soft wraps join without a newline, explicit line breaks remain `\n`, and cores with unknown wrap boundaries retain physical row breaks. This includes the built-in core, which does not currently expose wrap metadata.

`getSelectionText()` returns the same text without accessing the clipboard. Framework users call it through the WTerm instance received from `onReady` (React/Svelte), `ready` (Vue), or the instance ref.

```ts
const text = wt.getSelectionText();
if (text !== null) console.log(text);
```

Partial grapheme and surrogate-pair selections expand to whole terminal cells. Wide continuations and flagged right-edge spacer heads add no text. Block and box-drawing glyphs remain text; hyperlinks copy their displayed characters without markup or link destinations. Trailing ASCII spaces are trimmed only when selection reaches the right edge of a hard-ended row. Interior spaces, spaces across soft wraps, and explicitly selected spaces at a partial row end are preserved.

Extraction uses the painted snapshot, so output awaiting a frame or synchronized-output release cannot replace the text being copied. The result is `null` for collapsed, unavailable, or multiple-range selections, selections outside the terminal, and selections spanning unmounted history gaps. Browser copying remains unchanged in those cases. Copy also respects input-field selections and events already handled by the host. An all-padding selection can produce an empty string.

With Ghostty's current WASM binary, WTerm preserves forward and backward selections through output scrolling and resize/reflow. It restores the native highlight after painting and clears the selection when selected text changes, an edge is discarded, or the application resets or switches screens. Selected history rows stay mounted separately from the viewport, leaving intervening gaps virtualized. Resize reports applied dimensions immediately and updates the DOM on the next paint frame.

Native selection preservation is limited to 1,000 physical rows and 1,048,576 UTF-16 units; reflow beyond the row limit clears a tracked selection. Larger selections, cores without `trackPosition`, and new selections made while the painted frame lags the core keep native browser behavior and may change or clear when rendering catches up. Native selection begins in mounted text.

### Words and Logical Lines

Double-click to select a word or path; triple-click to select the complete logical line. Confirmed soft wraps can include history outside the mounted window. Single-click dragging retains native browser selection. Hold Shift to select live text while an application reports mouse input; history remains selectable without Shift. Modified link clicks keep their normal behavior.

```ts
wt.selectWord({ row: 10, col: 4 });
wt.selectLine(10);
console.log(wt.getSelectionText());
```

Coordinates start at the oldest retained row; columns are terminal cells. Both methods return `true` when a native selection is created, or `false` for invalid coordinates, an unavailable or pending frame, or ranges beyond 1,000 physical rows or 1,048,576 UTF-16 units. Gestures fall back to browser selection in those cases. Route writes and resizes through WTerm and select after painting. Successful selection replaces Select All and releases terminal input focus for normal Copy. Framework users call these methods through the underlying WTerm instance.

Word boundaries match Ghostty's defaults: spaces, tabs, quotes, backticks, vertical bars (including `│`), colons, semicolons, commas, parentheses, square/curly/angle brackets, and dollar signs. Adjacent boundary characters form a separate run. Slashes, dots, hyphens, underscores, and Unicode text stay together. Either half of a wide glyph selects the whole grapheme. Line selection preserves indentation, trims trailing hard-line padding on copy, and excludes the next explicit newline. Hard and unknown row breaks stop expansion, so the built-in core selects within a physical row. These native selections follow the preservation behavior above.

### Rectangular Selection

Hold Alt (Option on macOS) and drag to copy columns from tables or logs. Hold Shift+Alt in the live screen of a mouse-reporting application. Drag beyond the top or bottom edge to scroll through history.

```ts
wt.selectRectangle({ row: 10, col: 4 }, { row: 14, col: 12 });
const text = wt.getSelectionText();
wt.clearSelection();
```

Corners are inclusive and may be supplied in either direction. Row zero is the oldest retained physical row; columns are terminal cells. Each selected row becomes a separate line, even across soft wraps. Selected spaces, including trailing padding, remain intact. Wide glyphs intersecting either edge expand to the complete grapheme, and highlights follow that expansion.

`selectRectangle(start, end)` focuses terminal input and returns `true` when the complete snapshot is ready. Copy with Cmd+C, Ctrl+C, or Ctrl+Shift+C; Escape clears it. The method returns `false` for invalid coordinates, an unavailable or pending frame, or an oversized rectangle. A rejected request leaves the previous selection intact unless focus handling changes the terminal. Bounds are 1,000 rows, 1,048,576 UTF-16 units, and `rowCount * (selectedWidth + 1) <= 65,536`. No truncated prefix is exposed.

The snapshot includes unmounted history without mounting extra rows. Only mounted rows are highlighted, using `--term-selection-bg`. Scrolling preserves it; any WTerm write or resize, new input, pointer selection, focus outside the terminal, replacement selection, or destruction clears it. Route core mutations through WTerm and select after painting. Framework users access this method through their WTerm instance.

### Select All

With terminal input focused, **Cmd+A** or **Ctrl+Shift+A** selects all retained history and the active screen. **Ctrl+A** still reaches the shell. Copy with **Cmd+C**, **Ctrl+C**, or **Ctrl+Shift+C**. These shortcuts also work with Kitty keyboard mode. Escape clears Select All without sending Escape to the application.

```ts
if (await wt.selectAll()) {
  console.log(wt.getSelectionText());
}
wt.clearSelection();
```

Select All captures text in cancellable batches after the current frame paints. The promise resolves `true` when ready, or `false` if unavailable, cancelled, or too large. It preserves the same line/cell semantics as native Copy, including blank screen rows. Discarded history and the inactive screen are excluded. Virtual highlights keep the DOM window bounded; they do not create a browser DOM selection. Customize their color with `--term-selection-bg`.

The snapshot is limited to 16,777,216 UTF-16 units; no truncated prefix is exposed or copied. While capture runs, `getSelectionText()` returns `null` and Copy is withheld. Wait until “Selecting terminal text…” disappears before copying. A visible status reports cancellation during capture or failure. Scrolling preserves the selection. Any WTerm `write` or `resize`, new input, pointer selection, focus outside the terminal, replacement selection, or destruction clears it. Route core mutations through WTerm. `clearSelection()` also clears a native selection wholly inside this terminal, leaving selections elsewhere untouched. Framework users access both methods through the underlying WTerm instance.

## Terminal Search

```ts
import type { SearchOptions, SearchState } from "@wterm/dom";

wt.onSearchChange = (state: SearchState) => {
  // Update your Find controls from this snapshot.
  console.log(state.count, state.activeIndex, state.searching, state.limited);
};
const options: SearchOptions = { caseSensitive: false };
wt.search("connection refused", options);
```

`onSearchChange` can also be passed in the WTerm constructor options. Framework users access it and the search methods through their underlying WTerm instance (`onReady` in React/Svelte, `ready` in Vue, or the instance ref).

`SearchState` contains `query`, `caseSensitive`, `count`, `activeIndex`, `searching`, and `limited`. Results arrive incrementally in chronological order. The first match is selected and revealed; `activeIndex` is zero-based or -1 when empty. Next/previous navigation wraps among matches found so far. Empty queries cancel work and remove highlights. Native text selection is preserved; the host owns Find controls and shortcuts.

Search includes unmounted retained history. It scans in cancellable slices with a soft 4 ms budget, yielding between browser tasks so input and rendering can continue. Long searches avoid nested-timer delays where message tasks are available, with a timer fallback. Single-character cells that cannot start or continue a match skip coordinate bookkeeping, reducing work for sparse or absent matches. Ghostty joins confirmed soft wraps, including the history/screen boundary, while explicit newlines and unknown boundaries separate matches. The built-in core currently searches each physical row independently. Grapheme strings preserve Unicode content; matches highlight whole cells even when only part of a grapheme matches. Width-zero continuations and flagged `spacerHead` cells are omitted. Real spaces and blank-cell padding remain literal. Queries do not cross hard newlines and regular expressions are not interpreted.

Matching ignores case by default using locale-independent JavaScript `toLowerCase()` on each code point. There is no Unicode normalization or full case folding: composed and decomposed accents differ, `ß` does not match `ss`, and final sigma differs from sigma. Lowercase expansions keep the original cell coordinates.

Queries longer than 1,024 UTF-16 code units throw `RangeError` without changing the current search. At most 10,000 matches are retained; `limited` indicates additional matches exist. Narrow the query to reach them. Output, pruning, resize, and screen switches immediately discard stale results and restart after painting. Refresh selects the first new result without moving the scroll position. Continuous output can delay completion. Synchronized output waits for paint release; destruction cancels pending work. Use WTerm's `write` and `resize` methods for buffer mutations so results stay current.

Highlight colors use `--term-search-match`, `--term-search-active`, and `--term-search-border` on the terminal element.

## Imperative Handle (React)

The `useTerminal` hook returns a ref and convenience methods that delegate to the underlying `WTerm` instance:

```tsx
const { ref, write, resize, focus } = useTerminal();
```

<table>
  <thead>
    <tr>
      <th>
        Return
      </th>

      <th>
        Type
      </th>

      <th>
        Description
      </th>
    </tr>
  </thead>

  <tbody>
    <tr>
      <td>
        <code>ref</code>
      </td>

      <td>
        <code />
      </td>

      <td>
        Pass to

        <code />
      </td>
    </tr>

    <tr>
      <td>
        <code>write</code>
      </td>

      <td>
        <code>(data: string | Uint8Array) => void</code>
      </td>

      <td>
        Write data to the terminal
      </td>
    </tr>

    <tr>
      <td>
        <code>resize</code>
      </td>

      <td>
        <code>(cols: number, rows: number) => void</code>
      </td>

      <td>
        Resize the terminal grid
      </td>
    </tr>

    <tr>
      <td>
        <code>focus</code>
      </td>

      <td>
        <code>() => void</code>
      </td>

      <td>
        Focus the terminal input
      </td>
    </tr>
  </tbody>
</table>

The `TerminalHandle` interface exposed via `ref`:

```ts
interface TerminalHandle {
  write(data: string | Uint8Array): void;
  resize(cols: number, rows: number): void;
  focus(): void;
  readonly instance: WTerm | null;
}
```

## Template Ref (Vue)

The Vue `<Terminal>` component exposes the same methods directly on its instance — no separate composable. Access them via `useTemplateRef`:

```vue
<script setup lang="ts">
import { useTemplateRef } from "vue";
import { Terminal } from "@wterm/vue";

const term = useTemplateRef("term");
</script>

<template>
  <Terminal ref="term" />
</template>
```

<table>
  <thead>
    <tr>
      <th>
        Member
      </th>

      <th>
        Type
      </th>

      <th>
        Description
      </th>
    </tr>
  </thead>

  <tbody>
    <tr>
      <td>
        <code>write</code>
      </td>

      <td>
        <code>(data: string | Uint8Array) => void</code>
      </td>

      <td>
        Write data to the terminal. Safe to call after the

        <code>ready</code>

        event; calls before mount are ignored.
      </td>
    </tr>

    <tr>
      <td>
        <code>resize</code>
      </td>

      <td>
        <code>(cols: number, rows: number) => void</code>
      </td>

      <td>
        Resize the terminal grid. Calls before mount are ignored.
      </td>
    </tr>

    <tr>
      <td>
        <code>focus</code>
      </td>

      <td>
        <code>() => void</code>
      </td>

      <td>
        Focus the terminal input.
      </td>
    </tr>

    <tr>
      <td>
        <code>instance</code>
      </td>

      <td>
        <code>WTerm | null</code>
      </td>

      <td>
        Underlying

        <code>WTerm</code>

        instance.

        <code>null</code>

        until the component has mounted; the WASM bridge is only available after the

        <code>ready</code>

        event.
      </td>
    </tr>
  </tbody>
</table>

## Imperative Handle (Svelte)

Bind the Svelte component instance to access its imperative methods:

```svelte
<script lang="ts">
import { Terminal, type TerminalHandle } from "@wterm/svelte";

let terminal: TerminalHandle;
</script>

<Terminal bind:this={terminal} />
```

<table>
  <thead>
    <tr>
      <th>
        Member
      </th>

      <th>
        Type
      </th>

      <th>
        Description
      </th>
    </tr>
  </thead>

  <tbody>
    <tr>
      <td>
        <code>write</code>
      </td>

      <td>
        <code>(data: string | Uint8Array) => void</code>
      </td>

      <td>
        Write data to the terminal. Calls before initialization are ignored.
      </td>
    </tr>

    <tr>
      <td>
        <code>resize</code>
      </td>

      <td>
        <code>(cols: number, rows: number) => void</code>
      </td>

      <td>
        Resize the terminal grid.
      </td>
    </tr>

    <tr>
      <td>
        <code>focus</code>
      </td>

      <td>
        <code>() => void</code>
      </td>

      <td>
        Focus the terminal input.
      </td>
    </tr>
  </tbody>
</table>

To access the underlying <code>WTerm</code>, bind the component's
<code>instance</code> prop:

```svelte
<script lang="ts">
import { Terminal, type WTerm } from "@wterm/svelte";

let instance: WTerm | null = null;
</script>

<Terminal bind:instance />
```

## WebSocketTransport

Connect to a PTY backend over WebSocket with binary frames, automatic reconnection, and bounded send buffering.

### Options

<table>
  <thead>
    <tr>
      <th>
        Option
      </th>

      <th>
        Type
      </th>

      <th>
        Default
      </th>

      <th>
        Description
      </th>
    </tr>
  </thead>

  <tbody>
    <tr>
      <td>
        <code>url</code>
      </td>

      <td>
        <code>string</code>
      </td>

      <td>
        —
      </td>

      <td>
        WebSocket server URL
      </td>
    </tr>

    <tr>
      <td>
        <code>reconnect</code>
      </td>

      <td>
        <code>boolean</code>
      </td>

      <td>
        <code>true</code>
      </td>

      <td>
        Automatically reconnect on disconnect with exponential backoff
      </td>
    </tr>

    <tr>
      <td>
        <code>maxReconnectDelay</code>
      </td>

      <td>
        <code>number</code>
      </td>

      <td>
        <code>30000</code>
      </td>

      <td>
        Maximum delay between reconnection attempts (ms)
      </td>
    </tr>

    <tr>
      <td>
        <code>maxBufferedBytes</code>
      </td>

      <td>
        <code>number</code>
      </td>

      <td>
        <code>1048576</code>
      </td>

      <td>
        Maximum queued bytes, including the current open socket's buffered amount. Positive safe integer.
      </td>
    </tr>

    <tr>
      <td>
        <code>maxBufferedMessages</code>
      </td>

      <td>
        <code>number</code>
      </td>

      <td>
        <code>1024</code>
      </td>

      <td>
        Maximum messages waiting in the transport queue, including empty messages. Positive safe integer.
      </td>
    </tr>

    <tr>
      <td>
        <code>highWaterMark</code>
      </td>

      <td>
        <code>number</code>
      </td>

      <td>
        <code>65536</code>
      </td>

      <td>
        Byte threshold for pausing socket writes and reporting pressure. Defaults to the smaller of 64 KiB and the byte cap.
      </td>
    </tr>

    <tr>
      <td>
        <code>lowWaterMark</code>
      </td>

      <td>
        <code>number</code>
      </td>

      <td>
        <code>16384</code>
      </td>

      <td>
        Resume socket writes at or below this threshold. Defaults to one quarter of the high-water mark.
      </td>
    </tr>

    <tr>
      <td>
        <code>onBackpressure</code>
      </td>

      <td>
        <code>(paused: boolean) => void</code>
      </td>

      <td>
        —
      </td>

      <td>
        Called on pressure transitions. Pause producers while true and resume when false.
      </td>
    </tr>

    <tr>
      <td>
        <code>onData</code>
      </td>

      <td>
        <code>(data: Uint8Array | string) => void</code>
      </td>

      <td>
        —
      </td>

      <td>
        Called when data is received from the server
      </td>
    </tr>

    <tr>
      <td>
        <code>onOpen</code>
      </td>

      <td>
        <code>() => void</code>
      </td>

      <td>
        —
      </td>

      <td>
        Called when the connection opens
      </td>
    </tr>

    <tr>
      <td>
        <code>onClose</code>
      </td>

      <td>
        <code>() => void</code>
      </td>

      <td>
        —
      </td>

      <td>
        Called when the connection closes
      </td>
    </tr>

    <tr>
      <td>
        <code>onError</code>
      </td>

      <td>
        <code>(event: Event) => void</code>
      </td>

      <td>
        —
      </td>

      <td>
        Called when a WebSocket error occurs
      </td>
    </tr>
  </tbody>
</table>

### Methods

<table>
  <thead>
    <tr>
      <th>
        Method
      </th>

      <th>
        Description
      </th>
    </tr>
  </thead>

  <tbody>
    <tr>
      <td>
        <code>connect(url?)</code>
      </td>

      <td>
        Open the WebSocket connection. Same-URL calls while connecting/open do nothing. Changing the URL discards unsent data.
      </td>
    </tr>

    <tr>
      <td>
        <code>send(data: string | Uint8Array)</code>
      </td>

      <td>
        Accept a complete message for ordered delivery. Throws RangeError before accepting it if either buffer limit would be exceeded. Throws after explicit close until connect is called again.
      </td>
    </tr>

    <tr>
      <td>
        <code>close()</code>
      </td>

      <td>
        Close the connection, discard unsent data, and stop reconnect and drain timers.
      </td>
    </tr>
  </tbody>
</table>

### Properties

<table>
  <thead>
    <tr>
      <th>
        Property
      </th>

      <th>
        Type
      </th>

      <th>
        Description
      </th>
    </tr>
  </thead>

  <tbody>
    <tr>
      <td>
        <code>connected</code>
      </td>

      <td>
        <code>boolean</code>
      </td>

      <td>
        Whether the WebSocket is currently open
      </td>
    </tr>

    <tr>
      <td>
        <code>bufferedAmount</code>
      </td>

      <td>
        <code>number</code>
      </td>

      <td>
        Bytes in the transport queue plus the current open socket's send buffer.
      </td>
    </tr>

    <tr>
      <td>
        <code>queuedBytes</code>

        /

        <code>queuedMessages</code>
      </td>

      <td>
        <code>number</code>
      </td>

      <td>
        Bytes/messages not yet handed to the browser socket.
      </td>
    </tr>

    <tr>
      <td>
        <code>backpressured</code>
      </td>

      <td>
        <code>boolean</code>
      </td>

      <td>
        Whether producers should pause. Clears when buffered bytes reach the low-water mark and the message queue has room.
      </td>
    </tr>
  </tbody>
</table>

### Buffering and delivery

Strings are counted and sent as UTF-8 bytes. Queued byte-array views are copied,
so changing the caller's buffer cannot change a pending command. Message order
and boundaries are preserved. The limits apply before the first connection,
while disconnected, and while the socket is open. Limits are construction
options; they require positive safe integer caps and a positive high-water mark,
with `0 <= lowWaterMark < highWaterMark <= maxBufferedBytes`.

The transport pauses feeding the socket at the high-water mark and resumes at
the low-water mark. A single message larger than the high-water mark is allowed
only when the socket buffer is empty and it fits the hard byte cap. Drains
process at most 64 messages at a time and poll every 16 ms only while an open
socket has pending data. Background browser throttling can delay these polls.
`onBackpressure` reports high/low byte threshold transitions and a full message
queue; hosts should also inspect `connected` for connection status.

Catch a buffer-limit `RangeError` and tell the user that the input was not
accepted. A rejected message is never partially queued; existing queued messages
remain unchanged. Do not silently ignore the error or automatically retry shell
commands. `send()` accepting data and `bufferedAmount` reaching zero are not
server acknowledgments.

Unexpected disconnection retains only messages still in the transport queue.
Bytes already handed to `WebSocket.send()` have uncertain delivery and are never
replayed. Explicit `close()` clears unsent data and rejects sends until a new
`connect()`; changing the URL also clears unsent data. Delayed callbacks from
replaced sockets cannot feed output or close their replacement. This transport
bounds client sends; it does not implement incoming-output flow control, PTY
session recovery, or delivery acknowledgments.

## WasmBridge

Low-level interface to the Zig/WASM terminal state machine. The bridge manages a virtual terminal grid in WASM memory — you write data in and read cells, cursor state, and scrollback out.

### Loading

```ts
import { WasmBridge } from "@wterm/core";

const bridge = await WasmBridge.load();
const bridge = await WasmBridge.load("/wterm.wasm");
```

When no URL is provided, the \~49 KB WASM binary is decoded from a base64 string inlined in the package. Pass a URL when you want to serve the binary separately for caching or CDN use.

### Methods

<table>
  <thead>
    <tr>
      <th>
        Method
      </th>

      <th>
        Description
      </th>
    </tr>
  </thead>

  <tbody>
    <tr>
      <td>
        <code />
      </td>

      <td>
        Load the WASM binary and return a new bridge instance
      </td>
    </tr>

    <tr>
      <td>
        <code>init(cols, rows)</code>
      </td>

      <td>
        Initialize the terminal grid
      </td>
    </tr>

    <tr>
      <td>
        <code>writeString(str, afterChunk?)</code>
      </td>

      <td>
        Write a UTF-8 string and optionally run a callback after each internal chunk
      </td>
    </tr>

    <tr>
      <td>
        <code>writeRaw(data, afterChunk?)</code>
      </td>

      <td>
        Write raw bytes and optionally run a callback after each 8192-byte internal chunk
      </td>
    </tr>

    <tr>
      <td>
        <code>resize(cols, rows)</code>
      </td>

      <td>
        Resize the terminal grid
      </td>
    </tr>

    <tr>
      <td>
        <code>getCell(row, col): CellData</code>
      </td>

      <td>
        Get cell data at a grid position
      </td>
    </tr>

    <tr>
      <td>
        <code>getCursor(): CursorState</code>
      </td>

      <td>
        Get current cursor position and visibility
      </td>
    </tr>

    <tr>
      <td>
        <code>getCols() / getRows()</code>
      </td>

      <td>
        Get current grid dimensions
      </td>
    </tr>

    <tr>
      <td>
        <code>isDirtyRow(row): boolean</code>
      </td>

      <td>
        Check if a row has changed since last

        <code>clearDirty()</code>
      </td>
    </tr>

    <tr>
      <td>
        <code>getRowId(row): number | null</code>
      </td>

      <td>
        Optional. Live row identity that follows scrolling; null when unavailable
      </td>
    </tr>

    <tr>
      <td>
        <code>isRowContentDirty(row): boolean</code>
      </td>

      <td>
        Optional. Whether the row's content changed since

        <code>clearDirty()</code>

        , ignoring movement
      </td>
    </tr>

    <tr>
      <td>
        <code>clearDirty()</code>
      </td>

      <td>
        Reset all dirty-row flags
      </td>
    </tr>

    <tr>
      <td>
        <code>getTitle(): string | null</code>
      </td>

      <td>
        Get pending title change (via OSC escape), or

        <code>null</code>

        if unchanged
      </td>
    </tr>

    <tr>
      <td>
        <code>getWorkingDirectory?(): string | null</code>
      </td>

      <td>
        Consume a raw OSC 7 URI from a supporting core. Empty clears it; null means unchanged or unsupported. See

        <a href="/configuration#working-directories">Working directories</a>

        .
      </td>
    </tr>

    <tr>
      <td>
        <code>getBellCount?(): number</code>
      </td>

      <td>
        Read and clear pending BEL controls. Optional for custom cores; the built-in and Ghostty cores return

        <code>0</code>

        when none are pending.
      </td>
    </tr>

    <tr>
      <td>
        <code>getShellIntegrationState?(): ShellIntegrationState | null</code>
      </td>

      <td>
        Consume the latest shell-reported state change. The caller-owned snapshot has phase and exitCode; null means unchanged or unsupported. See

        <a href="/configuration#shell-integration">Shell integration</a>

        .
      </td>
    </tr>

    <tr>
      <td>
        <code>findPrompt?(row: number, direction: -1 | 1): number | null</code>
      </td>

      <td>
        Optional Ghostty lookup for the previous/next prompt start. Row zero is the oldest retained row; the origin must be in range. Skips continuation rows when moving forwards and returns null for unsupported cores, alternate screens, invalid inputs, or missing prompts. Does not move the cursor or consume shell state.
      </td>
    </tr>

    <tr>
      <td>
        <code>getClipboardWrite?(): string | null</code>
      </td>

      <td>
        Consume the latest application clipboard-write request. Empty text requests a clear; null means none. Ghostty only; the host owns acceptance.
      </td>
    </tr>

    <tr>
      <td>
        <code>getResponse(): string | null</code>
      </td>

      <td>
        Dequeue one pending host response (e.g. DSR), or

        <code>null</code>

        .
      </td>
    </tr>

    <tr>
      <td>
        <code>getResourceState?(): TerminalResourceState</code>
      </td>

      <td>
        Get optional core resource state. The built-in core reports hyperlink identity capacity, usage, capacity-rejected opens, and saturation.
      </td>
    </tr>

    <tr>
      <td>
        <code>getScrollbackCount(): number</code>
      </td>

      <td>
        Number of lines in the scrollback buffer
      </td>
    </tr>

    <tr>
      <td>
        <code>getScrollbackDiscardedCount?(): number</code>
      </td>

      <td>
        Cumulative rows discarded from the oldest end. Optional for third-party cores; WTerm uses it to preserve history position through rollover.
      </td>
    </tr>

    <tr>
      <td>
        <code>setScrollbackLines?(lines): void</code>
      </td>

      <td>
        Keep this many lines of history, dropping the oldest. Optional for third-party cores; required by the

        <code>scrollbackLines</code>

        option.
      </td>
    </tr>

    <tr>
      <td>
        <code>getScrollbackCell(offset, col): CellData</code>
      </td>

      <td>
        Get cell data from a scrollback line
      </td>
    </tr>

    <tr>
      <td>
        <code>getScrollbackLineLen(offset): number</code>
      </td>

      <td>
        Get the length of a scrollback line
      </td>
    </tr>

    <tr>
      <td>
        <code>cursorKeysApp(): boolean</code>
      </td>

      <td>
        Whether cursor keys are in application mode
      </td>
    </tr>

    <tr>
      <td>
        <code>bracketedPaste(): boolean</code>
      </td>

      <td>
        Whether bracketed paste mode is active
      </td>
    </tr>

    <tr>
      <td>
        <code>usingAltScreen(): boolean</code>
      </td>

      <td>
        Whether the alternate screen buffer is active
      </td>
    </tr>

    <tr>
      <td>
        <code>mouseTracking(): number</code>
      </td>

      <td>
        Active mouse tracking mode (

        <code>0</code>

        ,

        <code>1000</code>

        ,

        <code>1002</code>

        , or

        <code>1003</code>

        )
      </td>
    </tr>

    <tr>
      <td>
        <code>mouseSgr(): boolean</code>
      </td>

      <td>
        Whether cell-coordinate SGR mouse encoding (mode 1006) is active
      </td>
    </tr>

    <tr>
      <td>
        <code>mouseEncoding(): MouseEncoding | null</code>
      </td>

      <td>
        Active mouse wire format:

        <code>x10</code>

        ,

        <code>utf8</code>

        ,

        <code>sgr</code>

        ,

        <code>urxvt</code>

        , or

        <code>sgr-pixels</code>

        . Optional for custom cores. The DOM layer sends all five formats;

        <code>sgr-pixels</code>

        uses 1-based CSS-pixel coordinates.
      </td>
    </tr>

    <tr>
      <td>
        <code>focusEvents(): boolean</code>
      </td>

      <td>
        Whether focus reporting (mode 1004) is active
      </td>
    </tr>

    <tr>
      <td>
        <code>synchronizedOutput(): boolean</code>
      </td>

      <td>
        Whether synchronized output mode (2026) is active
      </td>
    </tr>

    <tr>
      <td>
        <code>synchronizedOutputGeneration(): number</code>
      </td>

      <td>
        Monotonic generation for synchronized output blocks
      </td>
    </tr>

    <tr>
      <td>
        <code>kittyKeyboardFlags?(): number</code>
      </td>

      <td>
        Active Kitty keyboard protocol flags. Optional for third-party cores; omission keeps legacy keyboard encoding.
      </td>
    </tr>

    <tr>
      <td>
        <code>modifyOtherKeys?(): 0 | 2</code>
      </td>

      <td>
        2 while xterm modifyOtherKeys level 2 is active. Optional for third-party cores; omission keeps legacy keyboard encoding.
      </td>
    </tr>
  </tbody>
</table>

### Types

`underlineStyle` takes precedence over the underline flag (`0x08`). If omitted,
that flag selects a single underline. `underlineRgb` is an optional resolved
`0xRRGGBB` color, including `0` for black; omitted colors follow the displayed
foreground, including reverse video. Ghostty supplies the metadata in live
cells and scrollback. The built-in core and older Ghostty binaries retain
flag-based single underlines. Custom cores can adopt either optional field.
`UnderlineStyle` is exported by `@wterm/core` and `@wterm/dom`.

```ts
type UnderlineStyle = "none" | "single" | "double" | "curly" | "dotted" | "dashed";

interface CellData {
  char: number;   // Unicode code point
  chars?: string; // Complete grapheme cluster when the cell has multiple code points
  fg: number;     // Foreground color index (256 = default)
  bg: number;     // Background color index (256 = default)
  flags: number;  // Style flags (bold, italic, underline, etc.)
  width?: number; // 1 = narrow, 2 = wide leading cell, 0 = continuation
  spacerHead?: boolean; // Empty right-edge filler before a wrapped wide glyph
  fgRgb?: number; // Resolved 0xRRGGBB foreground
  bgRgb?: number; // Resolved 0xRRGGBB background
  underlineStyle?: UnderlineStyle; // Overrides flags bit 0x08 when present
  underlineRgb?: number; // Resolved 0xRRGGBB; omitted follows the displayed foreground
}

interface CursorState {
  row: number;
  col: number;
  visible: boolean;
  shape?: "block" | "bar" | "underline";
  blinking?: boolean;
}
```

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

---
title: Configuration
url: "https://wterm.dev/configuration"
docs_index: /llms.txt
lastUpdated: 2026-11-06
navTitle: "Configuration"
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

## Options

The React, Vue, and Svelte `Terminal` components and the vanilla `WTerm` constructor accept the same core options — `cols`, `rows`, `core`, `wasmUrl`, `autoResize`, `maxImageWidth`, `maxImageHeight`, `cursorBlink`, `announceOutput`, `scrollbackLines`, `renderingPaused`, `renderer`, `webmcp`, and `debug` — plus event callbacks `onData`, `onTitle`, `onBell`, `onClipboardWrite`, `onShellIntegration`, `onWorkingDirectory`, and `onResize` (exposed as `@data`, `@title`, `@bell`, `@clipboard-write`, `@shell-integration`, `@working-directory`, and `@resize` events in Vue).

See the full [API Reference](/api-reference#terminal-options) for types, defaults, and descriptions.

### WebGL renderer

Set `renderer: "webgl"` to paint live output on a WebGL2 canvas instead of
updating DOM text every frame. Use it for full-screen apps that redraw the
whole grid, such as `btop`, games, and video, and for pages with many panes.
All terminals on a page share one WebGL2 context, so large pane grids are not
limited by the browser's cap on live contexts.

The DOM text remains the source for selection, find, copying, and screen
readers. It catches up when output goes quiet and as soon as the pointer or
wheel touches the terminal. While text is selected, searched, or scrolled into
history, the terminal shows the DOM text; the canvas resumes with the next
live output. Without WebGL2, the option falls back to `"dom"`.

```ts
const term = new WTerm(element, { renderer: "webgl" });
```

### Inactive panes

Set `renderingPaused: true` for a pane that is not visible. Change it with
`term.setRenderingPaused(paused)` in vanilla JavaScript, or the reactive
`renderingPaused` prop in React, Vue, and Svelte. The local workspace does this
automatically for sessions outside the visible layout. All visible split panes
keep painting, including those without keyboard focus.

Paused panes keep parsing output, retaining history, resizing the core, and
delivering replies, titles, and bells. They stop scheduling terminal paints and
show the latest state on the next eligible frame after resuming. Output is not
queued for replay, and the PTY connection stays active. All terminals also pause
painting while their browser document is hidden; returning does not override
an explicitly paused pane.

The option controls rendering only: hosts still manage visibility, focus,
`inert`, and `aria-hidden`. CSS visibility alone does not set the option. New
search scans, `readText()`, and Select All wait for a paint before starting;
their existing cancellation rules still apply. Pending output announcements
are discarded on pause. Synchronized drawing continues to hold a frame until
the application ends the update or its recovery timeout expires.

### Many terminals on one page

Each terminal using the built-in core is a separate WebAssembly instance. In
Chrome, a page can hold about a hundred live instances before the next `init()`
rejects with `WebAssembly.instantiate(): Out of memory`, although each one uses
about 2 MB. `destroy()` releases the instance, so the limit counts terminals
that exist at the same time.

`renderingPaused` stops painting but keeps the instance. For a page that can
show many terminals, such as a transcript or a log viewer, create each terminal
when it scrolls into view and destroy it when it leaves. Keep the output it
received and write it into a new terminal when it comes back. The built-in core
retains 1,000 scrollback lines, so that is all a new terminal needs. The
[ACP agent example](https://github.com/vercel-labs/wterm/tree/main/examples/acp-agent)
works this way.

### Local workspace shortcuts

The [local shell workspace](https://github.com/vercel-labs/wterm/tree/main/examples/local)
includes **Keyboard shortcuts** in its sidebar. Customize bindings for sessions,
splits, directional pane focus, zoom/restore, Find, and prompt navigation. **Change** records one
combination; **Clear** disables a binding and **Reset defaults** restores the
defaults. Conflicts are reported before settings change. Preferences persist in
the browser and synchronize between tabs and engine routes.

Find defaults to Command+F or Control+Shift+F. Pane focus defaults to
Command+Option+arrows or Control+Alt+arrows. Other commands start unassigned.
Custom combinations require Control or Command and use physical key positions.
Common clipboard/browser commands and shell interruption keys remain reserved;
Control+Alt text keys are reserved for AltGr. Browser or OS shortcuts may be
unavailable to the page.

Shortcuts act from terminal input; Find and New terminal session also work from
workspace buttons. They do not take over Find fields, dialogs, composition, or
AltGr. Assigned keys are consumed even at pane limits and do not repeat while
held. Close current session terminates that shell. Escape cancels recording
before closing the dialog. Storage failures leave changes active for the current
page with a visible status. These controls belong to the local workspace; they
do not change the terminal libraries' keyboard bindings. Session recovery is
described below.

### Scrollback rendering

Scrollback keeps a bounded window of mounted rows. Small scrolls reuse this
window until the viewport approaches its edge. When it moves, overlapping
rows reuse their existing elements; unchanged text and links keep their DOM
nodes when new output arrives. This reduces DOM work while reading history
without changing selection, copy, or the amount of retained history. The
behavior is automatic for both cores and all framework bindings.

### PTY size

With vanilla `WTerm` and `autoResize: true`, omitted `cols` and `rows` are
measured from the container before the core is initialized. This avoids allocating
an 80×24 grid and immediately resizing it. Explicit dimensions are respected at
initialization; subsequent container changes still auto-resize. Hidden or
unmeasurable containers use the 80×24 defaults until they can be measured.
With `autoResize: false`, dimensions retain their explicit values or defaults.

After `init()` or `resize()`, read `wt.cols` and `wt.rows` to size a connected
PTY. `onResize(cols, rows)` also receives the applied grid size, including when
initialization changes the requested size. A core can
apply fewer cells than requested; the built-in core grows its cell storage as needed, up to
1024 columns and 512 rows. Keeping the PTY at the applied size prevents its output from
wrapping at different columns than the browser terminal.

### Bell notifications

Both cores count BEL controls outside OSC sequences. `onBell(count)` runs as
output is written, even if synchronized output is holding a paint. Several
bells in one write chunk arrive as one count. WTerm leaves sound and visual
alerts to the host application.

### Working directories

With Ghostty, `onWorkingDirectory(uri)` receives the latest shell-reported
OSC 7 URI. React and Svelte use that callback prop; Vue uses
`@working-directory`. Svelte also accepts `onworkingdirectory`. Events arrive
during parsing, including in hidden panes and while synchronized output holds
a paint. Missing callbacks discard updates. The built-in core and older WASM
assets produce no events; update a manually served Ghostty binary when upgrading.

The raw URI is untrusted metadata. It does not establish a local filesystem
path or authenticate a remote host. Validate it for your application and render
labels as text. WTerm does not open URLs, access directories, or change a
shell's working directory in response.

Ghostty retains at most 2047 UTF-8 bytes. Invalid UTF-8 and oversized reports
are ignored; pending reports coalesce and identical repeats are suppressed.
An empty report or full terminal reset (RIS) clears the value with `""`.
`TerminalCore.getWorkingDirectory?()` consumes the same updates, with `null`
meaning unchanged or unsupported. Reports apply to either screen and survive
resizing; initialization starts without a directory.

Enable OSC 7 in your shell integration, including the local shell when
returning from SSH. WTerm does not install shell configuration. For example,
this report displays `build-box:/home/me/project name` in the local `/ghostty`
workspace:

```sh
printf '\033]7;%s\033\\' 'file://build-box/home/me/project%20name'
```

The workspace accepts `file://host/absolute/path` reports, decodes the path,
and retains any nonempty host in the label. The tooltip identifies the value as
shell-reported. Invalid/non-file URLs, credentials, ports, and control-bearing
paths are ignored. The last valid report takes precedence over process polling;
empty OSC 7 or RIS restores the latest local process directory. Directory
reports restore through saved session output within the documented reload
recovery limits.

### Shell integration

Ghostty forwards shell-reported OSC 133 state through
`onShellIntegration({ phase, exitCode })`. React and Svelte use that callback
prop; Vue uses `@shell-integration`. Svelte also accepts `onshellintegration`.
The snapshot arrives as output is parsed, including in hidden panes and while
synchronized output holds a paint. A missing callback discards changes; adding
one later does not replay them.

<table>
  <thead>
    <tr><th>Marker</th><th>Phase</th><th>Exit code</th></tr>
  </thead>

  <tbody>
    <tr>
      <td>OSC 133 A, N, or P</td>

      <td><code>prompt</code></td>

      <td>Retains the previous result</td>
    </tr>

    <tr>
      <td>OSC 133 B or I</td>

      <td><code>input</code></td>

      <td>Retains the previous result</td>
    </tr>

    <tr>
      <td>OSC 133 C</td>

      <td><code>running</code></td>

      <td>Cleared to <code>null</code></td>
    </tr>

    <tr>
      <td>OSC 133 D</td>

      <td><code>complete</code></td>

      <td>Signed 32-bit code, or <code>null</code> if missing or invalid</td>
    </tr>

    <tr>
      <td>Terminal reset (RIS)</td>

      <td><code>unknown</code></td>

      <td>Cleared to <code>null</code></td>
    </tr>
  </tbody>
</table>

Multiple markers within one parser chunk coalesce into the latest state. For
example, D with exit code 7 followed by A and B produces `input` with code 7.
This is a bounded current-state snapshot, not a command history or proof of
process completion. No command text is retained. Alternate-screen markers do
not replace the primary shell's state. The built-in core, custom cores without
`getShellIntegrationState()`, and older Ghostty WASM assets report no state.
Update a manually served WASM asset when upgrading.

The local `/ghostty` workspace displays **Ready**, **Running**, **Done**, or
**Exit N** for each participating session, including background sessions. It
hides the indicator when disconnected or reset. These labels are separate from
connection and PTY exit status.

The workspace also provides **Previous prompt** and **Next prompt** controls for
participating sessions. Assign either command in **Keyboard shortcuts**; both
start unassigned. Navigation starts at the viewport's top row, skips continuation
rows of multiline prompts, and stops at either end without wrapping. It can
reach unmounted history and follows reflow and pruning. Full-screen applications
on the alternate screen keep control of the terminal.

Assigned navigation keys are consumed by the workspace. Applications requesting
physical modifier-key reports still receive those reports without scrolling
the viewport back to the bottom.

In an embed, use the underlying `WTerm` instance:

```ts
term.scrollToPrompt(-1); // previous prompt
term.scrollToPrompt(1);  // next prompt
```

The boolean result reports whether the viewport moved. Focus, selection, and
the Find query are preserved, and no input is sent to the shell. Unsupported
cores and older WASM assets return `false`, as do missing prompts, hidden/paused
terminals, and synchronized output that is holding a paint. Navigation into
the live screen stops at the bottom of the scrollable viewport. No shortcuts
are installed by the terminal library.

The shell must emit OSC 133. WTerm does not modify shell startup files. Enable
markers in your existing shell integration, or try these minimal zsh hooks in a
fresh interactive session that does not already emit them:

```zsh
autoload -Uz add-zsh-hook
typeset -gi _wterm_command_running=0
_wterm_command_start() {
  _wterm_command_running=1
  printf '\033]133;C\007'
}
_wterm_command_done() {
  local result=$?
  if (( _wterm_command_running )); then
    printf '\033]133;D;%s\007' "$result"
  fi
  _wterm_command_running=0
  printf '\033]133;A\007'
}
add-zsh-hook preexec _wterm_command_start
add-zsh-hook precmd _wterm_command_done
```

These minimal hooks mark prompt start, command start, and completion. They do
not rewrite the prompt or add an input-start marker. Existing themes or hooks
can affect their ordering and the exit status they observe. Do not combine them
with another OSC 133 integration.

### Clipboard requests

With Ghostty, `onClipboardWrite(text)` receives application OSC 52 clipboard
writes. WTerm never reads or writes the browser clipboard automatically. A
missing handler discards requests; installing one later does not replay them.
React and Svelte use the callback prop; Vue uses `@clipboard-write`. Svelte also
accepts `onclipboardwrite`.

The host must apply its policy, for example by displaying a stable text preview
and calling `navigator.clipboard.writeText(text)` from an explicit Copy button.
Handle browser permission failures. The local Ghostty workspace provides
**Review clipboard request**, **Copy**, and **Dismiss** controls.

Ghostty accepts valid base64-encoded UTF-8 up to 65,536 decoded bytes for the
default selector or `c`. Empty text requests a clear. Clipboard reads, other
selectors, invalid text, and oversized writes are ignored. Only the latest
valid pending write in a parsed chunk is retained. Delivery continues while
painting is paused or synchronized. The optional `getClipboardWrite()` core
method is absent from the built-in core; older Ghostty binaries produce no
requests. Update any manually served WASM asset when upgrading.

### Cursor appearance

Both cores honor application requests for block, bar, and underline cursors
(DECSCUSR), plus cursor blink mode (`CSI ? 12 h/l`). This lets editors change
the cursor when switching between normal and insert mode. The cursor keeps its
active appearance while the mouse is pressed inside the terminal, preserving
native drag selection. Otherwise, unfocused terminals show a steady outline.
Hiding the cursor with mode 25 takes effect even
when no text changes.

Omit `cursorBlink` to follow the application (initially steady). Set it to
`true` to force blinking or `false` to keep the cursor steady, regardless of
application requests. React, Vue, and Svelte also support changing this prop
after mounting; set it back to `undefined` to follow the application again.
Cursor shape always follows the core. Custom cores can omit the optional
`CursorState.shape` and `blinking` fields for a steady block fallback.

### Pluggable cores

By default, wterm uses its built-in lightweight Zig WASM core (\~49 KB). To use a different terminal emulation backend, pass a `TerminalCore` instance via the `core` option. See the [Ghostty Core](/ghostty) page for an example using libghostty.

Graphics support is optional on `TerminalCore`. The built-in core safely
consumes unsupported Kitty APC payloads without rendering image bytes;
`@wterm/ghostty` provides bounded direct Kitty PNG/RGB/RGBA rendering. Its
decoded image budget defaults to 32 MiB and can be disabled with
`imageStorageLimit: 0`. Use `maxImageWidth` and/or `maxImageHeight` to cap the
rendered dimensions of decoded images without changing that storage budget.
The DOM wrapper answers Kitty pixel geometry queries (`CSI 14 t` and
`CSI 16 t`) from the rendered terminal element and forwards the reports
through `onData`.

### Worker core

`WorkerCore` from `@wterm/core/worker` runs the built-in core in a Web Worker. Parsing
leaves the main thread, so pages with many busy panes can use more than one
CPU core. Pair it with `renderer: "webgl"`:

```ts
import { WorkerCore } from "@wterm/core/worker";

const core = await WorkerCore.load();
const term = new WTerm(element, { core, renderer: "webgl" });
```

Writes return before they are parsed: cells, replies, titles, and bells arrive
with the worker's next update. Each `WorkerCore` owns one worker; call
`dispose()` when the terminal is gone. The worker uses code splitting, so set
`worker: { format: "es" }` in a Vite config.

### React-only

The React `Terminal` component adds `theme`, `onReady`, and `onError` on top of the shared options. It also spreads standard HTML attributes onto the root `div`, so you can pass `className`, `style`, ARIA attributes, and other DOM props directly.

See [React-Only Props](/api-reference#react-only-props) for details.

### Vue-only

The Vue `Terminal` component adds a `theme` prop. `onReady` and `onError` are exposed as `@ready` and `@error` events instead of props. Standard DOM attributes (`class`, `style`, `id`, ARIA props, etc.) are forwarded to the root `<div>` via the default `inheritAttrs` behavior.

See [Vue-Only Props](/api-reference#vue-only-props) and [Vue Events](/api-reference#vue-events) for details.

### Svelte-only

The Svelte `Terminal` component adds the `theme` prop and forwards standard
`<div>` attributes such as `class`, `style`, `id`, and ARIA attributes. Svelte 5
applications can use the `onData`, `onTitle`, `onBell`, `onClipboardWrite`, `onShellIntegration`, `onWorkingDirectory`, `onResize`, `onReady`, and
`onError` callback props.

See [Svelte-only Props](/api-reference#svelte-only-props) for details.

## Imperative Handle (React)

When using `useTerminal`, the returned ref exposes an imperative handle:

```tsx
import { Terminal, useTerminal } from "@wterm/react";

function App() {
  const { ref } = useTerminal();

  return (
    <Terminal
      ref={ref}
      onReady={(wt) => {
        wt.resize(120, 40);
      }}
    />
  );
}
```

See the full [Imperative Handle](/api-reference#imperative-handle-react) reference for all returned methods.

## Template Ref (Vue)

Access imperative methods on the Vue component via a template ref:

```vue
<script setup lang="ts">
import { useTemplateRef } from "vue";
import { Terminal, type WTerm } from "@wterm/vue";

const term = useTemplateRef("term");

function onReady(wt: WTerm) {
  wt.resize(120, 40);
}
</script>

<template>
  <Terminal ref="term" @ready="onReady" />
</template>
```

See the full [Template Ref (Vue)](/api-reference#template-ref-vue) reference for all exposed methods.

## Imperative Handle (Svelte)

Bind the Svelte component instance to call its imperative methods:

```svelte
<script lang="ts">
import { Terminal, type TerminalHandle } from "@wterm/svelte";

let terminal: TerminalHandle;
</script>

<Terminal bind:this={terminal} />
```

See the full [Imperative Handle (Svelte)](/api-reference#imperative-handle-svelte) reference for all exposed methods.

## WebSocket Transport

For connecting to a remote shell over WebSocket, use the built-in `WebSocketTransport`:

```ts
import { WTerm, WebSocketTransport } from "@wterm/dom";

const ws = new WebSocketTransport({
  url: "wss://your-server.example/shell",
  reconnect: true,
  maxReconnectDelay: 5000,
  onBackpressure(paused) {
    console.log(paused ? "Waiting for the connection to drain" : "Send buffer ready");
  },
  onData(data) {
    term.write(data);
  },
  onOpen() {
    console.log("connected");
  },
  onClose() {
    console.log("disconnected");
  },
});

const term = new WTerm(document.getElementById("terminal"), {
  onData(data) {
    try {
      ws.send(data);
    } catch (error) {
      console.error("Input was not sent", error); // Show this in your app's UI.
    }
  },
});

await term.init();
ws.connect();
```

See the full [WebSocketTransport](/api-reference#websockettransport) reference for all options, methods, and properties.

The transport buffers sends within configurable byte/message limits and drains
them in order. The defaults are 1 MiB across the transport and socket buffers,
and 1,024 messages waiting in the transport. Use `onBackpressure` to pause
producers and catch `RangeError` to report input that exceeded a limit. Explicit
`close()` discards unsent data; reconnect never replays bytes already handed to
the browser. See [Buffering and delivery](/api-reference#buffering-and-delivery)
for thresholds, lifecycle behavior, and delivery limits.

### Local split panes

The [local example](https://github.com/vercel-labs/wterm/tree/main/examples/local)
can display up to four shells using **Split right** and **Split down**. Drag a
divider, or focus it and use arrow keys, to resize. Home/End move to its bounds;
Enter centers it. Panes retain at least 320×220 CSS pixels, with workspace
scrolling when needed. Each shell receives its own updated dimensions.

Click a terminal to focus it. Command+Option+arrows on macOS or
Control+Alt+arrows move from terminal input to the adjacent pane. Find and
dialog text fields retain their keyboard behavior. Only the focused terminal
receives keyboard input; all visible panes continue rendering.

Selecting a hidden sidebar session replaces the focused pane without closing
its shell. **Zoom pane** and **Restore panes** temporarily hide and restore
the other panes. Closing a session collapses its split while other sessions
keep their terminal state and connections. Layout changes preserve history
and per-session Find queries. Reloading restores nested splits, divider
proportions, the focused terminal, and zoom, including the arrangement behind a
zoomed pane. Smaller windows retain the arrangement with minimum pane sizes and
workspace scrolling. Hidden sessions remain accessible in the sidebar.

### Local workspace recovery

The [local example](https://github.com/vercel-labs/wterm/tree/main/examples/local)
uses a paired client/server protocol to resume the same shell after a brief
connection interruption. It preserves the existing page's terminal state and
replays only output the terminal has not parsed. Detached PTYs pause, retained
output stays bounded, and the server expires detached sessions after 30 seconds.
The browser retries for up to 25 seconds after detecting an interruption.

Input during reconnection is rejected visibly. Sequenced input is acknowledged
after the server hands it to the PTY; duplicate accepted messages are ignored.
The browser limits unacknowledged input to 64 KiB and 1,024 messages, and keeps
only message lengths for tracking. Reattachment confirms the accepted prefix
even if an acknowledgment was lost. If any input was not delivered, the workspace
reports it and asks you to check the command line before continuing. If recovery
fails, unconfirmed delivery remains visible. Input is never retried automatically.
An acknowledgment confirms PTY acceptance, not command execution or completion.
Reloading the page can restore saved terminal tabs and reconnect to their same
shells. The workspace stores exact output, resize and theme events, attachment
credentials, and input sequence counters in tab-scoped `sessionStorage`, separately
for each engine route. A fresh terminal replays that complete prefix before
reattaching; historical replies and clipboard requests are suppressed. Saved
output may contain sensitive text and echoed commands. Closing a session removes
its records, and closing the browser tab removes its session storage.

Pane layout is stored separately in the same tab and engine route. Only sessions
in the recovery registry can appear in the restored layout; closed sessions stay
closed. Missing, stale, or malformed layout records fall back to a single pane
with the first saved session selected. A layout storage failure shows a status
without disabling terminal replay or interrupting live input and output.

Replay records are capped at 1 MiB of output and 4,096 events per session, subject
to browser storage quotas. A limit or storage failure disables refresh recovery
with a visible status while the live terminal continues. Invalid or incomplete
records, an expired session, an unavailable output range, or a server restart
produce an explicit failure without starting a replacement shell. Reload must
reattach within the 30-second server grace period. Find queries are not restored;
normal navigation and duplicated tabs start independently.
This behavior belongs to the local example, not `WebSocketTransport` alone.

## WebMCP

Set `webmcp` to `true`, or to `{ name, description, readOnly }`, to let in-page
agents list, read, and type into the terminal through
[WebMCP](/webmcp). It is off by default and does nothing in browsers without a
WebMCP provider. Use `readOnly: true` for terminals agents should only watch.

## Output announcements

Opt in with `announceOutput` to announce terminal text changes politely while
input has focus. The option defaults to `false`; component prop changes apply
without restarting the terminal, and vanilla callers use
`term.setOutputAnnouncements(enabled)`. Announcements are batched and stop
when focus leaves input. Bursts are bounded to avoid overwhelming the speech
queue. See [Output announcements](/api-reference#output-announcements) for
capture limits, pause/resume behavior, and redraw semantics.

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

---
title: Core / Advanced
url: "https://wterm.dev/core"
docs_index: /llms.txt
lastUpdated: 2026-11-06
navTitle: "Core / Advanced"
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

`@wterm/core` provides the headless terminal engine and WebSocket transport. Use it when you need direct access to the WASM bridge for a custom renderer, headless testing, or a server-connected terminal without the DOM layer.

With `@wterm/dom`, terminal applications can enable mouse tracking (modes 1000, 1002, and 1003) in X10, UTF-8 (1005), SGR (1006), urxvt (1015), or SGR pixel (1016) encoding, plus focus reporting (mode 1004). UTF-8, SGR, urxvt, SGR pixel, and focus reports reach `onData`; raw X10 reports reach `onBinary` when provided. SGR pixel reports use 1-based CSS pixels relative to the visible grid. Mode 1003 reports unpressed pointer motion once per cell in cell formats and once per CSS pixel in 1016.

## Install

```bash
npm install @wterm/core
```

## WasmBridge

Low-level interface to the Zig/WASM terminal state machine. The bridge manages a virtual terminal grid in WASM memory — you write data in and read cells, cursor state, and scrollback out.

### Loading

```ts
import { WasmBridge } from "@wterm/core";

// Use the embedded WASM binary (default, zero-config)
const bridge = await WasmBridge.load();

// Or fetch from a URL (useful for CDN caching)
const bridge = await WasmBridge.load("/wterm.wasm");
```

When no URL is provided, the \~49 KB WASM binary is decoded from a base64 string inlined in the package. Pass a URL when you want to serve the binary separately for caching or CDN use. The grid grows as needed, up to 1024 columns and 512 rows.

See the full [WasmBridge](/api-reference#wasmbridge) reference for all methods, types, and scrollback APIs.

The built-in core answers DEC private-mode status queries through `getResponse()`. For example, `\x1b[?2026$p` receives `\x1b[?2026;2$y` when synchronized output is off and `\x1b[?2026;1$y` when it is on. It also reports supported mouse, focus, cursor, bracketed-paste, and alternate-screen modes; unrecognized modes return status `0`. Drain responses and send them back to a connected application. `WTerm` does this through `onData`.

The built-in core also answers primary device-attributes queries (`CSI c`, including `CSI 0 c`) with `\x1b[?1;2c`, identifying VT100 advanced-video support. It does not claim secondary or private device attributes.

Operating-status queries (`CSI 5 n`) receive `\x1b[0n` when the built-in core is ready. Cursor-position queries (`CSI 6 n`) receive `\x1b[row;colR`. These replies share the `getResponse()` queue with device-attribute replies and preserve query order; unsupported or malformed status requests receive no reply.

`getBellCount()` reads and clears pending BEL controls. A BEL used to terminate
an OSC sequence does not count. The DOM wrapper forwards the count through
`onBell`, leaving the alert behavior to the host.

### Example

Headless usage — load the bridge, write data, and read cells back:

```ts
import { WasmBridge } from "@wterm/core";

const bridge = await WasmBridge.load();
bridge.init(80, 24);

bridge.writeString("Hello, world!\r\n");
bridge.writeString("\x1b[1;31mRed bold text\x1b[0m");

for (let col = 0; col < 13; col++) {
  const cell = bridge.getCell(0, col);
  process.stdout.write(String.fromCodePoint(cell.char));
}
// → "Hello, world!"

const cursor = bridge.getCursor();
// → { row: 1, col: 13, visible: true }
```

Wide single-codepoint characters such as CJK, fullwidth forms, and emoji expose `width: 2` on the leading cell and `width: 0` on the continuation cell. Custom renderers should skip continuation cells.

The built-in core also implements the optional `getRowId(row)` and
`isRowContentDirty(row)` methods, which let the DOM renderer reuse rows that
only scrolled. Custom cores can omit them.

Character insertion (`ICH`) and deletion (`DCH`) shift exactly the requested number of columns from the cursor, up to the right edge. If an edit splits a wide character, its remaining cells become spaces with the current background color. With automatic wrapping disabled (`CSI ? 7 l`), a wide character that cannot fit at the right edge leaves the row unchanged. In a one-column terminal, wide characters are consumed as spaces with normal cursor and wrapping behavior.

Cores that preserve multi-codepoint graphemes expose the complete string as `chars`. Render `chars` when present and fall back to `String.fromCodePoint(char)` otherwise.

### DEC line drawing

The built-in core translates DEC Special Graphics sequences used by tmux and other TUIs into Unicode cells. For example:

```ts
bridge.writeString("\x1b(0lqqk\x1b(B"); // ┌──┐
```

`ESC (` through `ESC +` designate G0 through G3. The supported sets are ASCII (`B`), British (`A`, mapping `#` to `£`), and DEC Special Graphics (`0`). SI/SO select G0/G1; `ESC n` and `ESC o` select G2/G3. `ESC N` and `ESC O` use G2/G3 for the next printed character only. Unsupported designations are ignored.

Character-set state is preserved by cursor save/restore and restored when returning from the alternate screen with `CSI ? 1049 l`. Soft and full resets restore the built-in core's default ASCII sets. Mapping changes printable ASCII only; UTF-8 text keeps its original codepoints. Custom renderers receive the translated Unicode characters through `getCell()` and `getScrollbackCell()`.

### Optional terminal graphics

The `TerminalCore` contract has optional `getGraphicsState()` and
`getGraphicsImage()` methods. A graphics provider returns copied metadata and
tightly packed RGBA pixels for direct terminal images, along with pinned
placements in retained-row coordinates. Existing cores remain compatible when
these methods are absent. The built-in `WasmBridge` consumes unsupported 7-bit
Kitty APC payloads safely but does not decode or advertise image data; use the
Ghostty core for rendering Kitty PNG/RGB/RGBA output.

## WebSocketTransport

Connect to a PTY backend over WebSocket with automatic reconnection and bounded
send buffering. Defaults allow 1 MiB across the transport and socket buffers and
1,024 queued messages. `onBackpressure` reports when producers should pause;
`send()` throws `RangeError` before accepting a message that would exceed a
limit. Report that rejected input to the user. Explicit close discards unsent
data, and bytes already handed to the socket are never replayed.

See the full [WebSocketTransport](/api-reference#websockettransport) reference for all options, methods, and properties.

### Example

Connecting a terminal to a remote shell via WebSocket:

```ts
import { WTerm, WebSocketTransport } from "@wterm/dom";
import "@wterm/dom/css";

const term = new WTerm(document.getElementById("terminal"), {
  cols: 80,
  rows: 24,
});
await term.init();

const ws = new WebSocketTransport({
  url: "ws://localhost:8080/pty",
  onData: (data) => term.write(data),
  onOpen: () => console.log("connected"),
  onClose: () => console.log("disconnected"),
});

ws.connect();
term.onData = (data) => {
  try {
    ws.send(data);
  } catch (error) {
    console.error("Input was not sent", error); // Show this in your app's UI.
  }
};
```

For a full working example with a Node.js PTY server, see the [Local Shell example](https://github.com/vercel-labs/wterm/tree/main/examples/local).
It uses its own client/server protocol: binary PTY output plus JSON input,
resize, and consumption acknowledgments. A bounded output window pauses the
PTY when the browser falls behind, and parsing yields between batches. Small
PTY reads are collected into frames up to 16 KiB using a four-millisecond
timer, reducing WebSocket message overhead during output bursts. Full frames
can send immediately when credit is available; normal exit drains the final
batch. Collecting bytes remain subject to the same queue limits. The
workspace reports rejected input and connection failures. This protocol
requires both halves of the example; it is not built into `WebSocketTransport`.

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

---
title: Get Started
url: "https://wterm.dev/get-started"
docs_index: /llms.txt
lastUpdated: 2026-11-06
navTitle: "Get Started"
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

## Install

### React

```bash
npm install @wterm/dom @wterm/react
```

### Vue

```bash
npm install @wterm/dom @wterm/vue
```

### Svelte

```bash
npm install @wterm/dom @wterm/svelte svelte
```

### Vanilla JS

```bash
npm install @wterm/dom
```

The WASM binary is embedded in the package — no extra setup required.
It is decoded and compiled once per copy of `@wterm/core`, including concurrent
loads. Each terminal still gets its own instance and memory.

## Quick Start

### React

```tsx
import { Terminal } from "@wterm/react";
import "@wterm/react/css";

function App() {
  return <Terminal />;
}
```

### Vue

```vue
<script setup lang="ts">
import { Terminal } from "@wterm/vue";
import "@wterm/vue/css";
</script>

<template>
  <Terminal />
</template>
```

### Svelte

```svelte
<script lang="ts">
import { Terminal } from "@wterm/svelte";
import "@wterm/svelte/css";
</script>

<Terminal />
```

### Vanilla JS

```js
import { WTerm } from "@wterm/dom";
import "@wterm/dom/css";

const term = new WTerm(document.getElementById("terminal"));
await term.init();
```

## Serving WASM separately

By default the \~49 KB WASM binary is embedded in the package and imported when
the built-in core is first loaded. Bundlers with code splitting emit it in a
separate JavaScript chunk, so deploy all emitted application assets. Ghostty and
custom `wasmUrl` loads do not request that chunk. To serve it as a separate static file instead (for caching or CDN), copy it from the package and pass `wasmUrl`:

```bash
cp node_modules/@wterm/core/wasm/wterm.wasm public/wterm.wasm
```

```tsx
<Terminal wasmUrl="/wterm.wasm" />
```

Loads of the same resolved URL share the download and compiled module. The
built-in loader retains up to four recently used URLs per copy of `@wterm/core`
in a page or worker, separately from its embedded module. Failed downloads and
compilations can be retried. Use versioned or hashed URLs when updating the
binary; cached modules remain until eviction or page/worker reload.

Serve `Content-Type: application/wasm` to compile during download in supported
browsers. Other MIME types, unavailable streaming, or streaming failures fall
back to buffered compilation without fetching the file again.

## Building the WASM Binary

Requires [Zig](https://ziglang.org/) 0.16.0

```bash
zig build -Doptimize=ReleaseSmall
```

The build writes `zig-out/bin/wterm.wasm` and `web/wterm.wasm`. The packages ship
`packages/@wterm/core/wasm/wterm.wasm`. Copy the build there, then run
`pnpm --filter @wterm/core build` to regenerate the embedded binary that loads
when `wasmUrl` is not set.

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

---
title: Ghostty Core
url: "https://wterm.dev/ghostty"
docs_index: /llms.txt
lastUpdated: 2026-11-08
navTitle: "Ghostty Core"
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

The `@wterm/ghostty` package provides a full-featured terminal emulation core powered by [libghostty](https://ghostty.org) built directly from upstream source. It implements the same `TerminalCore` interface as wterm's built-in Zig core, so it's a drop-in replacement. When your bundler supports code splitting, Ghostty-backed terminals do not download the built-in core's embedded binary.

## Why use it?

wterm ships with a lightweight built-in core (\~49 KB WASM) that covers basic VT100/VT220/xterm escape sequences. For apps that need comprehensive terminal emulation — full Unicode grapheme clusters, all SGR attributes, terminal modes, and more — `@wterm/ghostty` provides all of that via Ghostty's battle-tested VT parser.

Ghostty's native OSC 8 metadata is preserved through the active viewport, scrollback, and reflow. The DOM renderer resolves it into safe HTTP and HTTPS anchors without exposing Ghostty's page-local numeric IDs.

Ghostty allocates terminal pages as needed, reducing initial memory use for idle terminals and alternate screens. The configured history budget still controls retained output. Freed pages can be reused, but WebAssembly linear memory does not shrink after clearing history or resetting a screen.

`getColorOverrides()` exposes application-requested default foreground,
background, and cursor colors from OSC 10/11/12. The DOM renderer applies them
to live cells, retained history, the terminal background, and cursor shapes.
OSC 110/111/112 restores the current CSS theme without rewriting host theme
variables. Explicit SGR colors remain unchanged. Serve the current WASM binary;
older binaries return an empty snapshot and retain their CSS defaults.

Ghostty preserves single, double, curly, dotted, and dashed underlines through
`CellData.underlineStyle`, with resolved colors in `underlineRgb`. The DOM
renderer displays them in the viewport and scrollback, including after reflow.
SGR `4:1` through `4:5` select the styles; `4` selects single and `21` selects
double. SGR `58` sets an indexed or RGB underline color, `59` restores the
foreground color, and `24` or `4:0` removes the underline. SGR `0` resets both.
Strikethrough stays solid and follows the text color. Apps serving an older
WASM file retain single underlines; serve the binary shipped with the package
to enable the additional styles and colors.

Ghostty's cursor shape and blink state also reach the DOM renderer. Applications
can switch between block, bar, and underline cursors; the wrapper's optional
`cursorBlink` setting can force blinking on or off. See
[cursor appearance](/configuration#cursor-appearance) for the shared behavior.

Shells and applications can set the window title with OSC 0 or OSC 2. The
Ghostty core forwards the latest complete title to `WTerm`'s `onTitle`
callback, including an empty title that clears it. As with the built-in core,
the callback runs as output is written, including while painting is paused.
Several changes within a parsed chunk may coalesce to the latest complete
title. Ghostty ignores titles longer than 255 bytes.

BEL controls reach the shared `onBell(count)` callback as output is written.
The BEL that terminates an OSC title does not count as a bell.

With mouse tracking enabled, `mouseEncoding()` exposes Ghostty's active wire
format. The DOM layer sends UTF-8 (1005), SGR (1006), urxvt (1015), and SGR
pixel (1016) reports through `onData`; X10 reports use `onBinary` when provided.
Mode 1003 sends unpressed pointer motion once per cell for cell formats and
once per CSS pixel for 1016. Pixel coordinates are 1-based and relative to the
visible grid.

Ghostty-backed terminals also render direct Kitty Graphics Protocol PNG, RGB,
and RGBA data through the optional graphics contract. The DOM layer draws
bounded, transient canvas overlays for pinned placements and follows scrollback,
resize, and primary/alternate screen changes. Non-direct media is rejected
before file, shared-memory, or URL access; Sixel, iTerm2/OSC 1337, animation, virtual
Unicode placements, and persistence are outside the supported boundary. The
common auto-sized Kitty placement also reserves its rendered height in the
visual DOM flow, keeping a following shell prompt below the image.

## Install

```bash
npm install @wterm/ghostty
```

## Usage

Load the Ghostty core and pass it to `WTerm` via the `core` option. Everything else stays the same.

### Vanilla JS

```ts
import { WTerm } from "@wterm/dom";
import { GhosttyCore } from "@wterm/ghostty";
import "@wterm/dom/css";

const core = await GhosttyCore.load();
const term = new WTerm(document.getElementById("terminal"), {
  core,
  onTitle(title) {
    document.title = title || "Terminal";
  },
});
await term.init();
```

### React

```tsx
import { Terminal } from "@wterm/react";
import { GhosttyCore } from "@wterm/ghostty";
import "@wterm/dom/css";

const core = await GhosttyCore.load();

function App() {
  return <Terminal core={core} />;
}
```

### Vue

```vue
<script setup lang="ts">
import { Terminal } from "@wterm/vue";
import { GhosttyCore } from "@wterm/ghostty";

const core = await GhosttyCore.load();
</script>

<template>
  <Terminal :core="core" />
</template>
```

### Svelte

```svelte
<script lang="ts">
import { onMount } from "svelte";
import { Terminal } from "@wterm/svelte";
import { GhosttyCore } from "@wterm/ghostty";

let core: GhosttyCore | undefined;

onMount(() => {
  void GhosttyCore.load().then((loaded) => {
    core = loaded;
  });
});
</script>

{#if core}
  <Terminal {core} />
{/if}
```

For large Kitty images, pass display limits to the terminal wrapper. The
limits are CSS pixels, preserve the image aspect ratio, and only scale images
down:

```ts
const term = new WTerm(document.getElementById("terminal"), {
  core,
  maxImageWidth: 640,
  maxImageHeight: 480,
});
```

The equivalent React, Vue, and Svelte props are `maxImageWidth` and
`maxImageHeight`.

`WTerm` answers Kitty's pixel geometry queries from the browser viewport, so
direct image commands such as `kitten icat --transfer-mode=stream image.png`
can negotiate their display size before transferring image data. The
[local shell example](https://github.com/vercel-labs/wterm/tree/main/examples/local)
forwards the browser's pixel dimensions to its PTY, so `kitten icat --detect-support`
also works from that shell.

## Options

`GhosttyCore.load()` accepts an optional options object:

<table>
  <thead>
    <tr>
      <th>
        Option
      </th>

      <th>
        Type
      </th>

      <th>
        Description
      </th>
    </tr>
  </thead>

  <tbody>
    <tr>
      <td>
        <code>
          wasmPath
        </code>
      </td>

      <td>
        <code>
          string
        </code>
      </td>

      <td>
        Custom path to the ghostty-vt WASM binary. By default it resolves to the
        committed binary inside the package.
      </td>
    </tr>

    <tr>
      <td>
        <code>
          scrollbackLines
        </code>
      </td>

      <td>
        <code>
          number
        </code>
      </td>

      <td>
        Lines of history to keep (default: 1000). Ghostty stores history in
        pages, so up to a few pages of extra rows stay allocated but are not
        reported. History memory is capped at twice what plain text needs at
        the same width, so output dense with distinct colors, graphemes, or
        hyperlinks can keep fewer lines.
      </td>
    </tr>

    <tr>
      <td>
        <code>
          scrollbackLimit
        </code>
      </td>

      <td>
        <code>
          number
        </code>
      </td>

      <td>
        Scrollback budget in bytes instead of lines. Ignored when
        <code>scrollbackLines</code> is set; the retained row count depends on
        the terminal width.
      </td>
    </tr>

    <tr>
      <td>
        <code>
          foregroundColor
        </code>
      </td>

      <td>
        <code>
          string
        </code>
      </td>

      <td>
        Foreground reported by OSC 10 in <code>#RRGGBB</code> format. Defaults
        to <code>#d4d4d4</code>.
      </td>
    </tr>

    <tr>
      <td>
        <code>
          backgroundColor
        </code>
      </td>

      <td>
        <code>
          string
        </code>
      </td>

      <td>
        Background reported by OSC 11 in <code>#RRGGBB</code> format. Defaults
        to <code>#1e1e1e</code>.
      </td>
    </tr>

    <tr>
      <td>
        <code>imageStorageLimit</code>
      </td>

      <td>
        <code>number</code>
      </td>

      <td>
        Maximum decoded Kitty image bytes per screen (default: 32 MiB). Set to

        <code>0</code>

        to disable graphics. One image is capped at

        <code>MAX\_IMAGE\_BYTES</code>

        (32 MiB).
      </td>
    </tr>
  </tbody>
</table>

Pass colors matching your CSS theme so terminal applications receive the
colors they are actually rendered with:

```ts
const core = await GhosttyCore.load({
  foregroundColor: "#ededed",
  backgroundColor: "#0a0a0a",
  imageStorageLimit: 32 * 1024 * 1024,
});
```

The core rejects malformed, oversized, and non-direct images before they can
access a path or shared-memory segment. `getResourceState()` reports graphics
bytes used/capacity, image and placement counts, rejection and eviction
counters, and saturation. `GhosttyCore.dispose()` is idempotent and releases
WASM resources when the application owns the core; `WTerm.destroy()` never
disposes a caller-supplied core.

The exported `MAX_IMAGE_BYTES` constant documents the hard per-image decoded
byte cap. A larger `imageStorageLimit` is still useful for storing several
smaller images. Each screen also retains at most 4,096 image descriptors and
4,096 placements; additional unique records fail closed so tiny-image churn
cannot grow WASM metadata without bound.
The DOM overlay independently caps visible canvas backing stores at 32 MiB and
bounds each destination canvas to the terminal pixel area; placements that
exceed those browser limits are skipped.

## Updating a running theme

Use [`wt.setThemeColors(colors)`](/themes#updating-host-colors) to update browser
colors and Ghostty defaults together without restarting the terminal. Headless
hosts can call `core.setThemeColors(colors)` with the same `TerminalThemeColors`
object. Application overrides and partial parser input are preserved; resets
use the latest host defaults. Use the updated bundled WASM asset for this API.

## Bundlers

Loads of the same resolved WASM URL share the download and compiled module,
including concurrent calls. Each core still has its own instance and memory.
The loader retains up to four recently used URLs per copy of the package in a
page or worker; failed downloads and compilations can be retried. Use versioned
or hashed URLs for new binaries because cached modules are not revalidated on
each load.

Serve the binary with `Content-Type: application/wasm` to compile while it
downloads in supported browsers. Other MIME types or unavailable or failing
streaming support fall back to buffered compilation without another fetch.

The WASM binary is fetched at runtime rather than inlined, so the default has to resolve to a URL your app serves. `GhosttyCore.load()` resolves it with `new URL("../wasm/ghostty-vt.wasm", import.meta.url)`. Bundlers that implement that asset pattern emit the binary and rewrite the URL. Ones that do not leave `import.meta.url` pointing at the machine that built the bundle, and `load()` throws an error saying so.

<table>
  <thead>
    <tr>
      <th>
        Bundler
      </th>

      <th>
        Default <code>GhosttyCore.load()</code>
      </th>

      <th>
        Verified
      </th>
    </tr>
  </thead>

  <tbody>
    <tr>
      <td>
        Vite (dev and build)
      </td>

      <td>
        Works, emits a hashed asset.
      </td>

      <td>
        Yes
      </td>
    </tr>

    <tr>
      <td>
        Bun dev server
      </td>

      <td>
        Fails, pass <code>wasmPath</code>.
      </td>

      <td>
        Yes
      </td>
    </tr>

    <tr>
      <td>
        Others
      </td>

      <td>
        Untested. Use <code>wasmPath</code> if the default throws.
      </td>

      <td>
        No
      </td>
    </tr>
  </tbody>
</table>

When the default cannot work, serve the binary yourself and point at it:

```bash
cp node_modules/@wterm/ghostty/wasm/ghostty-vt.wasm public/ghostty-vt.wasm
```

```ts
const core = await GhosttyCore.load({ wasmPath: "/ghostty-vt.wasm" });
```

The binary is also addressable as `@wterm/ghostty/ghostty-vt.wasm`, so a bundler with a URL import can take it directly.

## How it works

`@wterm/ghostty` builds libghostty directly from [ghostty-org/ghostty](https://github.com/ghostty-org/ghostty) source — no third-party npm packages or pre-built binaries from other projects. The architecture:

1. **Zig package dependency**: ghostty v1.3.1 is declared as a URL dependency in `zig/build.zig.zon`. Zig's package manager fetches it automatically.
2. **WASM compatibility patches**: ghostty's `Terminal` uses `posix.mmap` and Mach VM allocators internally, which don't exist on `wasm32-freestanding`. The build script applies small, targeted patches to replace these with `std.heap.wasm_allocator`, expose discarded scrollback rows, forward the per-screen Kitty image limit, bound and compact Kitty image/placement metadata, release tracked page pins during eviction, and make direct PNG decoding work without POSIX time. The patches touch `Terminal.zig`, `kitty/graphics_image.zig`, `kitty/graphics_storage.zig`, `kitty/graphics_exec.zig`, `kitty/graphics_unicode.zig`, `page.zig`, and `PageList.zig`; they also configure Wuffs with the freestanding compatibility headers and source flags in `zig/build.zig`.
3. **Thin WASM export layer**: `zig/src/wasm_api.zig` imports ghostty's `Terminal` and `RenderState` APIs and exports functions to JavaScript.
4. **Committed WASM binary**: The built `wasm/ghostty-vt.wasm` is checked into the repo so consumers never need Zig installed.
5. **TypeScript bindings**: `wasm-bindings.ts` loads the WASM module and provides typed accessors for the exported functions.
6. **TerminalCore adapter**: `ghostty-core.ts` implements the `TerminalCore` interface by calling the WASM bindings, converting ghostty's pre-resolved 24-bit RGB colors to wterm's `CellData` format via the `fgRgb`/`bgRgb` fields.

The `TerminalCore` interface means the DOM renderer, input handler, and framework bindings don't need to know which core they're talking to.

Ghostty enables grapheme clustering and returns combining sequences and ZWJ emoji through `CellData.chars`. The same complete strings are available from active cells and scrollback.

`CellData.spacerHead` identifies empty right-edge filler before a wrapped wide glyph. [Terminal search](/api-reference#terminal-search) omits that filler and width-zero continuations, preserving real spaces and mapping grapheme matches to complete cells.

[Selection and Copy](/api-reference#selection-and-copy) uses the same metadata to join soft wraps, omit filler cells, and preserve whole graphemes in copied text while retaining explicit newlines.

Ghostty also supplies [tracked cell positions](/api-reference#tracked-cell-positions) through `trackPosition({ row, col })`. WTerm uses these to preserve native selections through output scrolling and reflow, clearing selections if their text changes or their positions become invalid. Serve the current WASM binary to enable tracking; older binaries return `null`.

The adapter also reports how many rows Ghostty has discarded from the oldest end of its page-backed history. WTerm uses that count to keep retained rows anchored when the scrollback budget rolls over.

For headless row reads, `getRowMetadata(row)` and
`getScrollbackRowMetadata(offset)` expose native `wrapsToNext` and
`continuesPrevious` flags. They distinguish automatic wrapping from explicit
newlines across the live screen and retained history, including after reflow.
Live row zero is the top of the screen; history offset zero is the newest
retained row. Read metadata again after output, resize, or screen changes.
Invalid positions, disposed cores, and older WASM binaries return `null`.
See [Row Metadata](/api-reference#row-metadata) for the optional core contract.

Terminal applications can query the Ghostty core's foreground and background colors with OSC 10 and OSC 11. Attached to `WTerm`, the core answers with the terminal's CSS theme colors (see [Color queries](/themes#color-queries)); headless, it starts with its configured colors. It tracks later OSC color changes and resets either way.

Ghostty also exposes synchronized-output mode (`CSI ? 2026 h/l`) and its generation to the DOM renderer, so updates inside a synchronized block paint as one final frame.

Kitty keyboard query and negotiation operate on Ghostty's native active-screen state. Primary and alternate screens have independent flag stacks, DECSTR preserves them, and RIS clears them. The DOM renderer reads those flags directly instead of maintaining a TypeScript shadow state.

The bundled binary includes upstream's [WASM page-initialization fix](https://github.com/ghostty-org/ghostty/commit/420de124f04aa322bf250098cc62d7195db94bfd). New and replacement terminal pages are cleared before use, including release builds where the allocator may return memory used by earlier terminal state. If you serve the binary from a public directory, copy the updated package asset when upgrading.

## Application clipboard requests

Ghostty exposes OSC 52 writes through `getClipboardWrite()` and WTerm's
`onClipboardWrite(text)` callback. The host chooses whether to accept the text;
no browser clipboard access is automatic. Reads are unsupported. Requests are
limited to 65,536 decoded UTF-8 bytes, with bounded encoded accumulation and
only one pending write. See [Clipboard requests](/configuration#clipboard-requests)
for selectors, delivery behavior, and browser permission handling. Serve the
updated bundled WASM to enable this optional effect.

## Rebuilding the WASM

Only needed by maintainers. Requires [Zig 0.15.2](https://ziglang.org/download/),
Bash, and Python 3. This is separate from the built-in core's Zig 0.16 toolchain.

```bash
pnpm --filter @wterm/ghostty rebuild-wasm
```

Each build verifies the exact compiler version and upstream content hash,
patches a fresh dependency tree, checks patch idempotence, and compiles in
isolated caches under `/tmp`. Temporary files are removed on exit; the shared
Zig cache is never read or patched. Set `WTERM_GHOSTTY_ZIG=/path/to/zig` to
select a compiler explicitly.

Zig 0.15.2 cannot link its native build runner on macOS 26. Use the Linux
container build when the host toolchain cannot build:

```bash
pnpm --filter @wterm/ghostty rebuild-wasm:docker
```

Docker and CI download the pinned compiler and verify its SHA-256 before
extraction. Check the committed artifact without replacing it with:

```bash
pnpm --filter @wterm/ghostty check-wasm
# Linux container with a read-only checkout:
pnpm --filter @wterm/ghostty check-wasm:docker
```

CI requires a byte-for-byte match on every PR and push to `main`. If it reports
drift, rebuild and commit `packages/@wterm/ghostty/wasm/ghostty-vt.wasm` alongside
the source changes. Consumers do not need Zig or Docker.

## Comparison

<table>
  <thead>
    <tr>
      <th />

      <th>
        Built-in (default)
      </th>

      <th>
        <code>
          @wterm/ghostty
        </code>
      </th>
    </tr>
  </thead>

  <tbody>
    <tr>
      <td>
        WASM binary size
      </td>

      <td>
        \~49 KB WASM (inlined)
      </td>

      <td>
        \~580 KB WASM (fetched)
      </td>
    </tr>

    <tr>
      <td>
        VT compliance
      </td>

      <td>
        Basic VT100/VT220/xterm
      </td>

      <td>
        Comprehensive
      </td>
    </tr>

    <tr>
      <td>
        Unicode
      </td>

      <td>
        Single codepoints with wide-cell support
      </td>

      <td>
        Full grapheme clusters
      </td>
    </tr>

    <tr>
      <td>
        Color model
      </td>

      <td>
        256-color palette indices
      </td>

      <td>
        Pre-resolved 24-bit RGB
      </td>
    </tr>

    <tr>
      <td>
        Dependencies
      </td>

      <td>
        None
      </td>

      <td>
        None (WASM built from source)
      </td>
    </tr>

    <tr>
      <td>
        Setup
      </td>

      <td>
        Zero-config
      </td>

      <td>
        Requires <code>@wterm/ghostty</code> install
      </td>
    </tr>
  </tbody>
</table>

## Shell integration

OSC 7 directory reports reach `onWorkingDirectory(uri)`, or
`GhosttyCore.getWorkingDirectory()` for headless consumers. Empty strings clear
the value; `null` from the core means unchanged. See
[Working directories](/configuration#working-directories) for URI handling,
resource limits, shell setup, and workspace display behavior.

With Ghostty and a shell emitting OSC 133, `onShellIntegration` receives the
latest prompt, input, running, or completion state and the last reported exit
code. Delivery continues while painting is paused; markers in one parser chunk
coalesce into current state. See [Shell integration](/configuration#shell-integration)
for the contract, shell setup, and workspace indicators.

Use the underlying `WTerm` instance’s `scrollToPrompt(-1)` and
`scrollToPrompt(1)` methods to navigate previous and next prompts in retained
history. See [Shell integration](/configuration#shell-integration) for viewport
behavior, unsupported cores, and inactive states.

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

---
title: A terminal emulator for the web
url: "https://wterm.dev/"
docs_index: /llms.txt
lastUpdated: 2026-11-04
navTitle: "Introduction"
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

<div className="mb-12">
  <div className="mb-8 hidden text-sm leading-relaxed text-neutral-600 dark:text-neutral-400 md:block">
    wterm <span className="text-neutral-500 dark:text-neutral-400">("dub-term")</span> renders to the DOM — native text selection, copy/paste, find, and accessibility come for free. Try the Ghostty-powered terminal below.
  </div>

  <div className="mb-6 md:mb-0" />

  <div className="mt-4 text-sm leading-relaxed text-neutral-600 dark:text-neutral-400 md:hidden">
    wterm <span className="text-neutral-500 dark:text-neutral-400">("dub-term")</span> renders to the DOM — native text selection, copy/paste, find, and accessibility come for free. Try the Ghostty-powered terminal above.
  </div>

  <div className="mt-3 text-xs text-neutral-500 dark:text-neutral-400">
    The interactive example uses the <a href="/ghostty" className="underline underline-offset-2 hover:text-neutral-700 dark:hover:text-neutral-300">Ghostty core</a> and <a href="https://github.com/vercel-labs/just-bash" className="underline underline-offset-2 hover:text-neutral-700 dark:hover:text-neutral-300">just-bash</a>. See also: <a href="https://github.com/vercel-labs/wterm/tree/main/examples/ssh" className="underline underline-offset-2 hover:text-neutral-700 dark:hover:text-neutral-300">SSH</a>, <a href="https://github.com/vercel-labs/wterm/tree/main/examples/local" className="underline underline-offset-2 hover:text-neutral-700 dark:hover:text-neutral-300">local</a>.
  </div>
</div>

## Features

- **Pluggable cores** — a lightweight Zig/WASM core or Ghostty VT emulation for richer terminal compatibility
- **DOM rendering** — native text selection, clipboard, browser find, screen reader support
- **Native hyperlinks** — OSC 8 links remain attached to their exact cells through viewport and scrollback, with safe HTTP(S) anchors
- **Dirty-row tracking** — only touched rows are re-rendered each frame via `requestAnimationFrame`
- **Synchronized output** — mode 2026 blocks paint atomically with a bounded recovery deadline
- **Themes** — CSS custom properties with built-in Default, Solarized Dark, Monokai, and Light themes
- **Alternate screen buffer** — `vim`, `less`, `htop` work correctly
- **Scrollback history** — configurable ring buffer
- **Wide Unicode cells** — CJK, fullwidth, and emoji codepoints stay aligned during cursor-addressed redraws
- **IME composition** — tentative text appears at the cursor; only committed text reaches the application
- **24-bit color** — full RGB SGR support
- **Framework bindings** — React, Vue 3, and Svelte components
- **Auto-resize** — `ResizeObserver`-based terminal resizing
- **WebSocket transport** — connect to a PTY backend with reconnection

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

---
title: Just Bash
url: "https://wterm.dev/just-bash"
docs_index: /llms.txt
lastUpdated: 2026-11-04
navTitle: "Just Bash"
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

Shell adapter for wterm, powered by [just-bash](https://github.com/vercel-labs/just-bash). Provides line editing, tab completion, command history, and a colored prompt — all running in the browser with no backend.

## Install

```bash
npm install @wterm/just-bash just-bash
```

`just-bash` 3 is a peer dependency.

## Quick Start

```tsx
import { useCallback, useRef } from "react";
import { Terminal, useTerminal } from "@wterm/react";
import { BashShell } from "@wterm/just-bash";
import "@wterm/react/css";

function App() {
  const { ref, write } = useTerminal();
  const shellRef = useRef<BashShell | null>(null);

  const handleReady = useCallback(() => {
    if (shellRef.current) return;
    const shell = new BashShell({
      files: { "/home/user/hello.txt": "Hello, world!\n" },
      greeting: "Welcome to wterm!",
    });
    shellRef.current = shell;
    shell.attach(write);
  }, [write]);

  const handleData = useCallback((data: string) => {
    shellRef.current?.handleInput(data);
  }, []);

  return (
    <Terminal
      ref={ref}
      onReady={handleReady}
      onData={handleData}
    />
  );
}
```

Use a ref to hold the `BashShell` instance so it's accessible from both the `onReady` and `onData` callbacks.

Each submitted command runs once. After it completes, the shell automatically updates its working directory and prompt from the command's final directory, including when a command exits with a nonzero status.

## Options

<table>
  <thead>
    <tr>
      <th>
        Option
      </th>

      <th>
        Type
      </th>

      <th>
        Default
      </th>

      <th>
        Description
      </th>
    </tr>
  </thead>

  <tbody>
    <tr>
      <td>
        <code>files</code>
      </td>

      <td>
        <code />
      </td>

      <td>
        <code />
      </td>

      <td>
        Virtual filesystem (keys are absolute paths, values are file contents)
      </td>
    </tr>

    <tr>
      <td>
        <code>env</code>
      </td>

      <td>
        <code />
      </td>

      <td>
        <code />
      </td>

      <td>
        Environment variables
      </td>
    </tr>

    <tr>
      <td>
        <code>cwd</code>
      </td>

      <td>
        <code>string</code>
      </td>

      <td>
        <code>"/home/user"</code>
      </td>

      <td>
        Initial working directory
      </td>
    </tr>

    <tr>
      <td>
        <code>greeting</code>
      </td>

      <td>
        <code>string | string\[]</code>
      </td>

      <td>
        —
      </td>

      <td>
        Lines printed when the shell attaches
      </td>
    </tr>

    <tr>
      <td>
        <code>prompt</code>
      </td>

      <td>
        <code>(cwd: string) => string</code>
      </td>

      <td>
        colored

        <code>user\@wterm:\~$</code>
      </td>

      <td>
        Custom prompt function — receives the current working directory
      </td>
    </tr>

    <tr>
      <td>
        <code>network</code>
      </td>

      <td>
        <code>NetworkConfig</code>
      </td>

      <td>
        —
      </td>

      <td>
        Network access configuration (see

        <a href="#network-access">Network Access</a>

        )
      </td>
    </tr>
  </tbody>
</table>

## Methods

<table>
  <thead>
    <tr>
      <th>
        Method
      </th>

      <th>
        Description
      </th>
    </tr>
  </thead>

  <tbody>
    <tr>
      <td>
        <code />
      </td>

      <td>
        Connect to a terminal write function. Loads just-bash, prints the greeting, and displays the initial prompt.
      </td>
    </tr>

    <tr>
      <td>
        <code />
      </td>

      <td>
        Process terminal input (keystrokes, paste). Call this from the terminal's

        <code>onData</code>

        callback.
      </td>
    </tr>
  </tbody>
</table>

## Properties

<table>
  <thead>
    <tr>
      <th>
        Property
      </th>

      <th>
        Type
      </th>

      <th>
        Description
      </th>
    </tr>
  </thead>

  <tbody>
    <tr>
      <td>
        <code>cwd</code>
      </td>

      <td>
        <code>string</code>
      </td>

      <td>
        Current working directory (updates after

        <code>cd</code>

        )
      </td>
    </tr>

    <tr>
      <td>
        <code>bash</code>
      </td>

      <td>
        <code>Bash | null</code>
      </td>

      <td>
        Underlying just-bash instance (

        <code>null</code>

        until

        <code>attach</code>

        completes)
      </td>
    </tr>
  </tbody>
</table>

## Virtual Filesystem

The `files` option populates an in-memory filesystem. Keys are absolute paths, values are file contents:

```ts
const shell = new BashShell({
  files: {
    "/home/user/README.md": "# My Project\n\nHello, world!\n",
    "/home/user/src/main.zig": 'const std = @import("std");\n',
    "/home/user/data/config.json": '{ "key": "value" }\n',
  },
});
```

Directories are created implicitly from file paths. Commands like `ls`, `cat`, `cd`, and `pwd` work against this virtual filesystem.

## Network Access

By default, the shell runs fully offline. To enable network access (for commands like `curl` or `fetch`), pass the `network` option from `just-bash`:

```ts
const shell = new BashShell({
  network: {
    dangerouslyAllowFullInternetAccess: true,
  },
});
```

The `NetworkConfig` type is re-exported from `just-bash`. See the [just-bash documentation](https://github.com/vercel-labs/just-bash) for all available network options.

## Keyboard Shortcuts

The command line accepts pasted Unicode text and treats emoji and combining sequences as whole characters when moving or deleting. Cursor movement uses terminal columns, keeping wide characters aligned.

<table>
  <thead>
    <tr>
      <th>
        Key
      </th>

      <th>
        Action
      </th>
    </tr>
  </thead>

  <tbody>
    <tr>
      <td>
        <code>Tab</code>
      </td>

      <td>
        Autocomplete files and commands at the cursor, including when editing earlier words in a command
      </td>
    </tr>

    <tr>
      <td>
        <code>Up</code>

        /

        <code>Down</code>
      </td>

      <td>
        Navigate command history; returning to the newest entry restores your unfinished command and cursor position
      </td>
    </tr>

    <tr>
      <td>
        <code>Ctrl+R</code>
      </td>

      <td>
        Search command history from newest to oldest as you type; press again to find an older match. Enter runs the match, Escape accepts it for editing, and Ctrl+G restores your unfinished command.
      </td>
    </tr>

    <tr>
      <td>
        <code>Ctrl+S</code>

        during history search
      </td>

      <td>
        Find a newer matching command. Typing continues the search toward newer commands until you press Ctrl+R again.
      </td>
    </tr>

    <tr>
      <td>
        <code>Left</code>

        /

        <code>Right</code>
      </td>

      <td>
        Move cursor within the current line
      </td>
    </tr>

    <tr>
      <td>
        <code>Backspace</code>

        /

        <code>Delete</code>
      </td>

      <td>
        Remove the character before / at the cursor
      </td>
    </tr>

    <tr>
      <td>
        <code>Option/Alt+Left</code>

        /

        <code>Option/Alt+Right</code>

        ,

        <code>Ctrl+Left</code>

        /

        <code>Ctrl+Right</code>

        , or

        <code>Alt+B</code>

        /

        <code>Alt+F</code>
      </td>

      <td>
        Move the cursor by word
      </td>
    </tr>

    <tr>
      <td>
        <code>Option/Alt+Backspace</code>

        /

        <code>Ctrl+W</code>
      </td>

      <td>
        Remove the word before the cursor
      </td>
    </tr>

    <tr>
      <td>
        <code>Home</code>

        /

        <code>Ctrl+A</code>
      </td>

      <td>
        Jump to start of line
      </td>
    </tr>

    <tr>
      <td>
        <code>End</code>

        /

        <code>Ctrl+E</code>
      </td>

      <td>
        Jump to end of line
      </td>
    </tr>

    <tr>
      <td>
        <code>Ctrl+U</code>

        /

        <code>Command+Backspace</code>

        (macOS)
      </td>

      <td>
        Remove text before the cursor
      </td>
    </tr>

    <tr>
      <td>
        <code>Ctrl+K</code>
      </td>

      <td>
        Remove text after the cursor
      </td>
    </tr>

    <tr>
      <td>
        <code>Ctrl+Y</code>
      </td>

      <td>
        Restore text removed with Option/Alt+Backspace, Ctrl+W, Ctrl+U, or Ctrl+K; consecutive erasures restore together
      </td>
    </tr>

    <tr>
      <td>
        <code>Ctrl+C</code>
      </td>

      <td>
        Cancel current input or interrupt a running command when it reaches a cancellation point
      </td>
    </tr>

    <tr>
      <td>
        <code>Ctrl+L</code>
      </td>

      <td>
        Clear screen
      </td>
    </tr>

    <tr>
      <td>
        <code>\\</code>

        at end of line
      </td>

      <td>
        Line continuation (multi-line input)
      </td>
    </tr>
  </tbody>
</table>

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

---
title: Markdown
url: "https://wterm.dev/markdown"
docs_index: /llms.txt
lastUpdated: 2026-11-04
navTitle: "Markdown"
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

Streaming Markdown-to-ANSI renderer for terminals. Designed for rendering LLM output in real time — push text chunks as they arrive and get styled terminal output back.

## Install

```bash
npm install @wterm/markdown
```

## Quick Start

### Vanilla JS

```js
import { WTerm } from "@wterm/dom";
import { MarkdownRenderer } from "@wterm/markdown";
import "@wterm/dom/css";

const term = new WTerm(document.getElementById("terminal"));
await term.init();

const md = new MarkdownRenderer();
const response = await fetch("/api/chat", { method: "POST" });
const reader = response.body.getReader();
const decoder = new TextDecoder();

while (true) {
  const { done, value } = await reader.read();
  if (done) break;
  const rendered = md.push(decoder.decode(value));
  if (rendered) term.write(rendered);
}

term.write(md.flush());
```

### React

```tsx
import { useCallback, useRef } from "react";
import { Terminal, useTerminal } from "@wterm/react";
import { MarkdownRenderer } from "@wterm/markdown";
import "@wterm/react/css";

function App() {
  const { ref, write } = useTerminal();
  const mdRef = useRef(new MarkdownRenderer());

  const handleReady = useCallback(async () => {
    const response = await fetch("/api/chat", { method: "POST" });
    const reader = response.body!.getReader();
    const decoder = new TextDecoder();

    while (true) {
      const { done, value } = await reader.read();
      if (done) break;
      const rendered = mdRef.current.push(decoder.decode(value));
      if (rendered) write(rendered);
    }

    write(mdRef.current.flush());
  }, [write]);

  return <Terminal ref={ref} onReady={handleReady} />;
}
```

## Options

<table>
  <thead>
    <tr>
      <th>
        Option
      </th>

      <th>
        Type
      </th>

      <th>
        Default
      </th>

      <th>
        Description
      </th>
    </tr>
  </thead>

  <tbody>
    <tr>
      <td>
        <code>width</code>
      </td>

      <td>
        <code>number</code>
      </td>

      <td>
        <code>80</code>
      </td>

      <td>
        Terminal width in columns (used for horizontal rules)
      </td>
    </tr>
  </tbody>
</table>

## Methods

<table>
  <thead>
    <tr>
      <th>
        Method
      </th>

      <th>
        Description
      </th>
    </tr>
  </thead>

  <tbody>
    <tr>
      <td>
        <code>push(delta: string): string</code>
      </td>

      <td>
        Feed a chunk of Markdown text. Returns rendered ANSI output for any complete lines. Buffers incomplete lines internally.
      </td>
    </tr>

    <tr>
      <td>
        <code>flush(): string</code>
      </td>

      <td>
        Flush remaining buffered content. Call this when the stream ends to render any trailing text and close open code blocks.
      </td>
    </tr>
  </tbody>
</table>

## Supported Syntax

<table>
  <thead>
    <tr>
      <th>
        Syntax
      </th>

      <th>
        Rendering
      </th>
    </tr>
  </thead>

  <tbody>
    <tr>
      <td>
        <code># Heading</code>

        through

        <code>### Heading</code>
      </td>

      <td>
        Bold, bright white for h1–h2; bold for h3+
      </td>
    </tr>

    <tr>
      <td>
        <code>**bold**</code>

        or

        <code>**bold**</code>
      </td>

      <td>
        Bold text
      </td>
    </tr>

    <tr>
      <td>
        <code>*italic*</code>

        or

        <code>*italic*</code>
      </td>

      <td>
        Italic text
      </td>
    </tr>

    <tr>
      <td>
        <code>`code`</code>
      </td>

      <td>
        Cyan inline code
      </td>
    </tr>

    <tr>
      <td>
        <code>[text](url)</code>
      </td>

      <td>
        Underlined green link text with dimmed URL
      </td>
    </tr>

    <tr>
      <td>
        Fenced code blocks (

        <code>\`\`\`</code>

        )
      </td>

      <td>
        Indented with dimmed borders
      </td>
    </tr>

    <tr>
      <td>
        <code>- item</code>

        ,

        <code>\* item</code>

        ,

        <code>+ item</code>
      </td>

      <td>
        Unordered list with indented bullets
      </td>
    </tr>

    <tr>
      <td>
        <code>1. item</code>

        or

        <code>1) item</code>
      </td>

      <td>
        Ordered list with numbered items
      </td>
    </tr>

    <tr>
      <td>
        <code>> quote</code>
      </td>

      <td>
        Blockquote with dimmed vertical bar
      </td>
    </tr>

    <tr>
      <td>
        <code>---</code>

        ,

        <code>\*\*\*</code>

        ,

        <code>\_\_\_</code>
      </td>

      <td>
        Dimmed horizontal rule
      </td>
    </tr>
  </tbody>
</table>

## Streaming LLM Output

The renderer is designed for the streaming pattern common with LLM APIs. Here's a complete walkthrough:

```ts
import { MarkdownRenderer } from "@wterm/markdown";

const md = new MarkdownRenderer();

async function streamChat(
  write: (data: string) => void,
  prompt: string,
) {
  const response = await fetch("/api/chat", {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({ prompt }),
  });

  const reader = response.body!.getReader();
  const decoder = new TextDecoder();

  while (true) {
    const { done, value } = await reader.read();
    if (done) break;

    const chunk = decoder.decode(value, { stream: true });
    const rendered = md.push(chunk);
    if (rendered) write(rendered);
  }

  const remaining = md.flush();
  if (remaining) write(remaining);
}
```

**How it works:**

1. Create a `MarkdownRenderer` instance before the stream starts
2. As each chunk arrives, call `push(chunk)` — it buffers incomplete lines and only returns output for complete lines
3. When the stream ends, call `flush()` to render any remaining buffered content and close open code blocks
4. Write each non-empty result to the terminal with `write()`

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

---
title: React
url: "https://wterm.dev/react"
docs_index: /llms.txt
lastUpdated: 2026-11-04
navTitle: "React"
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

The `@wterm/react` package provides a `<Terminal>` component and a `useTerminal` hook for integrating wterm into React applications.

## Install

```bash
npm install @wterm/dom @wterm/react
```

## Basic Usage

```tsx
import { Terminal } from "@wterm/react";
import "@wterm/react/css";

function App() {
  return <Terminal />;
}
```

## Custom Input Handling

By default, typed input is echoed back to the terminal. Use `onData` with `useTerminal` when you need control over input — for example, sending it to a server:

```tsx
import { Terminal, useTerminal } from "@wterm/react";
import "@wterm/react/css";

function App() {
  const { ref, write } = useTerminal();

  return (
    <Terminal
      ref={ref}
      onData={(data) => {
        socket.send(data);
      }}
    />
  );
}
```

## Props

The `<Terminal>` component accepts all [shared terminal options](/api-reference#terminal-options) (`cols`, `rows`, `core`, `wasmUrl`, `autoResize`, `maxImageWidth`, `maxImageHeight`, `cursorBlink`, `announceOutput`, `renderingPaused`, `webmcp`, `onData`, `onBinary`, `onTitle`, `onBell`, `onClipboardWrite`, `onShellIntegration`, `onWorkingDirectory`, `onResize`) plus [React-only props](/api-reference#react-only-props) (`theme`, `onReady`, `onError`). Pass a [`TerminalCore`](/ghostty) instance to the `core` prop to use an alternative emulation backend.

Standard `div` props (`className`, `style`, `id`, etc.) are forwarded to the container element.

Use `<Terminal aria-label="Build shell" aria-describedby="shell-help" />` to name and describe the actual input. Label attributes stay synchronized when props change. The wrapper is a group; input is the single editable control. `tabIndex={-1}` removes input from page tab entry. Press Escape, then Tab to move focus out of the terminal, or Escape, then Shift+Tab to move backward. Use `readText()` on the WTerm instance to present a stable snapshot of retained output in a labelled, read-only text area; see [Reading Terminal Output](/api-reference#reading-terminal-output). See [Input Accessibility](/api-reference#input-accessibility).

The React wrapper delegates rendering to `@wterm/dom`, so a graphics-capable
core such as `@wterm/ghostty` automatically renders direct Kitty PNG/RGB/RGBA
images. Set `maxImageWidth` and/or `maxImageHeight` to constrain their rendered
size in CSS pixels; images preserve their aspect ratio and are never enlarged.
Image canvases are decorative (`aria-hidden`, non-focusable, and
pointer-transparent); text remains the accessible terminal surface.

The underlying DOM terminal also answers Kitty pixel geometry queries from the
rendered viewport, so direct image clients can negotiate their display size.

## useTerminal Hook

Returns a ref and imperative helpers:

```tsx
const { ref, write, resize, focus } = useTerminal();
```

See the full [Imperative Handle](/api-reference#imperative-handle-react) reference for all returned methods and the `TerminalHandle` interface.

## Themes

Import the stylesheet and switch themes via the `theme` prop:

```tsx
import "@wterm/react/css";

<Terminal theme="monokai" />;
```

Built-in themes: `solarized-dark`, `monokai`, `light`. Define custom themes with CSS custom properties (`--term-fg`, `--term-bg`, `--term-color-0` through `--term-color-15`).

## Next.js

For Next.js, add `@wterm/dom` and `@wterm/react` to `transpilePackages` in your config:

```js
// next.config.mjs
const nextConfig = {
  transpilePackages: ["@wterm/dom", "@wterm/react"],
};
```

## Terminal Search

The underlying WTerm instance exposes full-history `search`, `findNext`, `findPrevious`, `getSearchState`, and `clearSearch`. Set its `onSearchChange` callback to keep Find controls current. Use `onReady` or `ref.current.instance`. See [Terminal Search](/api-reference#terminal-search) for Unicode behavior, progress, and limits.

## Selection and Copy

Native Copy preserves terminal line breaks and complete Unicode cells, joining confirmed Ghostty soft wraps. Call `getSelectionText()` on the underlying WTerm instance to read that text without writing the clipboard. See [Selection and Copy](/api-reference#selection-and-copy) for whitespace behavior, return values, and native-selection limits.

With Ghostty's current WASM binary, selections also follow output scrolling and resize/reflow while their text remains intact. Overwritten or discarded text, resets, and screen switches clear them. The same reference covers preservation limits and pending-frame behavior.

Use `await wt.selectAll()` on the WTerm instance to select all retained history and the active screen without mounting extra rows, then read `wt.getSelectionText()`. Cmd+A or Ctrl+Shift+A invokes the same action from terminal input. `wt.clearSelection()` cancels it. See [Select All](/api-reference#select-all) for capture limits, shortcuts, and when selection clears.

Double-click words or paths, or triple-click logical lines. The WTerm methods `selectWord({ row, col })` and `selectLine(row)` create the same native selection, including confirmed Ghostty soft wraps and unmounted history. See [Words and Logical Lines](/api-reference#words-and-logical-lines) for coordinates, boundaries, and limits.

Alt/Option-drag selects rectangular columns; Shift+Alt selects inside mouse-reporting applications. Call `selectRectangle(start, end)` on the WTerm instance with inclusive retained-row/cell corners. Rectangles preserve selected spaces and physical row breaks and clear on output or resize. See [Rectangular Selection](/api-reference#rectangular-selection) for copy shortcuts, coordinates, and limits.

## Output announcements

Opt in with `announceOutput` to announce terminal text changes politely while
input has focus. The option defaults to `false`; component prop changes apply
without restarting the terminal, and vanilla callers use
`term.setOutputAnnouncements(enabled)`. Announcements are batched and stop
when focus leaves input. Bursts are bounded to avoid overwhelming the speech
queue. See [Output announcements](/api-reference#output-announcements) for
capture limits, pause/resume behavior, and redraw semantics.

## Inactive panes

Use `renderingPaused` for panes that are not visible. Parsing and terminal
effects continue, and resuming paints the latest state without restarting.
In vanilla code, call `term.setRenderingPaused(paused)`; framework props are
reactive. Browser document visibility also pauses painting. See
[Inactive panes](/configuration#inactive-panes) for focus and capture behavior.

Application clipboard writes require an explicit host policy; see [Clipboard requests](/configuration#clipboard-requests).

## Shell integration

With Ghostty, the `onWorkingDirectory` prop receives raw OSC 7 URI reports,
including while rendering is paused. An empty string clears the value. Treat
the URI as untrusted metadata; see [Working directories](/configuration#working-directories).

With Ghostty and a shell emitting OSC 133, `onShellIntegration` receives the
latest prompt, input, running, or completion state and the last reported exit
code. Delivery continues while painting is paused; markers in one parser chunk
coalesce into current state. See [Shell integration](/configuration#shell-integration)
for the contract, shell setup, and workspace indicators.

Use the underlying `WTerm` instance’s `scrollToPrompt(-1)` and
`scrollToPrompt(1)` methods to navigate previous and next prompts in retained
history. See [Shell integration](/configuration#shell-integration) for viewport
behavior, unsupported cores, and inactive states.

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

---
title: Svelte
url: "https://wterm.dev/svelte"
docs_index: /llms.txt
lastUpdated: 2026-11-04
navTitle: "Svelte"
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

The `@wterm/svelte` package provides a `<Terminal>` component for Svelte 5
applications. It re-exports the DOM and core APIs from `@wterm/dom` so the
component and terminal types can come from one import.

Use `<Terminal aria-label="Build shell" aria-describedby="shell-help" />` to name and describe the actual input. Label attributes stay synchronized when they change. The wrapper is a group; input is the single editable control. `tabindex={-1}` removes input from page tab entry. Press Escape, then Tab to move focus out of the terminal, or Escape, then Shift+Tab to move backward. Use `readText()` on the WTerm instance to present a stable snapshot of retained output in a labelled, read-only text area; see [Reading Terminal Output](/api-reference#reading-terminal-output). See [Input Accessibility](/api-reference#input-accessibility).

## Install

```bash
npm install @wterm/dom @wterm/svelte svelte
```

## Basic Usage

```svelte
<script lang="ts">
import { Terminal } from "@wterm/svelte";
import "@wterm/svelte/css";
</script>

<Terminal />
```

The WASM binary is embedded in the package — no extra setup is required. To
serve it separately, pass `wasmUrl`.

## Custom Input Handling

Typed input is echoed back by default. In Svelte 5, pass `onData` when input
should be sent to a PTY or another backend:

```svelte
<script lang="ts">
import { Terminal } from "@wterm/svelte";
import "@wterm/svelte/css";

function onData(data: string) {
  socket.send(data);
}
</script>

<Terminal {onData} />
```

`onBinary` receives raw X10 mouse bytes. Forward its `Uint8Array` unchanged
when the terminal input transport accepts binary data.

The other callback props are `onTitle`, `onBell`, `onClipboardWrite`, `onShellIntegration`, `onWorkingDirectory`, `onResize`, `onReady`, and `onError`.
`onReady` receives the underlying `WTerm` instance.

## Props

The component accepts the shared terminal options `cols`, `rows`, `core`,
`wasmUrl`, `autoResize`, `maxImageWidth`, `maxImageHeight`, `cursorBlink`, `announceOutput`, `renderingPaused`, `webmcp`, and
`debug`, plus the `theme` prop. Standard `<div>` attributes such as `class`,
`style`, `id`, and ARIA attributes are forwarded to the root element.

See the [Terminal Options](/api-reference#terminal-options) and
[Svelte-only Props](/api-reference#svelte-only-props) reference sections for
details.

The Svelte wrapper delegates rendering to `@wterm/dom`, so a graphics-capable
core such as `@wterm/ghostty` automatically renders direct Kitty PNG/RGB/RGBA
images. Set `maxImageWidth` and/or `maxImageHeight` to constrain rendered
images while preserving their aspect ratio.

## Imperative Control

Bind the component instance to call `write`, `resize`, and `focus`:

```svelte
<script lang="ts">
import { Terminal, type TerminalHandle } from "@wterm/svelte";

let terminal: TerminalHandle;
</script>

<Terminal bind:this={terminal} />
```

To access the underlying `WTerm`, bind the `instance` prop:

```svelte
<script lang="ts">
import { Terminal, type WTerm } from "@wterm/svelte";

let instance: WTerm | null = null;
</script>

<Terminal bind:instance />
```

## Themes

Import the stylesheet and pass the theme name:

```svelte
<script lang="ts">
import { Terminal } from "@wterm/svelte";
import "@wterm/svelte/css";
</script>

<Terminal theme="monokai" />
```

Built-in themes are `solarized-dark`, `monokai`, and `light`. Define custom
themes with CSS custom properties.

The component initializes in `onMount`, so it is safe to render from an SSR
application.

## Terminal Search

The underlying WTerm instance exposes full-history `search`, `findNext`, `findPrevious`, `getSearchState`, and `clearSearch`. Set its `onSearchChange` callback to keep Find controls current. Use `onReady` or `bind:instance`. See [Terminal Search](/api-reference#terminal-search) for Unicode behavior, progress, and limits.

## Selection and Copy

Native Copy preserves terminal line breaks and complete Unicode cells, joining confirmed Ghostty soft wraps. Call `getSelectionText()` on the underlying WTerm instance to read that text without writing the clipboard. See [Selection and Copy](/api-reference#selection-and-copy) for whitespace behavior, return values, and native-selection limits.

With Ghostty's current WASM binary, selections also follow output scrolling and resize/reflow while their text remains intact. Overwritten or discarded text, resets, and screen switches clear them. The same reference covers preservation limits and pending-frame behavior.

Use `await wt.selectAll()` on the WTerm instance to select all retained history and the active screen without mounting extra rows, then read `wt.getSelectionText()`. Cmd+A or Ctrl+Shift+A invokes the same action from terminal input. `wt.clearSelection()` cancels it. See [Select All](/api-reference#select-all) for capture limits, shortcuts, and when selection clears.

Double-click words or paths, or triple-click logical lines. The WTerm methods `selectWord({ row, col })` and `selectLine(row)` create the same native selection, including confirmed Ghostty soft wraps and unmounted history. See [Words and Logical Lines](/api-reference#words-and-logical-lines) for coordinates, boundaries, and limits.

Alt/Option-drag selects rectangular columns; Shift+Alt selects inside mouse-reporting applications. Call `selectRectangle(start, end)` on the WTerm instance with inclusive retained-row/cell corners. Rectangles preserve selected spaces and physical row breaks and clear on output or resize. See [Rectangular Selection](/api-reference#rectangular-selection) for copy shortcuts, coordinates, and limits.

## Output announcements

Opt in with `announceOutput` to announce terminal text changes politely while
input has focus. The option defaults to `false`; component prop changes apply
without restarting the terminal, and vanilla callers use
`term.setOutputAnnouncements(enabled)`. Announcements are batched and stop
when focus leaves input. Bursts are bounded to avoid overwhelming the speech
queue. See [Output announcements](/api-reference#output-announcements) for
capture limits, pause/resume behavior, and redraw semantics.

## Inactive panes

Use `renderingPaused` for panes that are not visible. Parsing and terminal
effects continue, and resuming paints the latest state without restarting.
In vanilla code, call `term.setRenderingPaused(paused)`; framework props are
reactive. Browser document visibility also pauses painting. See
[Inactive panes](/configuration#inactive-panes) for focus and capture behavior.

Application clipboard writes require an explicit host policy; see [Clipboard requests](/configuration#clipboard-requests).

## Shell integration

With Ghostty, `onWorkingDirectory` (or `onworkingdirectory`) receives raw OSC 7
URI reports, including while rendering is paused. An empty string clears the
value. Treat the URI as untrusted metadata; see
[Working directories](/configuration#working-directories).

With Ghostty and a shell emitting OSC 133, `onShellIntegration` receives the
latest prompt, input, running, or completion state and the last reported exit
code. Delivery continues while painting is paused; markers in one parser chunk
coalesce into current state. See [Shell integration](/configuration#shell-integration)
for the contract, shell setup, and workspace indicators.

Use the underlying `WTerm` instance’s `scrollToPrompt(-1)` and
`scrollToPrompt(1)` methods to navigate previous and next prompts in retained
history. See [Shell integration](/configuration#shell-integration) for viewport
behavior, unsupported cores, and inactive states.

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

---
title: Themes
url: "https://wterm.dev/themes"
docs_index: /llms.txt
lastUpdated: 2026-11-04
navTitle: "Themes"
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

wterm uses CSS custom properties for all visual styling, making it easy to switch between built-in themes or create your own.

On this site's homepage, **Default** follows your site theme: light, dark, or
system. The greeting and interactive terminal use the same colors, including in
fullscreen. Selecting Solarized, Monokai, or Light keeps that terminal preset
independent of the site theme. This site-specific Default does not change the
package's default dark palette described below.

The [local shell workspace](https://github.com/vercel-labs/wterm/tree/main/examples/local)
has an **Appearance** dialog with System, Dark, and Light choices and a 10–32 px
font-size control. Settings apply to all panes and persist in the same browser.
System follows OS changes; switching appearance preserves existing shells.

## Using a Theme

### React

Pass the theme name via the `theme` prop:

```tsx
<Terminal theme="monokai" />
```

### Vue

Same `theme` prop:

```vue
<Terminal theme="monokai" />
```

### Svelte

Pass the theme name through the `theme` prop:

```svelte
<Terminal theme="monokai" />
```

### Vanilla JS

Add the theme class to the terminal element:

```js
const term = new WTerm(document.getElementById("terminal"));
term.element.classList.add("theme-monokai");
await term.init();
```

## Built-in Themes

### Default

The default dark theme, inspired by VS Code's Dark+ palette.

<table>
  <thead>
    <tr>
      <th>
        Property
      </th>

      <th>
        Value
      </th>
    </tr>
  </thead>

  <tbody>
    <tr>
      <td>Background</td>

      <td><code>#1e1e1e</code></td>
    </tr>

    <tr>
      <td>Foreground</td>

      <td><code>#d4d4d4</code></td>
    </tr>

    <tr>
      <td>Cursor</td>

      <td><code>#aeafad</code></td>
    </tr>
  </tbody>
</table>

### Solarized Dark

The classic Solarized Dark color scheme by Ethan Schoonover.

```tsx
<Terminal theme="solarized-dark" />
```

<table>
  <thead>
    <tr>
      <th>
        Property
      </th>

      <th>
        Value
      </th>
    </tr>
  </thead>

  <tbody>
    <tr>
      <td>Background</td>

      <td><code>#002b36</code></td>
    </tr>

    <tr>
      <td>Foreground</td>

      <td><code>#839496</code></td>
    </tr>

    <tr>
      <td>Cursor</td>

      <td><code>#93a1a1</code></td>
    </tr>
  </tbody>
</table>

### Monokai

Based on the Monokai color scheme.

```tsx
<Terminal theme="monokai" />
```

<table>
  <thead>
    <tr>
      <th>
        Property
      </th>

      <th>
        Value
      </th>
    </tr>
  </thead>

  <tbody>
    <tr>
      <td>Background</td>

      <td><code>#272822</code></td>
    </tr>

    <tr>
      <td>Foreground</td>

      <td><code>#f8f8f2</code></td>
    </tr>

    <tr>
      <td>Cursor</td>

      <td><code>#f8f8f0</code></td>
    </tr>
  </tbody>
</table>

### Light

A clean light theme inspired by Atom's One Light.

```tsx
<Terminal theme="light" />
```

<table>
  <thead>
    <tr>
      <th>
        Property
      </th>

      <th>
        Value
      </th>
    </tr>
  </thead>

  <tbody>
    <tr>
      <td>Background</td>

      <td><code>#fafafa</code></td>
    </tr>

    <tr>
      <td>Foreground</td>

      <td><code>#383a42</code></td>
    </tr>

    <tr>
      <td>Cursor</td>

      <td><code>#526eff</code></td>
    </tr>
  </tbody>
</table>

## Custom Themes

Create a custom theme by defining a CSS class that overrides the wterm custom properties:

```css
.wterm.theme-tokyo-night {
  --term-bg: #1a1b26;
  --term-fg: #c0caf5;
  --term-cursor: #f7768e;

  --term-color-0: #15161e;
  --term-color-1: #f7768e;
  --term-color-2: #9ece6a;
  --term-color-3: #e0af68;
  --term-color-4: #7aa2f7;
  --term-color-5: #bb9af7;
  --term-color-6: #7dcfff;
  --term-color-7: #a9b1d6;
  --term-color-8: #414868;
  --term-color-9: #f7768e;
  --term-color-10: #9ece6a;
  --term-color-11: #e0af68;
  --term-color-12: #7aa2f7;
  --term-color-13: #bb9af7;
  --term-color-14: #7dcfff;
  --term-color-15: #c0caf5;
}
```

Then apply it the same way as a built-in theme:

```tsx
<Terminal theme="tokyo-night" />
```

## CSS Custom Properties Reference

<table>
  <thead>
    <tr>
      <th>
        Property
      </th>

      <th>
        Description
      </th>
    </tr>
  </thead>

  <tbody>
    <tr>
      <td><code>--term-bg</code></td>

      <td>Terminal background color</td>
    </tr>

    <tr>
      <td><code>--term-fg</code></td>

      <td>Default text color</td>
    </tr>

    <tr>
      <td><code>--term-cursor</code></td>

      <td>Cursor color</td>
    </tr>

    <tr>
      <td><code>--term-font-family</code></td>

      <td>Font stack (default: Menlo, Consolas, DejaVu Sans Mono, Courier New, monospace)</td>
    </tr>

    <tr>
      <td><code>--term-font-size</code></td>

      <td>Font size (default: <code>14px</code>)</td>
    </tr>

    <tr>
      <td><code>--term-line-height</code></td>

      <td>Line height multiplier (default: <code>1.2</code>)</td>
    </tr>

    <tr>
      <td><code>--term-color-0</code> … <code>--term-color-7</code></td>

      <td>Standard ANSI colors (black, red, green, yellow, blue, magenta, cyan, white)</td>
    </tr>

    <tr>
      <td><code>--term-color-8</code> … <code>--term-color-15</code></td>

      <td>Bright ANSI colors</td>
    </tr>
  </tbody>
</table>

Colors beyond the 16-color palette (indices 16–255 and true-color RGB) are computed by the renderer and do not use custom properties.

## Updating host colors

Use `wt.setThemeColors(colors)` to update a running terminal's foreground,
background, cursor, and 16 ANSI colors together. The method sets the corresponding
CSS custom properties and forwards the defaults to a supporting core. It repaints
live and retained rows without writing escape sequences, restarting the parser,
or clearing application color overrides. Painting still respects synchronized
output, hidden pages, and paused panes.

```ts
wt.setThemeColors({
  foreground: 0x383a42,
  background: 0xfafafa,
  cursor: 0x526fff,
  palette: [
    0x383a42, 0xe45649, 0x50a14f, 0xc18401,
    0x4078f2, 0xa626a4, 0x0184bc, 0xa0a1a7,
    0x4f525e, 0xe45649, 0x50a14f, 0xc18401,
    0x4078f2, 0xa626a4, 0x0184bc, 0xffffff,
  ],
});
```

`TerminalThemeColors` uses opaque integer RGB values from `0` to `0xffffff`
and exactly 16 palette entries; invalid values throw `RangeError` before changes
are applied. Values are copied and can be set before `init()`. Indices 16–255 and
explicit RGB cell colors are unchanged. Application overrides take precedence;
OSC resets return to the latest host defaults. The inline properties set by this
method take precedence over theme classes; call it again to change those colors.
Pass `null` to remove them and return to the CSS theme.

With Ghostty, use the updated bundled WASM asset to synchronize indexed colors
and foreground/background query replies. Older WASM assets retain their existing
engine defaults. Custom cores may implement the optional
`TerminalCore.setThemeColors()` hook; without it only the CSS defaults update.
Framework users can call the same method on the WTerm instance from `onReady`
or their imperative handle's `instance`.

## Application color changes

Ghostty applications can set the default foreground, background, and cursor
colors with OSC `10`, `11`, and `12`. OSC `110`, `111`, and `112` reset the
corresponding color to the current CSS theme, including theme changes made
while an override was active. Explicit SGR cell colors are unaffected. Inverse
cells swap the actual foreground/background, including default colors, in both
cores.

Application overrides are separate from `--term-fg`, `--term-bg`, and
`--term-cursor`; the renderer never overwrites these host theme properties.
Updates follow normal rendering, including synchronized output and paused
panes. Destroying the terminal removes its overrides. The built-in core and
older Ghostty WASM binaries retain CSS theme defaults.

## Color queries

Applications ask for the default foreground and background with OSC `10` and
`11` (`printf '\e]11;?\a'`). Both cores answer with the colors the terminal
element's CSS resolves to when the query arrives: `--term-fg` and `--term-bg`,
including theme classes, `prefers-color-scheme` rules and colors written in any
CSS color space. Replies end with the query's own terminator, BEL or ST.

Colors set with `setThemeColors()` take precedence until it is called with
`null`. A Ghostty application's own OSC `10`/`11` color changes are reported
until it resets them. A query split across two `write()` calls is answered
with the colors read at the previous query or at `init()`.

Headless Ghostty cores, which have no CSS, answer with their `foregroundColor`
and `backgroundColor` options. See [Ghostty options](/ghostty).

## Font and cell alignment

Choose a monospace font with `--term-font-family` and set its size with
`--term-font-size`. wterm measures the font's cell width and uses it for text,
backgrounds, and cursors. Braille, box drawing, and other fallback glyphs stay
within their assigned columns even when the fallback font has wider glyphs.
Wide characters occupy two cells; oversized glyphs are clipped to their cells.
Common light, heavy, and rounded box-drawing characters connect across cell
edges even when the font's glyphs fall short. They remain selectable as text.

Cell widths update when fonts load or the font size changes, including when
`autoResize` is disabled. Native selection and OSC 8 link text are preserved.
This behavior is shared by both cores and all framework components.

## Cell backgrounds

Colored and reversed cells keep their backgrounds within their columns,
including the last column and rows in scrollback. A complete row with one
shared, opaque background extends that color to the container's right edge,
keeping full-width status bars filled. Mixed, dim, or hidden cells do not
repaint neighboring cells or other rows. This behavior is shared by both cores
and all framework components.

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

---
title: Vanilla JS
url: "https://wterm.dev/vanilla"
docs_index: /llms.txt
lastUpdated: 2026-11-06
navTitle: "Vanilla JS"
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

The `@wterm/dom` package provides a framework-free terminal that works with plain HTML/CSS/JS.

## Install

```bash
npm install @wterm/dom
```

## Usage

```html
<div id="terminal"></div>

<script type="module">
  import { WTerm } from "@wterm/dom";
  import "@wterm/dom/css";

  const term = new WTerm(document.getElementById("terminal"), {
    onTitle(title) {
      document.title = title;
    },
  });

  await term.init();
</script>
```

## Input Accessibility

Give the host an `aria-label` or `aria-labelledby`, with an optional `aria-describedby` or `aria-description`. WTerm applies these to the actual input and keeps changes synchronized. The input defaults to the name **Terminal**; the host defaults to a group. Host `tabindex="-1"` excludes input from page tab entry, while `term.focus()` still focuses it programmatically. Press Escape, then Tab to move focus out of the terminal, or Escape, then Shift+Tab to move backward. Use `readText()` on the WTerm instance to present a stable snapshot of retained output in a labelled, read-only text area; see [Reading Terminal Output](/api-reference#reading-terminal-output). See [Input Accessibility](/api-reference#input-accessibility) for tab-order ownership and output limits.

## Custom Input Handling

By default, typed input is echoed back to the terminal. Pass `onData` when you need control over input — for example, sending it to a server:

```js
const term = new WTerm(document.getElementById("terminal"), {
  onData(data) {
    socket.send(data);
  },
});

await term.init();
```

## Options and Methods

The `WTerm` constructor accepts all [shared terminal options](/api-reference#terminal-options) (`cols`, `rows`, `core`, `wasmUrl`, `autoResize`, `maxImageWidth`, `maxImageHeight`, `cursorBlink`, `announceOutput`, `renderingPaused`, `webmcp`, `onData`, `onBinary`, `onTitle`, `onBell`, `onClipboardWrite`, `onShellIntegration`, `onWorkingDirectory`, `onResize`). Pass a [`TerminalCore`](/ghostty) instance to the `core` option to use an alternative emulation backend.

When auto-resizing, omitted dimensions are measured before core initialization,
avoiding an unnecessary initial resize. Explicit dimensions are preserved at
initialization. Hidden containers fall back to 80×24 until measurable. Read
`wt.cols` and `wt.rows` after `await wt.init()` when starting a PTY.

See [WTerm Methods](/api-reference#wterm-methods) for all instance methods, including [full-history Find](/api-reference#terminal-search): `search`, `findNext`, `findPrevious`, `getSearchState`, and `clearSearch`. Use the `onSearchChange` WTerm option or instance callback for progress and match counts.

When a terminal application enables mouse tracking, UTF-8 (1005), SGR (1006), urxvt (1015), and SGR pixel (1016) reports reach `onData` with keyboard input and focus reports. Forward `onData` strings through a UTF-8 transport. Mode 1016 uses 1-based CSS-pixel coordinates relative to the visible grid, independent of device pixel ratio. Mode 1003 sends unpressed pointer motion once per cell in cell formats and once per CSS pixel in 1016.

X10 mouse reports go to `onBinary` as raw bytes when that callback is supplied. Without `onBinary`, ASCII-safe X10 reports reach `onData`; coordinates requiring non-ASCII bytes are skipped. Hold Shift for native text selection.

When mouse tracking is active, clicks and wheel gestures on scrollback rows
stay with the browser. If you are reading history, the wheel scrolls it until
you reach the bottom; hold Shift and scroll to move through history from the
live viewport. A drag begun on the live grid still reports its release after
leaving the grid.

When an application rings BEL, `onBell(count)` receives the number of bells as
output is written. WTerm does not play sound automatically; your application
can choose an audible or visual notification.

When the active core negotiates the Kitty keyboard protocol, WTerm encodes key presses, repeats, and available releases from the core's current flags, including disambiguated escape codes and all-key reporting when the application requests them. The built-in and Ghostty cores keep separate primary and alternate-screen flag stacks. Third-party cores without Kitty state retain legacy keyboard behavior.

The built-in core answers DEC private-mode status queries such as `CSI ?2026$p` for synchronized output. A response of `CSI ?2026;1$y` means the mode is set; `CSI ?2026;2$y` means it is reset. WTerm forwards the response through `onData` to your connected application.

Printable AltGr input uses the browser's committed text path even in Kitty mode, so layouts that use AltGr can enter characters. This includes Control+Alt text with the right Alt key when the browser does not report AltGraph state. Control+Alt chords without either AltGr signal keep their Kitty encoding.

Outside Kitty keyboard mode, modified arrow, Home/End, Insert/Delete, Page
Up/Down, and F1–F12 keys use xterm-style CSI modifier sequences. For example,
Control+Left sends `CSI 1;5D` instead of an unmodified Left arrow. Unmodified
arrows still honor application cursor mode. Browser-reserved shortcuts may not
reach the terminal.

While the terminal is focused, handled key presses do not bubble to page-level
shortcut listeners. Control+K can reach the terminal instead of opening docs
search.

When Kitty keyboard mode is not negotiated, Control+Space sends NUL, Control+/
sends US, Control+? sends DEL, and Control+Backspace sends BS. Browsers may reserve some shortcuts.
Control+Alt printable input remains on the native text path in this mode for
keyboard layouts that use AltGr.

When an application enables xterm `modifyOtherKeys` level 2 (`CSI > 4 ; 2 m`)
and Kitty keyboard mode is not negotiated, keys whose legacy encoding loses a
modifier are sent as `CSI 27 ; modifier ; code ~`: Control and Alt with
characters, including Control+letter, Shift with letters and Space, Shift+Tab,
Alt+Tab, Alt+Enter, and Backspace with any modifier except Control alone. Other
keys keep their legacy encoding, and Meta shortcuts stay with the browser.
`CSI ? 4 m` reports the level, other `CSI > Pp m` changes return to legacy
input, and RIS resets it. Vim enables this mode for xterm terminals to map keys
such as Control+Shift+E apart from Control+E.

Browsers do not expose every native keyboard field. WTerm uses `KeyboardEvent.code` for functional and modifier identity, `KeyboardEvent.key` for text, and reports shifted alternates when available. It does not invent a base-layout alternate, synthesize missing release events, or retain associated text beyond the current press.

IME composition displays tentative text at the terminal cursor and sends only
the committed text to `onData`. If you are viewing scrollback when composition
starts, WTerm returns to the live viewport so the browser's candidate window
appears by the current input.

On touch-first devices, the transparent input target stays at the cursor for
soft-keyboard focus and the native paste menu. Holding Backspace sends repeated
deletions, and pasted text follows the application's bracketed-paste mode.

Application-provided OSC 8 hyperlinks render as native anchors across both the active viewport and scrollback. Only absolute HTTP and HTTPS URIs become clickable; invalid, relative, and executable schemes remain ordinary terminal text.

Partial row redraws reuse matching cell spans when most of the row is unchanged. When span styles and column widths stay the same, text changes update existing text nodes without reparsing HTML. Cell widths, selection, links, and text decorations remain intact.

Glyph ink can overhang its cell without shifting later columns, so italic and fallback characters retain their complete shape. The viewport still clips output at its edges. Fractional `--term-row-height` values are preserved during initialization, allowing the live grid to match a host's typography.

While hovering an anchor, holding Command on macOS or Control on Windows and Linux reveals its underline and pointer cursor. Plain clicks remain terminal interaction. Command-click, Control-click, or native keyboard activation when an anchor receives focus opens the link. Modified link activation remains available while SGR mouse tracking is active and is not forwarded to the terminal application.

Scrollback normally uses a bounded DOM window around the visible rows. Up to 1,000 selected history rows stay mounted separately from a distant viewport, leaving gaps virtualized. Native browser find and accessibility inspect mounted rows, not every retained history row. If the terminal is already at the bottom, new output keeps it at the exact bottom; reading older history does not enable auto-follow, and terminal input returns to the bottom.

With the built-in and Ghostty cores, small scrolls reuse the mounted overscan window until the viewport approaches its edge. When the window moves, overlapping history rows keep their content without rereading their cells. Output, theme changes, resize, and history changes refresh the content. Custom cores without a discarded-history count refresh mounted history on every render.

Copying a terminal selection preserves complete Unicode cells and explicit newlines; Ghostty soft wraps join without artificial line breaks. Block glyphs are included as text. Use `term.getSelectionText()` to read the same plain text without touching the clipboard. See [Selection and Copy](/api-reference#selection-and-copy) for whitespace behavior and native-selection limits.

With Ghostty's current WASM binary, selections also follow output scrolling and resize/reflow. Overwritten or discarded text, resets, and screen switches clear the selection. See the same reference for preservation limits and behavior while a frame is pending.

Use `await wt.selectAll()` on the WTerm instance to select all retained history and the active screen without mounting extra rows, then read `wt.getSelectionText()`. Cmd+A or Ctrl+Shift+A invokes the same action from terminal input. `wt.clearSelection()` cancels it. See [Select All](/api-reference#select-all) for capture limits, shortcuts, and when selection clears.

Double-click words or paths, or triple-click logical lines. The WTerm methods `selectWord({ row, col })` and `selectLine(row)` create the same native selection, including confirmed Ghostty soft wraps and unmounted history. See [Words and Logical Lines](/api-reference#words-and-logical-lines) for coordinates, boundaries, and limits.

Alt/Option-drag selects rectangular columns; Shift+Alt selects inside mouse-reporting applications. Call `selectRectangle(start, end)` on the WTerm instance with inclusive retained-row/cell corners. Rectangles preserve selected spaces and physical row breaks and clear on output or resize. See [Rectangular Selection](/api-reference#rectangular-selection) for copy shortcuts, coordinates, and limits.

### Terminal images

When the supplied core exposes the optional graphics methods, `WTerm` renders
visible pinned Kitty image placements in a separate canvas overlay. The overlay
is pointer-transparent, non-focusable, `aria-hidden`, and does not change
scroll height, so ordinary text selection, copy, keyboard input, and screen
reader behavior remain row-based. Image pixels are decorative; provide text
context separately when the image carries meaning.

For browser safety, every destination canvas is bounded by the active terminal
pixel area and all visible canvas backing stores share a 32 MiB budget. An
oversized or otherwise unmaterializable placement is skipped without changing
the text or scrollback surface.

The default lightweight core safely consumes unsupported Kitty APC data but
does not render it. Pass a Ghostty core for direct PNG/RGB/RGBA support. Images
are transient, bounded per-screen state; file/shared-memory/URL media, Sixel,
iTerm2, animation, and virtual placements are unsupported.
Use `maxImageWidth` and/or `maxImageHeight` in the `WTerm` options to constrain
oversized rendered images. The limits are CSS pixels, preserve aspect ratio,
and only scale images down.

The DOM wrapper answers Kitty pixel geometry queries (`CSI 14 t` and
`CSI 16 t`) from the rendered terminal element, allowing Kitty graphics clients
such as `kitten icat --transfer-mode=stream image.png` to negotiate image size.
Queries can span text and byte writes, including while rendering is paused.
Ordinary byte output is scanned directly without creating a decoded string copy.

## Themes

Import the stylesheet and apply a theme class to the terminal element:

```js
import "@wterm/dom/css";
```

Built-in themes: `theme-solarized-dark`, `theme-monokai`, `theme-light`:

```js
element.classList.add("theme-monokai");
```

All colors use CSS custom properties (`--term-fg`, `--term-bg`, `--term-color-0` through `--term-color-15`) so you can define your own theme with plain CSS.

## WebSocket Transport

Connect to a PTY backend over WebSocket:

```js
import { WTerm, WebSocketTransport } from "@wterm/dom";

const term = new WTerm(el, { cols: 80, rows: 24 });
await term.init();

const ws = new WebSocketTransport({
  url: "ws://localhost:8080/pty",
  onData: (data) => term.write(data),
});

ws.connect();
term.onData = (data) => {
  try {
    ws.send(data);
  } catch (error) {
    console.error("Input was not sent", error); // Show this in your app's UI.
  }
};
```

See the full [WebSocketTransport](/api-reference#websockettransport) reference for all options, methods, and properties.

Sends are bounded to 1 MiB and 1,024 queued messages by default. Observe
`onBackpressure` and catch buffer-limit `RangeError` to show input that was not
accepted. Explicit close discards unsent input. See
[Buffering and delivery](/api-reference#buffering-and-delivery) before wiring
shell input or implementing reconnect behavior.

## Output announcements

Opt in with `announceOutput` to announce terminal text changes politely while
input has focus. The option defaults to `false`; component prop changes apply
without restarting the terminal, and vanilla callers use
`term.setOutputAnnouncements(enabled)`. Announcements are batched and stop
when focus leaves input. Bursts are bounded to avoid overwhelming the speech
queue. See [Output announcements](/api-reference#output-announcements) for
capture limits, pause/resume behavior, and redraw semantics.

## Inactive panes

Use `renderingPaused` for panes that are not visible. Parsing and terminal
effects continue, and resuming paints the latest state without restarting.
In vanilla code, call `term.setRenderingPaused(paused)`; framework props are
reactive. Browser document visibility also pauses painting. See
[Inactive panes](/configuration#inactive-panes) for focus and capture behavior.

## Shell integration

With Ghostty, `onWorkingDirectory(uri)` receives raw OSC 7 directory reports,
including while rendering is paused. An empty string clears the value. Treat
the URI as untrusted metadata; see [Working directories](/configuration#working-directories).

With Ghostty and a shell emitting OSC 133, `onShellIntegration` receives the
latest prompt, input, running, or completion state and the last reported exit
code. Delivery continues while painting is paused; markers in one parser chunk
coalesce into current state. See [Shell integration](/configuration#shell-integration)
for the contract, shell setup, and workspace indicators.

Use the underlying `WTerm` instance’s `scrollToPrompt(-1)` and
`scrollToPrompt(1)` methods to navigate previous and next prompts in retained
history. See [Shell integration](/configuration#shell-integration) for viewport
behavior, unsupported cores, and inactive states.

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

---
title: Vue
url: "https://wterm.dev/vue"
docs_index: /llms.txt
lastUpdated: 2026-11-04
navTitle: "Vue"
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

The `@wterm/vue` package provides a `<Terminal>` component for integrating wterm into Vue 3 applications. It re-exports everything from `@wterm/dom`, so a single import covers both the component and its types.

With Ghostty, `@working-directory="onDirectory"` receives raw OSC 7 URI reports,
including while rendering is paused. An empty string clears the value. Treat
the URI as untrusted metadata; see [Working directories](/configuration#working-directories).

Use `<Terminal aria-label="Build shell" aria-describedby="shell-help" />` to name and describe the actual input. Label attributes stay synchronized when they change. The wrapper is a group; input is the single editable control. `:tabindex="-1"` removes input from page tab entry. Press Escape, then Tab to move focus out of the terminal, or Escape, then Shift+Tab to move backward. Use `readText()` on the WTerm instance to present a stable snapshot of retained output in a labelled, read-only text area; see [Reading Terminal Output](/api-reference#reading-terminal-output). See [Input Accessibility](/api-reference#input-accessibility).

## Install

```bash
npm install @wterm/dom @wterm/vue
```

## Basic Usage

```vue
<script setup lang="ts">
import { Terminal } from "@wterm/vue";
import "@wterm/vue/css";
</script>

<template>
  <Terminal />
</template>
```

## Custom Input Handling

By default, typed input is echoed back to the terminal. Listen to the `data` event when you need control over input — for example, sending it to a server:

```vue
<script setup lang="ts">
import { useTemplateRef } from "vue";
import { Terminal } from "@wterm/vue";
import "@wterm/vue/css";

const term = useTemplateRef("term");

function onData(chunk: string) {
  socket.send(chunk);
}
</script>

<template>
  <Terminal ref="term" @data="onData" />
</template>
```

## Props

The `<Terminal>` component accepts all [shared terminal options](/api-reference#terminal-options) (`cols`, `rows`, `core`, `wasmUrl`, `autoResize`, `maxImageWidth`, `maxImageHeight`, `cursorBlink`, `announceOutput`, `renderingPaused`, `webmcp`) plus [Vue-only props](/api-reference#vue-only-props) (`theme`). Pass a [`TerminalCore`](/ghostty) instance to the `core` prop to use an alternative emulation backend.

Because `inheritAttrs` is enabled, standard DOM attributes (`class`, `style`, `id`, ARIA props, etc.) are forwarded to the root `<div>`.

The Vue wrapper delegates rendering to `@wterm/dom`, so a graphics-capable
core such as `@wterm/ghostty` automatically renders direct Kitty PNG/RGB/RGBA
images. Set `maxImageWidth` and/or `maxImageHeight` to constrain their rendered
size in CSS pixels; images preserve their aspect ratio and are never enlarged.
Image canvases are decorative (`aria-hidden`, non-focusable, and
pointer-transparent); text remains the accessible terminal surface.

The underlying DOM terminal also answers Kitty pixel geometry queries from the
rendered viewport, so direct image clients can negotiate their display size.

## Events

Input callbacks are exposed as Vue events rather than `onXxx` props:

```vue
<Terminal
  @data="onData"
  @title="onTitle"
  @bell="onBell"
  @clipboard-write="onClipboardWrite"
  @resize="onResize"
  @ready="onReady"
  @error="onError"
/>
```

See the full [Vue Events](/api-reference#vue-events) reference for payload types.

The `binary` event carries raw X10 mouse bytes as a `Uint8Array`. Forward it
unchanged when the terminal input transport accepts binary data.

## Template Ref

Access imperative methods via a template ref:

```vue
<script setup lang="ts">
import { useTemplateRef } from "vue";
import { Terminal, type WTerm } from "@wterm/vue";

const term = useTemplateRef("term");

function onReady(wt: WTerm) {
  wt.write("hello\r\n");
  term.value?.resize(120, 40);
}
</script>

<template>
  <Terminal ref="term" @ready="onReady" />
</template>
```

The exposed methods are `write`, `resize`, `focus`, and `instance` (the underlying `WTerm`, or `null` before mount). See the full [Template Ref](/api-reference#template-ref-vue) reference.

## Themes

Import the stylesheet and switch themes via the `theme` prop:

```vue
<script setup lang="ts">
import { Terminal } from "@wterm/vue";
import "@wterm/vue/css";
</script>

<template>
  <Terminal theme="monokai" />
</template>
```

Built-in themes: `solarized-dark`, `monokai`, `light`. Define custom themes with CSS custom properties (`--term-fg`, `--term-bg`, `--term-color-0` through `--term-color-15`). See [Themes](/themes) for details.

## Nuxt

The component mounts in `onMounted`, so it is SSR-safe out of the box. If you need to guarantee client-only rendering (e.g. for debugging or to skip hydration entirely), wrap it in `<ClientOnly>`:

```vue
<template>
  <ClientOnly>
    <Terminal />
  </ClientOnly>
</template>
```

## Terminal Search

The underlying WTerm instance exposes full-history `search`, `findNext`, `findPrevious`, `getSearchState`, and `clearSearch`. Set its `onSearchChange` callback to keep Find controls current. Use the `ready` event or template ref’s `instance`. See [Terminal Search](/api-reference#terminal-search) for Unicode behavior, progress, and limits.

## Selection and Copy

Native Copy preserves terminal line breaks and complete Unicode cells, joining confirmed Ghostty soft wraps. Call `getSelectionText()` on the underlying WTerm instance to read that text without writing the clipboard. See [Selection and Copy](/api-reference#selection-and-copy) for whitespace behavior, return values, and native-selection limits.

With Ghostty's current WASM binary, selections also follow output scrolling and resize/reflow while their text remains intact. Overwritten or discarded text, resets, and screen switches clear them. The same reference covers preservation limits and pending-frame behavior.

Use `await wt.selectAll()` on the WTerm instance to select all retained history and the active screen without mounting extra rows, then read `wt.getSelectionText()`. Cmd+A or Ctrl+Shift+A invokes the same action from terminal input. `wt.clearSelection()` cancels it. See [Select All](/api-reference#select-all) for capture limits, shortcuts, and when selection clears.

Double-click words or paths, or triple-click logical lines. The WTerm methods `selectWord({ row, col })` and `selectLine(row)` create the same native selection, including confirmed Ghostty soft wraps and unmounted history. See [Words and Logical Lines](/api-reference#words-and-logical-lines) for coordinates, boundaries, and limits.

Alt/Option-drag selects rectangular columns; Shift+Alt selects inside mouse-reporting applications. Call `selectRectangle(start, end)` on the WTerm instance with inclusive retained-row/cell corners. Rectangles preserve selected spaces and physical row breaks and clear on output or resize. See [Rectangular Selection](/api-reference#rectangular-selection) for copy shortcuts, coordinates, and limits.

## Output announcements

Opt in with `announceOutput` to announce terminal text changes politely while
input has focus. The option defaults to `false`; component prop changes apply
without restarting the terminal, and vanilla callers use
`term.setOutputAnnouncements(enabled)`. Announcements are batched and stop
when focus leaves input. Bursts are bounded to avoid overwhelming the speech
queue. See [Output announcements](/api-reference#output-announcements) for
capture limits, pause/resume behavior, and redraw semantics.

## Inactive panes

Use `renderingPaused` for panes that are not visible. Parsing and terminal
effects continue, and resuming paints the latest state without restarting.
In vanilla code, call `term.setRenderingPaused(paused)`; framework props are
reactive. Browser document visibility also pauses painting. See
[Inactive panes](/configuration#inactive-panes) for focus and capture behavior.

Application clipboard writes require an explicit host policy; see [Clipboard requests](/configuration#clipboard-requests).

## Shell integration

With Ghostty and a shell emitting OSC 133, `@shell-integration` receives the
latest prompt, input, running, or completion state and the last reported exit
code. Delivery continues while painting is paused; markers in one parser chunk
coalesce into current state. See [Shell integration](/configuration#shell-integration)
for the contract, shell setup, and workspace indicators.

Use the underlying `WTerm` instance’s `scrollToPrompt(-1)` and
`scrollToPrompt(1)` methods to navigate previous and next prompts in retained
history. See [Shell integration](/configuration#shell-integration) for viewport
behavior, unsupported cores, and inactive states.

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)

---
title: WebMCP
url: "https://wterm.dev/webmcp"
docs_index: /llms.txt
lastUpdated: 2026-11-09
navTitle: "WebMCP"
---

> For an index of all documentation, see [/llms.txt](/llms.txt).

[WebMCP](https://github.com/webmachinelearning/webmcp) lets agents running in the browser call tools that a page registers. Turn on `webmcp` and agents can find the page's terminals, read them, type commands, and press keys. They don't have to scrape the DOM or fake keyboard events.

WebMCP is off by default. In browsers without a WebMCP provider, the option does nothing.

## Try it

The terminal on the [wterm.dev](https://wterm.dev) homepage is registered as `demo`. It runs a sandboxed bash in the page with [just-bash](/just-bash), so an agent can type into it without touching a real machine. With [agent-browser](https://github.com/vercel-labs/agent-browser), which turns on WebMCP in the Chrome it launches:

```bash
agent-browser open https://wterm.dev
agent-browser webmcp invoke type_in_terminal --params '{"terminal":"demo","text":"ls","submit":true}'
agent-browser webmcp invoke read_terminal --params '{"terminal":"demo","waitForIdleMs":300}'
```

## Enabling

Pass `webmcp: true`, or an options object, when you create the terminal:

```ts
import { WTerm } from "@wterm/dom";

const term = new WTerm(element, {
  webmcp: { name: "shell", description: "Project shell" },
  onData: (data) => socket.send(data),
});
await term.init();
```

The framework components take the same value as a prop, and changes apply without restarting the terminal:

```tsx
<Terminal webmcp={{ name: "shell" }} onData={send} />
```

```vue
<Terminal :webmcp="{ name: 'shell' }" @data="send" />
```

```svelte
<Terminal webmcp={{ name: "shell" }} onData={send} />
```

In vanilla JavaScript, call `term.setWebMCP(options)` to change the options later, or `term.setWebMCP(false)` to withdraw the terminal. Tools are registered once the terminal initializes and removed when it is destroyed. Withdrawing or destroying a terminal also ends any call still in progress for it, so a waiting `read_terminal` cannot return output written afterwards.

<table>
  <thead>
    <tr>
      <th>
        Option
      </th>

      <th>
        Type
      </th>

      <th>
        Default
      </th>

      <th>
        Description
      </th>
    </tr>
  </thead>

  <tbody>
    <tr>
      <td>
        <code>name</code>
      </td>

      <td>
        <code>string</code>
      </td>

      <td>
        <code>"terminal"</code>
      </td>

      <td>
        Name agents use to address the terminal. If another terminal on the page already uses it, a suffix is added (

        <code>terminal-2</code>

        ,

        <code>terminal-3</code>

        ) and a warning is logged.
      </td>
    </tr>

    <tr>
      <td>
        <code>description</code>
      </td>

      <td>
        <code>string</code>
      </td>

      <td>
        —
      </td>

      <td>
        What the terminal is for, returned by

        <code>list\_terminals</code>

        . It helps agents pick the right terminal.
      </td>
    </tr>

    <tr>
      <td>
        <code>readOnly</code>
      </td>

      <td>
        <code>boolean</code>
      </td>

      <td>
        <code>false</code>
      </td>

      <td>
        Agents can read the terminal but cannot type into it or press keys.
      </td>
    </tr>
  </tbody>
</table>

## Tools

<table>
  <thead>
    <tr>
      <th>
        Tool
      </th>

      <th>
        Input
      </th>

      <th>
        Description
      </th>
    </tr>
  </thead>

  <tbody>
    <tr>
      <td>
        <code>list\_terminals</code>
      </td>

      <td>
        —
      </td>

      <td>
        Every registered terminal with its name, description,

        <code>readOnly</code>

        ,

        <code>focused</code>

        , size, cursor, alternate-screen state, scrollback row count, and, when reported, title, working directory, and shell integration state.
      </td>
    </tr>

    <tr>
      <td>
        <code>read\_terminal</code>
      </td>

      <td>
        <code>terminal?</code>

        ,

        <code>lines?</code>

        ,

        <code>waitFor?</code>

        ,

        <code>waitForIdleMs?</code>

        ,

        <code>timeoutMs?</code>
      </td>

      <td>
        Plain text of the visible screen, or the last

        <code>lines</code>

        rows (up to 10,000), ending at the cursor or the last line of text and reaching into scrollback, plus the same state as

        <code>list\_terminals</code>

        . It can wait first; see below.
      </td>
    </tr>

    <tr>
      <td>
        <code>type\_in\_terminal</code>
      </td>

      <td>
        <code>terminal?</code>

        ,

        <code>text</code>

        ,

        <code>submit?</code>
      </td>

      <td>
        Types text as if the user typed it, then presses Enter if

        <code>submit</code>

        is true. Enter comes 150 ms after the text, so programs that detect pastes by input speed, such as agent CLIs, don't treat it as a pasted newline. Multi-line text is pasted with bracketed paste when the program has enabled it. Otherwise, newlines are sent as Enter.
      </td>
    </tr>

    <tr>
      <td>
        <code>press\_terminal\_keys</code>
      </td>

      <td>
        <code>terminal?</code>

        ,

        <code>keys</code>
      </td>

      <td>
        Presses up to 64 keys in order, such as

        <code>"Ctrl+C"</code>

        ,

        <code>"Enter"</code>

        ,

        <code>"Shift+Tab"</code>

        ,

        <code>"ArrowUp"</code>

        ,

        <code>"F5"</code>

        , or a single character. Modifiers are

        <code>Ctrl</code>

        ,

        <code>Alt</code>

        ,

        <code>Shift</code>

        , and

        <code>Meta</code>

        . Keys are encoded for the program's current modes, including application cursor keys and the Kitty keyboard protocol. If any key name is unknown, nothing is pressed.
      </td>
    </tr>
  </tbody>
</table>

Input from agents follows the same path as keyboard input. It reaches `onData`, or is echoed locally when `onData` is not set, and it scrolls the terminal to the bottom. `type_in_terminal` and `press_terminal_keys` are registered only while at least one terminal accepts input.

### Waiting for output

Programs answer asynchronously, so `read_terminal` can wait before it reads:

- `waitFor`: wait until this exact text appears in the rows being read. Text that is already on screen counts, so pick text that only the new output will contain.
- `waitForIdleMs`: wait until no output has arrived for this many milliseconds. With `waitFor`, the idle period starts once the text appears.
- `timeoutMs`: the longest wait, 10,000 by default and at most 120,000. When it expires, the tool returns the current text with `timedOut: true` instead of failing.

A typical agent turn types a command and then waits for it to finish:

```json
{ "tool": "type_in_terminal", "input": { "terminal": "shell", "text": "npm test", "submit": true } }
{ "tool": "read_terminal", "input": { "terminal": "shell", "waitFor": "Tests:", "waitForIdleMs": 500, "timeoutMs": 60000 } }
```

### Results and errors

Every tool returns a JSON string. When a call fails, for example because a terminal name is unknown, the terminal is read-only, or an argument is invalid, the result is `{"error": "..."}` and the tool does not throw. Browser providers drop the messages of thrown errors, so an in-band result like MCP's `isError` is the only way an agent can see what went wrong and fix its call. A wait that the agent cancels still rejects with the cancellation reason.

## Several terminals

All terminals in a document share one set of tools, and each tool takes a `terminal` name. You can leave the name out when the page has one terminal, or when one terminal has focus. Otherwise the error lists the available names. Give each terminal a `name` and `description` so agents can tell them apart without trial and error.

## Security

An agent that can call `type_in_terminal` can do anything the person at the keyboard can do. If the terminal is connected to a shell, a submitted command really runs. Enable `webmcp` only on terminals you want agents to drive, and use `readOnly: true` for terminals they should only watch, such as logs or build output.

Terminal output comes from the programs running in it, so it can contain text written to mislead an agent. `list_terminals` and `read_terminal` set the `untrustedContentHint` annotation so agents treat their results as data, not instructions.

## Browser support

wterm registers tools with `document.modelContext` or `navigator.modelContext`, whichever provides `registerTool`. It checks for a provider when the terminal initializes, so load any WebMCP polyfill before that. Earlier drafts of the API return a handle with `unregister()` from `registerTool`, and wterm supports those as well.

## Testing with agent-browser

[agent-browser](https://github.com/vercel-labs/agent-browser) runs Chrome with WebMCP enabled and can call page tools directly:

```bash
agent-browser open https://my-app.localhost
agent-browser webmcp list
agent-browser --json webmcp invoke type_in_terminal --params '{"text":"echo hello","submit":true}'
agent-browser --json webmcp invoke read_terminal --params '{"waitFor":"hello","waitForIdleMs":200}'
```

---

For a semantic overview of all documentation, see [/sitemap.md](/sitemap.md)

For an index of all available documentation, see [/llms.txt](/llms.txt)