Web terminal
Run Termy's terminal engine in the browser with the @termysh npm packages.
The @termysh packages are new and pre-1.0. The API can change between
releases, so pin a version and test upgrades.
The @termysh packages bring Termy's terminal engine to the web. The parser,
grid, scrollback, keyboard and mouse encoders, kitty protocols, box-drawing
geometry and themes are the same Rust code the desktop app runs, compiled to
WebAssembly. Use them to build a browser terminal or to replace xterm.js.
Choose a package
| Package | What it is | Use it when |
|---|---|---|
@termysh/web | Browser terminal with renderer, input, selection, links and images | You are building something new |
@termysh/xterm | xterm.js-compatible API on top of @termysh/web | You are replacing @xterm/xterm |
@termysh/core | WebAssembly engine with a typed headless API | You render yourself, run in a worker or Node, or test terminal output |
@termysh/pty | Pseudo-terminals for Node.js and Bun, a drop-in replacement for node-pty | Your server runs the shell or program the terminal shows |
Connect to a shell
A complete PTY server and browser client over WebSocket.
Frameworks and bundlers
React, Next.js, Vite, webpack, esbuild and serving the wasm file.
Options
Fonts, cursor, scrolling, input, links and every other setting.
Themes
Bundled Termy themes, xterm.js themes and overrides.
Migrate from xterm.js
Swap the import and keep your existing code.
Headless core
Drive the engine directly from a worker, Node or your own renderer.
PTY for Node.js
Run shells and programs on the server with @termysh/pty.
Troubleshooting
Blank terminals, wasm loading errors and build failures.
Live demo
Try the web terminal in your browser.
Install
npm install @termysh/web
# or: bun add @termysh/web / pnpm add @termysh/web@termysh/web depends on @termysh/core, which ships the termy.wasm module.
There is no stylesheet to import.
Quick start
<div id="terminal" style="height: 400px"></div>import { Terminal } from '@termysh/web'
const term = new Terminal({ theme: 'tokyo-night', fontSize: 13, cursorBlink: true })
term.open(document.getElementById('terminal')!)
term.write('Hello from \x1b[1;32mTermy\x1b[0m\r\n')
term.onData((data) => console.log('send to the host:', JSON.stringify(data)))The terminal fills its parent element and, with autoFit (on by default),
resizes its grid whenever the parent changes size. Give the parent a height.
To run a real shell, connect write and onData to a PTY on a server. The
Connect to a shell guide has a complete
server and client.
Essentials
These points cover most integration bugs:
- The parent needs a size. The terminal is
width: 100%; height: 100%of the element you pass toopen. A parent with no height renders nothing. - The constructor is synchronous; the engine is not. The WebAssembly
module loads in the background.
write,resize,pasteand other calls made before it is ready are queued. Awaitterm.ready(or useawait Terminal.create(options)) before you read state such asgetLine,cursororbuiltinThemeIds(). - Send everything from
onDatato the host. It carries keystrokes, pastes and the terminal's protocol replies (cursor position reports, device attributes, color queries). Programs likevimwait for those replies. - Send every
onResizeto the host. Programs lay out for the PTY's size, not the terminal's. Forward the newcolsandrowsto the PTY. - Call
dispose()when you are done. It removes the element, stops timers and frees the engine's memory. In React, do it in the effect cleanup. - Do not parse escape sequences yourself. Write raw bytes in and let the
engine handle them. Use events such as
onTitleChangeandonCwdChangefor the metadata programs send.
Load the WebAssembly module
By default termy.wasm is resolved next to @termysh/core with
new URL('./termy.wasm', import.meta.url). Vite, Next.js and webpack emit the
file automatically, with small caveats for some versions. See
Frameworks and bundlers.
To serve the file yourself, copy node_modules/@termysh/core/dist/termy.wasm
to your static assets and pass its URL:
new Terminal({ wasm: '/assets/termy.wasm' })
// or, once at startup, before creating terminals:
import { init } from '@termysh/web'
await init('/assets/termy.wasm')Serve it with the application/wasm content type for the fastest startup.
API
const term = new Terminal(options?) // or: await Terminal.create(options)
term.ready // Promise<void>, resolves when the engine is loaded
term.open(parent) // mount; throws if already open
term.write(data, callback?) // string or Uint8Array; callback runs once parsed
term.writeln(data)
term.paste(text) // honors bracketed paste
term.input(text) // as if the user typed it (fires onData)
term.resize(cols, rows)
term.fit() // resize to the container now
term.proposeDimensions() // { cols, rows } that would fit, without resizing
term.setOptions({ fontSize: 15 }) // any option, at runtime
term.setOption(key, value) / term.getOption(key)
term.focus() / term.blur() / term.hasFocus
term.clear() // clear scrollback, keep the prompt line
term.reset() // full reset (RIS)
term.processExited() // tell the terminal the host process ended
term.scrollLines(n) / term.scrollPages(n) / term.scrollToTop() / term.scrollToBottom() / term.scrollToLine(line)
term.select(col, line, length) / term.selectLines(start, end) / term.selectAll() / term.clearSelection()
term.getSelection() / term.hasSelection() / term.getSelectionPosition()
term.getLine(line) / term.isLineWrapped(line)
term.cols / term.rows / term.cursor / term.historySize / term.viewportY / term.modes / term.title
term.attachCustomKeyEventHandler((event) => true) // return false to stop the terminal handling a key
term.element / term.textarea / term.cellSize
term.core // the underlying TermyCore, for advanced use
term.dispose()Buffer lines are absolute: 0 is the oldest scrollback line and historySize
is the first line of the live screen. The text on the cursor's line is
term.getLine(term.historySize + term.cursor!.row) once term.ready has resolved.
Events
Every event is a function that takes a listener and returns a disposable:
const subscription = term.onTitleChange((title) => (document.title = title))
subscription.dispose()| Event | Payload | Fires when |
|---|---|---|
onData | string | Input for the host: keys, paste, protocol replies |
onBinary | string | Legacy X10 mouse reports that are not valid UTF-8, as a binary string |
onBytes | Uint8Array | Every byte sent to the host, whatever its encoding |
onResize | { cols, rows } | The grid size changes |
onTitleChange | string | The application sets the title (OSC 0/2) |
onBell | BEL | |
onCwdChange | string | OSC 7 working directory |
onProgress | { state, value } | OSC 9;4 progress |
onProgramStatus | ProgramStatusRecord[] | OSC 7501 program status changes. See Program status |
onShellIntegration | string | OSC 133 prompt and command marks |
onClipboard | { selection, text } | OSC 52 clipboard write |
onKey | { key, domEvent } | A key produced input |
onScroll | number | The viewport moves |
onRender | { start, end } | Rows were repainted |
onSelectionChange, onCursorMove, onWriteParsed, onFocus, onBlur | As named |
onBytes is the simplest choice when your transport carries bytes: it
includes everything onData and onBinary send.
Protocol support
The web terminal inherits the desktop engine's protocol support:
- xterm and VT control sequences, 256-color and truecolor
- Styled and colored underlines (curly, dotted, dashed, double)
- OSC 8 hyperlinks, plus plain URL detection
- OSC 4/10/11/12 color changes and queries, OSC 7, OSC 52, OSC 133, OSC 9;4, OSC 7501
- Synchronized output (mode 2026), bracketed paste, focus events
- Mouse tracking with default, UTF-8 and SGR encodings
- The kitty keyboard protocol, including release events
- The kitty graphics protocol: RGBA, RGB and PNG images, placements, z-index layering, animation and Unicode placeholders
Not yet supported: sixel and iTerm2 inline images, kitty OSC 5522 clipboard, and xterm.js decorations, custom link providers and parser hooks.
For AI coding agents
Every page in these docs is available as Markdown: add .md to the URL, for
example /docs/developer/web/connect.md. /llms.txt lists all
pages and /llms-full.txt contains the full text. The
package READMEs on npm carry the same essentials, and the published type
declarations document every option and method.