Ghostty Terminal API Documentation #
Complete API reference for @cmux/ghostty-terminal - a terminal emulator using Ghostty's VT100 parser via WebAssembly.
Table of Contents #
- Installation
- Quick Start
- Terminal Class
- Options
- Addons
- Low-Level APIs
- Examples
- Migration from xterm.js
- Troubleshooting
Installation #
From Source #
git clone https://github.com/coder/ghostty-wasm.git
cd ghostty-wasm/task8
bun install
bun run build
Import in Your Project #
import { Terminal } from './lib/index.ts';
import { FitAddon } from './lib/addons/fit.ts';
Quick Start #
import { Terminal } from './lib/index.ts';
import { FitAddon } from './lib/addons/fit.ts';
// Create terminal instance
const term = new Terminal({
cols: 80,
rows: 24,
cursorBlink: true,
theme: {
background: '#1e1e1e',
foreground: '#d4d4d4',
}
});
// Add FitAddon for responsive sizing
const fitAddon = new FitAddon();
term.loadAddon(fitAddon);
// Open in DOM container
const container = document.getElementById('terminal');
await term.open(container);
// Fit to container size
fitAddon.fit();
// Write output
term.write('Hello, World!\r\n');
term.write('\x1b[1;32mGreen text\x1b[0m\r\n');
// Handle user input
term.onData(data => {
console.log('User typed:', data);
// Echo back
term.write(data);
});
Terminal Class #
The main terminal emulator class that integrates all components.
Constructor #
new Terminal(options?: ITerminalOptions)
Creates a new terminal instance with optional configuration.
Parameters:
options(optional): Configuration options (see ITerminalOptions)
Example:
const term = new Terminal({
cols: 80,
rows: 24,
cursorBlink: true,
fontSize: 14,
fontFamily: 'Monaco, monospace',
});
Methods #
open(parent: HTMLElement): Promise<void> #
Opens the terminal in a parent DOM element. This initializes all components (buffer, parser, renderer, input handler) and starts rendering.
Parameters:
parent: The DOM element to render the terminal into
Returns: Promise that resolves when terminal is ready
Example:
const container = document.getElementById('terminal');
await term.open(container);
Note: Must be called before any other terminal operations.
write(data: string): void #
Writes data to the terminal. Supports plain text and ANSI escape sequences.
Parameters:
data: String to write (may contain ANSI escape codes)
Example:
term.write('Hello, World!\r\n');
term.write('\x1b[1;31mRed bold text\x1b[0m\r\n');
term.write('Line 1\r\nLine 2\r\n');
ANSI Sequences Supported:
- Colors:
\x1b[30-37m(fg),\x1b[40-47m(bg),\x1b[90-97m(bright fg) - Styles:
\x1b[1m(bold),\x1b[3m(italic),\x1b[4m(underline) - Cursor:
\x1b[H(home),\x1b[<row>;<col>H(position) - Erase:
\x1b[2J(clear screen),\x1b[K(clear line) - 256-color:
\x1b[38;5;<n>m(fg),\x1b[48;5;<n>m(bg) - RGB:
\x1b[38;2;<r>;<g>;<b>m(fg),\x1b[48;2;<r>;<g>;<b>m(bg)
writeln(data: string): void #
Writes data followed by a newline (\r\n).
Parameters:
data: String to write
Example:
term.writeln('Line 1');
term.writeln('Line 2');
// Equivalent to:
// term.write('Line 1\r\n');
// term.write('Line 2\r\n');
clear(): void #
Clears the terminal screen (erases all content).
Example:
term.clear();
reset(): void #
Resets the terminal to initial state. Clears screen, resets cursor, and clears styles.
Example:
term.reset();
resize(cols: number, rows: number): void #
Resizes the terminal dimensions.
Parameters:
cols: New column countrows: New row count
Example:
term.resize(100, 30);
Note: Triggers onResize event.
focus(): void #
Gives keyboard focus to the terminal.
Example:
term.focus();
blur(): void #
Removes keyboard focus from the terminal.
Example:
term.blur();
loadAddon(addon: ITerminalAddon): void #
Loads an addon into the terminal.
Parameters:
addon: Addon instance implementingITerminalAddon
Example:
const fitAddon = new FitAddon();
term.loadAddon(fitAddon);
dispose(): void #
Disposes the terminal and cleans up resources. Removes from DOM and stops rendering.
Example:
term.dispose();
Note: Terminal cannot be reused after disposal.
Events #
onData: IEvent<string> #
Fired when user types in the terminal. Use this to send input to your backend (PTY, WebSocket, etc.).
Callback Parameter:
data: String containing user input (may include escape sequences for special keys)
Special Keys:
- Enter:
\r - Backspace:
\x7For\x08 - Tab:
\t - Escape:
\x1b - Arrow Up:
\x1b[A - Arrow Down:
\x1b[B - Arrow Right:
\x1b[C - Arrow Left:
\x1b[D
Example:
term.onData(data => {
if (data === '\r') {
console.log('User pressed Enter');
} else if (data === '\x7F') {
console.log('User pressed Backspace');
} else {
console.log('User typed:', data);
}
// Echo back
term.write(data);
});
WebSocket Example:
const ws = new WebSocket('ws://localhost:3000');
// Send user input to backend
term.onData(data => {
ws.send(JSON.stringify({ type: 'input', data }));
});
// Display backend output
ws.onmessage = (event) => {
const msg = JSON.parse(event.data);
term.write(msg.data);
};
onResize: IEvent<{ cols: number; rows: number }> #
Fired when terminal is resized.
Callback Parameter:
- Object with
colsandrowsproperties
Example:
term.onResize(({ cols, rows }) => {
console.log(`Terminal resized to ${cols}x${rows}`);
// Notify backend of new size
ws.send(JSON.stringify({ type: 'resize', cols, rows }));
});
onBell: IEvent<void> #
Fired when terminal receives a bell character (\x07).
Example:
term.onBell(() => {
console.log('Bell!');
// Play sound, show notification, etc.
new Audio('bell.mp3').play();
});
Properties #
cols: number #
Current number of columns (read-only).
Example:
console.log(`Terminal has ${term.cols} columns`);
rows: number #
Current number of rows (read-only).
Example:
console.log(`Terminal has ${term.rows} rows`);
element?: HTMLElement #
The DOM element containing the terminal (set after open()).
Example:
if (term.element) {
term.element.style.border = '1px solid #ccc';
}
textarea?: HTMLTextAreaElement #
The hidden textarea used for input (set after open()).
Options #
ITerminalOptions #
Configuration options for Terminal constructor.
interface ITerminalOptions {
cols?: number; // Default: 80
rows?: number; // Default: 24
cursorBlink?: boolean; // Default: false
cursorStyle?: 'block' | 'underline' | 'bar'; // Default: 'block'
theme?: ITheme; // Custom theme
scrollback?: number; // Default: 1000 lines
fontSize?: number; // Default: 15
fontFamily?: string; // Default: 'monospace'
allowTransparency?: boolean; // Default: false
wasmPath?: string; // Path to ghostty-vt.wasm
}
Details:
cols: Number of columns (characters per line)rows: Number of rows (lines visible on screen)cursorBlink: Whether cursor should blinkcursorStyle: Cursor appearance'block': Filled rectangle (default)'underline': Line under character'bar': Vertical line before character
theme: Color scheme (see ITheme)scrollback: Number of lines to keep in scroll bufferfontSize: Font size in pixelsfontFamily: CSS font-family stringallowTransparency: Enable transparent backgroundwasmPath: Path toghostty-vt.wasmfile (relative to HTML file)
Example:
const term = new Terminal({
cols: 120,
rows: 40,
cursorBlink: true,
cursorStyle: 'bar',
fontSize: 16,
fontFamily: "'Fira Code', 'Monaco', monospace",
scrollback: 5000,
theme: {
background: '#1e1e1e',
foreground: '#d4d4d4',
}
});
ITheme #
Color scheme configuration.
interface ITheme {
foreground?: string; // Default text color
background?: string; // Background color
cursor?: string; // Cursor color
cursorAccent?: string; // Cursor text color
selectionBackground?: string; // Selection highlight color
selectionForeground?: string; // Selection text color
// ANSI colors (0-15)
black?: string; // Color 0
red?: string; // Color 1
green?: string; // Color 2
yellow?: string; // Color 3
blue?: string; // Color 4
magenta?: string; // Color 5
cyan?: string; // Color 6
white?: string; // Color 7
brightBlack?: string; // Color 8
brightRed?: string; // Color 9
brightGreen?: string; // Color 10
brightYellow?: string; // Color 11
brightBlue?: string; // Color 12
brightMagenta?: string; // Color 13
brightCyan?: string; // Color 14
brightWhite?: string; // Color 15
}
All colors are CSS color strings (hex, rgb, rgba, color names).
Example Themes:
// Dark theme (VS Code)
const vscodeTheme = {
background: '#1e1e1e',
foreground: '#d4d4d4',
cursor: '#ffffff',
black: '#000000',
red: '#cd3131',
green: '#0dbc79',
yellow: '#e5e510',
blue: '#2472c8',
magenta: '#bc3fbc',
cyan: '#11a8cd',
white: '#e5e5e5',
brightBlack: '#666666',
brightRed: '#f14c4c',
brightGreen: '#23d18b',
brightYellow: '#f5f543',
brightBlue: '#3b8eea',
brightMagenta: '#d670d6',
brightCyan: '#29b8db',
brightWhite: '#ffffff',
};
// Dracula theme
const draculaTheme = {
background: '#282a36',
foreground: '#f8f8f2',
cursor: '#f8f8f2',
black: '#21222c',
red: '#ff5555',
green: '#50fa7b',
yellow: '#f1fa8c',
blue: '#bd93f9',
magenta: '#ff79c6',
cyan: '#8be9fd',
white: '#f8f8f2',
brightBlack: '#6272a4',
brightRed: '#ff6e6e',
brightGreen: '#69ff94',
brightYellow: '#ffffa5',
brightBlue: '#d6acff',
brightMagenta: '#ff92df',
brightCyan: '#a4ffff',
brightWhite: '#ffffff',
};
// Use theme
const term = new Terminal({ theme: draculaTheme });
Addons #
FitAddon #
Automatically resizes terminal to fit its container element.
Import #
import { FitAddon } from './lib/addons/fit.ts';
Usage #
const fitAddon = new FitAddon();
term.loadAddon(fitAddon);
// Manual fit
fitAddon.fit();
// Auto-fit on container resize
fitAddon.observeResize();
// Stop observing
fitAddon.dispose();
Methods #
fit(): void #
Calculates optimal dimensions and resizes terminal to fit container.
fitAddon.fit();
observeResize(): void #
Automatically calls fit() when container is resized (uses ResizeObserver).
fitAddon.observeResize();
dispose(): void #
Stops observing and cleans up resources.
fitAddon.dispose();
Example: Responsive Terminal #
const container = document.getElementById('terminal');
const term = new Terminal();
const fitAddon = new FitAddon();
term.loadAddon(fitAddon);
await term.open(container);
// Initial fit
fitAddon.fit();
// Auto-fit on window resize
fitAddon.observeResize();
Creating Custom Addons #
Implement the ITerminalAddon interface:
interface ITerminalAddon {
activate(terminal: ITerminalCore): void;
dispose(): void;
}
Example: Simple Logger Addon
class LoggerAddon implements ITerminalAddon {
private terminal?: ITerminalCore;
private dataListener?: IDisposable;
activate(terminal: ITerminalCore): void {
this.terminal = terminal;
// Subscribe to data events
this.dataListener = terminal.onData(data => {
console.log('Terminal data:', data);
});
}
dispose(): void {
this.dataListener?.dispose();
}
}
// Usage
const logger = new LoggerAddon();
term.loadAddon(logger);
Low-Level APIs #
For advanced usage, you can access low-level components directly.
ScreenBuffer #
Manages terminal screen state (2D grid of cells).
import { ScreenBuffer } from './lib/buffer.ts';
const buffer = new ScreenBuffer(80, 24, 1000);
buffer.writeString('Hello');
buffer.moveCursorTo(10, 5);
const line = buffer.getLine(0);
See buffer implementation for full API.
VTParser #
Parses VT100/ANSI escape sequences.
import { VTParser } from './lib/vt-parser.ts';
const parser = new VTParser(buffer);
parser.parse('Hello\x1b[1;31mRed\x1b[0m');
CanvasRenderer #
Renders terminal buffer to canvas.
import { CanvasRenderer } from './lib/renderer.ts';
const renderer = new CanvasRenderer(canvas, buffer, {
fontSize: 14,
fontFamily: 'monospace',
});
renderer.render();
Ghostty WASM #
Direct access to Ghostty's WASM parsers.
import { Ghostty, SgrParser, KeyEncoder } from './lib/ghostty.ts';
const ghostty = await Ghostty.load('./ghostty-vt.wasm');
// Parse SGR (colors)
const sgrParser = ghostty.createSgrParser();
for (const attr of sgrParser.parse([1, 31])) {
console.log('Bold red:', attr);
}
// Encode keys
const keyEncoder = ghostty.createKeyEncoder();
const bytes = keyEncoder.encode({
action: KeyAction.PRESS,
key: Key.A,
mods: Mods.CTRL,
});
Examples #
Example 1: Basic Echo Terminal #
import { Terminal } from './lib/index.ts';
const term = new Terminal({ cols: 80, rows: 24 });
await term.open(document.getElementById('terminal'));
term.write('Type something:\r\n$ ');
term.onData(data => {
if (data === '\r') {
term.write('\r\n$ ');
} else if (data === '\x7F') {
term.write('\b \b'); // Backspace
} else {
term.write(data); // Echo
}
});
Example 2: WebSocket Integration (File Browser) #
const term = new Terminal();
await term.open(document.getElementById('terminal'));
const ws = new WebSocket('ws://localhost:3001/ws');
let currentLine = '';
ws.onmessage = (event) => {
const msg = JSON.parse(event.data);
if (msg.type === 'output') {
term.write(msg.stdout.replace(/\n/g, '\r\n'));
if (msg.stderr) {
term.write(`\x1b[31m${msg.stderr}\x1b[0m`);
}
term.write('\r\n$ ');
}
};
term.onData(data => {
if (data === '\r') {
ws.send(JSON.stringify({ type: 'command', data: currentLine }));
term.write('\r\n');
currentLine = '';
} else if (data === '\x7F') {
if (currentLine.length > 0) {
currentLine = currentLine.slice(0, -1);
term.write('\b \b');
}
} else {
currentLine += data;
term.write(data);
}
});
Example 3: Custom Theme #
const term = new Terminal({
theme: {
background: '#282c34',
foreground: '#abb2bf',
cursor: '#528bff',
black: '#282c34',
red: '#e06c75',
green: '#98c379',
yellow: '#e5c07b',
blue: '#61afef',
magenta: '#c678dd',
cyan: '#56b6c2',
white: '#abb2bf',
brightBlack: '#5c6370',
brightRed: '#e06c75',
brightGreen: '#98c379',
brightYellow: '#e5c07b',
brightBlue: '#61afef',
brightMagenta: '#c678dd',
brightCyan: '#56b6c2',
brightWhite: '#ffffff',
}
});
Example 4: Progress Bar #
function showProgress(percent: number) {
const width = 40;
const filled = Math.floor(width * percent / 100);
const empty = width - filled;
const bar = '\x1b[32m' + '█'.repeat(filled) +
'\x1b[90m' + '░'.repeat(empty) + '\x1b[0m';
term.write(`\r[${bar}] ${percent}%`);
}
// Animate
let progress = 0;
const interval = setInterval(() => {
showProgress(progress);
progress += 5;
if (progress > 100) {
clearInterval(interval);
term.write('\r\n\x1b[32mComplete!\x1b[0m\r\n');
}
}, 100);
Migration from xterm.js #
This library provides an xterm.js-compatible API for easy migration.
API Compatibility #
| Feature | xterm.js | ghostty-terminal | Notes |
|---|---|---|---|
new Terminal(options) |
✅ | ✅ | Same API |
term.open(parent) |
✅ | ✅ | Returns Promise in ghostty-terminal |
term.write(data) |
✅ | ✅ | Same |
term.writeln(data) |
✅ | ✅ | Same |
term.onData |
✅ | ✅ | Same |
term.onResize |
✅ | ✅ | Same |
term.resize(cols, rows) |
✅ | ✅ | Same |
term.clear() |
✅ | ✅ | Same |
term.reset() |
✅ | ✅ | Same |
term.dispose() |
✅ | ✅ | Same |
FitAddon |
✅ | ✅ | Same API |
| Selection API | ✅ | ❌ | Not yet implemented |
term.scrollToBottom() |
✅ | ❌ | Not yet implemented |
term.scrollLines(n) |
✅ | ❌ | Not yet implemented |
| Weblinks addon | ✅ | ❌ | Not yet implemented |
| Search addon | ✅ | ❌ | Not yet implemented |
Migration Example #
Before (xterm.js):
import { Terminal } from 'xterm';
import { FitAddon } from 'xterm-addon-fit';
const term = new Terminal();
const fitAddon = new FitAddon();
term.loadAddon(fitAddon);
term.open(document.getElementById('terminal'));
fitAddon.fit();
After (ghostty-terminal):
import { Terminal } from './lib/index.ts';
import { FitAddon } from './lib/addons/fit.ts';
const term = new Terminal();
const fitAddon = new FitAddon();
term.loadAddon(fitAddon);
await term.open(document.getElementById('terminal')); // Note: async
fitAddon.fit();
Key Differences:
term.open()is async (returns Promise) - addawait- Import paths are different
- Some addons not yet available
Troubleshooting #
Terminal doesn't appear #
Problem: Terminal container is empty
Solutions:
- Make sure you
await term.open(container) - Check container has non-zero dimensions
- Check console for errors
const container = document.getElementById('terminal');
console.log('Container size:', container.offsetWidth, container.offsetHeight);
await term.open(container);
WASM loading error #
Problem: Failed to fetch ghostty-vt.wasm
Solutions:
- Verify
ghostty-vt.wasmexists in correct location - Serve via HTTP server (not file://)
- Set correct
wasmPathin options
const term = new Terminal({
wasmPath: '/path/to/ghostty-vt.wasm'
});
Colors not displaying #
Problem: ANSI colors show as plain text
Solutions:
- Verify escape sequences are correct:
\x1b[31mnot\\x1b[31m - Check theme colors are set
- Use
\r\nfor newlines, not just\n
// ❌ Wrong
term.write('\\x1b[31mRed\\x1b[0m\n');
// ✅ Correct
term.write('\x1b[31mRed\x1b[0m\r\n');
Input not working #
Problem: Keyboard input doesn't trigger onData
Solutions:
- Make sure terminal is focused:
term.focus() - Check
onDatalistener is attached - Click on terminal to give it focus
term.onData(data => {
console.log('Got data:', data);
});
term.focus();
Poor performance #
Problem: Rendering is slow or laggy
Solutions:
- Reduce terminal size (cols × rows)
- Limit output rate (buffer large writes)
- Reduce scrollback buffer size
// Limit output rate
function writeAsync(data: string) {
const chunks = data.match(/.{1,1000}/g) || [];
let i = 0;
function writeNext() {
if (i < chunks.length) {
term.write(chunks[i++]);
setTimeout(writeNext, 10);
}
}
writeNext();
}
FitAddon not working #
Problem: Terminal doesn't resize to fit container
Solutions:
- Make sure container has explicit dimensions (CSS)
- Call
fitAddon.fit()after opening terminal - Use
fitAddon.observeResize()for automatic resizing
#terminal {
width: 100%;
height: 500px; /* Must have explicit height */
}
await term.open(container);
fitAddon.fit(); // Call after open()
Additional Resources #
License #
See project LICENSE (AGPL-3.0)