mrkeyoor.com_
Sat 08 Aug 22:00 UTC
npmWeb Frontendupdated 08 Aug 2026

@xterm/xterm

@xterm/xterm is a browser terminal emulator: it interprets terminal control sequences, maintains screen and scrollback buffers, renders text, handles keyboard, mouse, selection, IME, Unicode, and accessibility behavior, and exposes input and resize events. It is the display and interaction half of a web terminal, not a shell or process runner. A real interactive session still needs a server-side pseudoterminal, such as node-pty, plus a transport that sends process output to Terminal.write and terminal input back to the process.

Verdict

The default serious choice for a full VT-style terminal in a browser, backed by years of use in major developer tools. Do not mistake the polished renderer for a complete remote-shell system; the backend PTY, transport, access control, resizing, lifecycle, and reconnect behavior are your project.

API stability4/5The stable Terminal surface uses long-lived operations such as open, write, onData, onResize, loadAddon, clear, reset, selection, and dispose, and the declaration file distinguishes experimental APIs from normal semver commitments. Major releases still require attention. Version 6 changed the viewport and scrollbar, moved overviewRulerWidth under overviewRuler, removed windowsMode and fastScrollModifier, removed the canvas renderer, and changed an Alt key behavior that embedders may need to recreate.
Docs4/5The README clearly separates terminal emulation from shells, shows the minimum CSS and JavaScript setup, demonstrates bidirectional PTY wiring, lists every maintained addon, states the evergreen-browser policy, explains headless use, and warns about experimental and beta compatibility. The full public contract is accurately documented in a large TypeScript declaration file. The weak point is discoverability: many production details such as resize timing, reconnect design, addon disposal, renderer fallback, and framework lifecycle integration are spread across API comments, addon READMEs, releases, and issues.
Maintenance5/5The repository was pushed on August 5, 2026, GitHub reports 225 open issues and pull requests for a large terminal engine and its addon suite, and the 6.0.0 release shipped in December 2025 with parser, viewport, rendering, accessibility, performance, ESM, hyperlink, image, IME, and memory fixes. Continuous beta builds feed the primary VS Code integration, while stable releases are cut as needed. The project is neither archived nor dependent on a single narrow wrapper.
Ecosystem5/5The measured week recorded 3,682,649 downloads, the repository has 21,032 stars, and the README lists production use in VS Code, JupyterLab, Hyper, Replit, Portainer, Proxmox, cloud shells, web SSH clients, and developer platforms. Official addons cover attach, clipboard, fit, images, ligatures, progress, search, serialization, Unicode, web fonts, links, and WebGL. The separate @xterm/headless package extends the same terminal model to server-side state retention.

Use it if

  • You are building a browser IDE, SSH console, container exec view, serial console, or terminal log viewer that must understand VT control sequences
  • You need the terminal engine used by projects such as VS Code, JupyterLab, Hyper, and browser-based infrastructure consoles
  • You need selectable output, keyboard and mouse input, CJK and IME support, theming, screen-reader mode, and configurable contrast
  • You want an addon system for fitting, WebSocket attachment, search, links, WebGL rendering, serialization, clipboard, images, or richer Unicode
Skip it if

Setup reality

The npm package has no runtime dependencies, ships TypeScript declarations plus CommonJS and ESM files, and installs with npm install @xterm/xterm. You still must import @xterm/xterm/css/xterm.css; without it, the internal viewport, selection, helper textarea, and rows will not lay out correctly. The host element needs real dimensions before terminal.open runs. Most applications also install @xterm/addon-fit, load it, and call fit after the container becomes visible and after fonts settle. xterm.js does not launch bash, SSH, or containers. A server must own a pseudoterminal or other process, authenticate access, transport output to terminal.write, accept onData input, and apply onResize dimensions back to the PTY. A WebSocket only carries bytes; it does not provide process isolation, authorization, reconnect state, or flow control. For reconnectable sessions, the README points to @xterm/headless plus the serialize addon as one way to preserve terminal state on the process host. The browser package should be created only after a DOM node exists, so React, Next.js, and SSR integrations need a client effect and cleanup. Dispose the terminal, addons, ResizeObserver, WebSocket listeners, and every event disposable when the view unmounts. Addons must match the core major, and version 6 removed the old canvas renderer in favor of the default DOM renderer or @xterm/addon-webgl. Only enable screen input when intended; disableStdin is useful for read-only logs. Test keyboard shortcuts, IME, screen readers, font loading, WebGL context loss, resize loops, and mobile viewport behavior on actual target browsers.

Patterns

Open a terminal and write styled textopen-basic-terminal

import { Terminal } from '@xterm/xterm';
import '@xterm/xterm/css/xterm.css';

const host = document.querySelector<HTMLDivElement>('#terminal')!;
const terminal = new Terminal({ cursorBlink: true });
terminal.open(host);
terminal.write('Hello from \x1b[1;32mxterm.js\x1b[0m\r\n');

The host must have a non-zero size, and the package CSS is required for correct terminal layout. Use carriage return plus newline for a new line at column zero.

Wire terminal input and output to a WebSocketconnect-websocket-manually

const socket = new WebSocket('wss://example.com/terminal/session-123');
socket.binaryType = 'arraybuffer';

const input = terminal.onData((data) => {
  if (socket.readyState === WebSocket.OPEN) socket.send(data);
});

socket.addEventListener('message', (event) => {
  terminal.write(typeof event.data === 'string' ? event.data : new Uint8Array(event.data));
});

The server still needs an authenticated PTY or process. Preserve byte encoding end to end, and dispose the onData subscription when the session closes.

Use the official WebSocket attach addonattach-websocket-addon

import { Terminal } from '@xterm/xterm';
import { AttachAddon } from '@xterm/addon-attach';

const terminal = new Terminal();
const socket = new WebSocket('wss://example.com/terminal/session-123');
const attach = new AttachAddon(socket);
terminal.loadAddon(attach);

Install @xterm/addon-attach separately. The addon connects messages, but authentication, process ownership, reconnection, and server cleanup remain application concerns.

Refit when the host element changes sizefit-to-container

import { FitAddon } from '@xterm/addon-fit';

const fit = new FitAddon();
terminal.loadAddon(fit);
terminal.open(host);
fit.fit();

const observer = new ResizeObserver(() => fit.fit());
observer.observe(host);

Install @xterm/addon-fit separately. Avoid fitting a hidden or zero-size element, and disconnect the ResizeObserver during cleanup.

Send terminal dimensions to the backendresize-backend-pty

const resize = terminal.onResize(({ cols, rows }) => {
  if (socket.readyState === WebSocket.OPEN) {
    socket.send(JSON.stringify({ type: 'resize', cols, rows }));
  }
});

The backend must apply these dimensions to its pseudoterminal. Fitting only the browser grid leaves full-screen programs such as vim and tmux at the old size.

Detect web links with the official addonadd-clickable-links

import { WebLinksAddon } from '@xterm/addon-web-links';

const links = new WebLinksAddon((event, uri) => {
  event.preventDefault();
  if (new URL(uri).protocol === 'https:') window.open(uri, '_blank', 'noopener,noreferrer');
});
terminal.loadAddon(links);

Treat terminal output as untrusted. Validate allowed protocols and open new tabs with noopener and noreferrer rather than blindly navigating to detected text.

Search terminal scrollbacksearch-buffer

import { SearchAddon } from '@xterm/addon-search';

const search = new SearchAddon();
terminal.loadAddon(search);

const found = search.findNext('error', {
  caseSensitive: false,
  wholeWord: true,
});

Install @xterm/addon-search separately. Search operates on the terminal buffer and selection, not on the original process log outside retained scrollback.

Load WebGL rendering with a fallbackenable-webgl-renderer

import { WebglAddon } from '@xterm/addon-webgl';

const webgl = new WebglAddon();
webgl.onContextLoss(() => {
  webgl.dispose();
});
try {
  terminal.loadAddon(webgl);
} catch {
  webgl.dispose(); // terminal keeps the default DOM renderer
}

Version 6 removed the old canvas renderer. WebGL is optional, requires WebGL2, and should fall back to the built-in DOM renderer on setup failure or context loss.

Use the terminal as a read-only log viewercreate-read-only-log

const terminal = new Terminal({
  disableStdin: true,
  convertEol: true,
  scrollback: 5000,
});
terminal.open(host);
terminal.write(logChunk);

convertEol moves the cursor to column zero on line feeds, which is helpful for plain logs but can change intentional terminal output semantics.

Enable screen-reader output and contrast checksconfigure-accessibility

const terminal = new Terminal({
  screenReaderMode: true,
  minimumContrastRatio: 4.5,
  theme: {
    background: '#111827',
    foreground: '#f9fafb',
    cursor: '#fbbf24',
  },
});

Screen-reader mode has a rendering cost. Test the full keyboard and announcement flow with the assistive technologies your users actually run.

Reserve an application keyboard shortcutintercept-keyboard-shortcut

terminal.attachCustomKeyEventHandler((event) => {
  const isCopy = (event.ctrlKey || event.metaKey) && event.key.toLowerCase() === 'c';
  if (isCopy && terminal.hasSelection()) {
    navigator.clipboard.writeText(terminal.getSelection());
    return false;
  }
  return true;
});

Returning false prevents xterm.js from processing the event. Do not steal Ctrl+C when there is no selection, because terminal users expect it to send an interrupt.

Clean up a terminal and its integrationsdispose-terminal-session

function closeTerminal() {
  observer.disconnect();
  input.dispose();
  resize.dispose();
  attach.dispose();
  socket.close(1000, 'terminal closed');
  terminal.dispose();
}

Dispose event registrations, addons, observers, transport listeners, and the Terminal itself. Also tell the server to release its process or session if closing the socket does not do that automatically.

Alternatives

PackageRegistryPick it when
@xterm/headlessnpmChoose it to parse and retain terminal state in Node.js without rendering a browser UI.
jquery.terminalnpmChoose it for a command-interpreter UI with prompts and commands when full PTY terminal emulation is unnecessary.
hterm-umdjsnpmChoose it when Chromium's hterm behavior or a UMD package is a firm compatibility requirement.
terminal-kitnpmChoose it to build an interactive terminal application that runs in a real Node.js TTY rather than in a browser.