npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

react-xterm-shell

v0.3.2

Published

A small React shell around xterm.js: a useXTerm hook + XTerm component with a stable imperative controller, automatic fitting, and opt-in webgl/web-links/unicode11 addons.

Readme

react-xterm-shell

Demo | npm package | Example source

Beta — under active development; the API may change between minor versions until 1.0.

A small React shell around xterm.js. It is a React shell around xterm, not a React renderer for terminal cells — xterm owns the grid, parsing, and rendering; this gives you the lifecycle, a stable imperative controller, automatic fitting, and opt-in addons as ordinary React.

The demo is a Vite web page with theme switching, transport telemetry, web-links/unicode11 examples, and a mock backend filesystem users can mutate from the terminal.

  • useXTerm() — creates and owns the xterm instance behind a stable controller.
  • <XTerm /> — the DOM mount point.
  • TerminalProvider / useTerminalController() — drive the terminal from surrounding UI without prop drilling.
  • Automatic sizing via FitAddon + ResizeObserver.
  • Opt-in webgl (with DOM fallback), web-links, and unicode11 addons, on by default.

It does not include a transport — wire onData/onResize to your own WebSocket/PTY backend. That keeps the wrapper reusable across rosbridge, a PTY service, SSH, or a local shell.

Install

npm install react-xterm-shell @xterm/xterm react@^19

React 19 and @xterm/xterm are peer dependencies. Import the xterm stylesheet once in your app:

import "@xterm/xterm/css/xterm.css";

Quick start

import { useEffect, useRef } from "react";
import { useXTerm, XTerm, type XTermHandle } from "react-xterm-shell";
import "@xterm/xterm/css/xterm.css";

export function TerminalPanel() {
  const terminalRef = useRef<XTermHandle | null>(null);
  const terminal = useXTerm({
    onData: (data) => terminalRef.current?.write(data === "\r" ? "\r\n$ " : data),
    theme: { background: "#1a1b26", foreground: "#a9b1d6" }
  });
  terminalRef.current = terminal;

  useEffect(() => {
    terminal.write("react-xterm-shell\r\n$ ");
    terminal.focus();
  }, [terminal]);

  return <XTerm terminal={terminal} style={{ width: "100%", height: 420 }} />;
}

External controls

useXTerm returns a stable controller, so surrounding UI can drive it imperatively without re-rendering as bytes stream:

import { useXTerm, XTerm, TerminalProvider, useTerminalController } from "react-xterm-shell";

function SessionActions() {
  const term = useTerminalController();
  return (
    <div>
      <button onClick={() => term.write("deploy --target staging\r\n")}>Insert command</button>
      <button onClick={term.clear}>Clear</button>
      <button onClick={term.focus}>Focus</button>
    </div>
  );
}

function Panel() {
  const terminal = useXTerm({ onData: send });
  return (
    <TerminalProvider value={terminal}>
      <SessionActions />
      <XTerm terminal={terminal} className="h-[420px]" />
    </TerminalProvider>
  );
}

Backend wiring

The package does not include a transport. Connect onData and onResize to a PTY, SSH, container, or WebSocket service, and stream backend output into terminal.write():

function RemoteShell({ socket }: { socket: WebSocket }) {
  const terminal = useXTerm({
    onData: (data) => socket.send(JSON.stringify({ type: "input", data })),
    onResize: ({ cols, rows }) =>
      socket.send(JSON.stringify({ type: "resize", cols, rows }))
  });

  socket.onmessage = (event) => terminal.write(event.data);

  // Re-send the size once the socket is open. The mount-time `onResize` fires
  // before the socket connects, so that first resize is dropped — without this
  // the PTY stays at its connect-time default and the shell renders narrower
  // than the pane.
  socket.onopen = () => {
    const size = terminal.getDimensions();
    if (size) socket.send(JSON.stringify({ type: "resize", ...size }));
  };

  return <XTerm terminal={terminal} className="h-[420px]" />;
}

Sizing gotcha: the terminal fits at mount, so the first onResize fires before your transport is connected and that size is lost. Always re-send the size on (re)connect using getDimensions(), as shown above.

API

useXTerm(options?) => XTermHandle

| Option | Type | Default | Notes | |--------|------|---------|-------| | onData | (data: string) => void | — | User keystrokes (xterm onData). | | onResize | (size: { cols, rows }) => void | — | Fires when the grid resizes. | | theme | ITheme | — | xterm color theme. | | fontSize | number | 13 | | | scrollback | number | 1000 | | | cursorBlink | boolean | true | | | webgl | boolean | true | GL renderer; falls back to DOM on context loss. | | webLinks | boolean | true | Clickable URLs. | | unicode11 | boolean | true | Correct width for box-drawing / emoji. | | options | ITerminalOptions | — | Merged over the above. | | addons | ITerminalAddon[] | — | Extra addons (e.g. a search addon). |

The returned XTermHandle has attach (the callback ref for <XTerm>), a live term getter, and write / clear / reset / focus / fit / getDimensions (clear keeps the prompt line; reset blanks the screen and drops scrollback; getDimensions returns the current { cols, rows } or null before attach — see the sizing gotcha under Backend wiring). The handle is stable across renders; callbacks are read through refs, so passing fresh onData / onResize each render does not remount the terminal.

<XTerm terminal={handle} className? style? />

Renders the mount element. The terminal.attach callback ref is the whole integration surface.

Addon notes

@xterm/addon-fit, -web-links, -unicode11, and -webgl are bundled as dependencies and pinned to the xterm-5 line. WebGL loads after open() and is guarded: if the GL context is unavailable or lost, it disposes and the DOM renderer takes over. Disable any of them via the webgl / webLinks / unicode11 options.

License

MIT