@beamterm/renderer

March 30, 2026 · View on GitHub

npm version License: MIT

High-performance WebGL2 terminal renderer achieving sub-millisecond render times through GPU-accelerated instanced rendering. Pure WASM + WebGL2 with zero runtime dependencies.

Features

  • Rich text styling — bold, italic, underline, strikethrough with 24-bit color
  • Batch updates — all cell changes are collected and uploaded to the GPU in a single pass
  • Two font atlas modes — pre-rasterized static atlas or on-demand dynamic atlas with any browser font
  • Built-in text selection — linear and block modes with clipboard integration and URL detection
  • Responsive — automatic terminal resizing with HiDPI support
  • TypeScript definitions included

Requires a browser with WebGL2 and WASM support (any modern browser).

Installation

npm install @beamterm/renderer

Or via CDN:

<script src="https://unpkg.com/@beamterm/renderer@latest/dist/cdn/beamterm.min.js"></script>
<script>
  await Beamterm.init();
  const renderer = new Beamterm.BeamtermRenderer('#terminal');
</script>

Quick Start

import {
  main as init,
  style,
  cell,
  BeamtermRenderer,
} from "@beamterm/renderer";

await init();

// Create renderer — uses the embedded static font atlas by default
const renderer = new BeamtermRenderer("#terminal");

// Or use a dynamic atlas that rasterizes any browser font on demand
// const renderer = BeamtermRenderer.withDynamicAtlas('#terminal', ['JetBrains Mono', 'Fira Code'], 16.0);

const { cols, rows } = renderer.terminalSize();
console.log(`Terminal: ${cols}x${rows} cells`);

const batch = renderer.batch();

batch.clear(0x1a1b26);
batch.text(
  2,
  1,
  "Hello, Beamterm!",
  style().bold().underline().fg(0x7aa2f7).bg(0x1a1b26),
);
batch.cell(0, 0, cell("🚀", style().fg(0xffffff)));
batch.fill(1, 3, 18, 1, cell("─", style().fg(0x565f89).bg(0x1a1b26)));

renderer.render();

API Reference

BeamtermRenderer

The main renderer class. Manages the WebGL2 context and rendering pipeline.

Constructors

SignatureDescription
new BeamtermRenderer(canvasSelector)Create with the embedded static font atlas
BeamtermRenderer.withDynamicAtlas(canvasSelector, fontFamilies, fontSize, autoResizeCss?)Create with a dynamic atlas using browser fonts
BeamtermRenderer.withStaticAtlas(canvasSelector, atlasData?, autoResizeCss?)Create with custom .atlas data (or null for default)

Rendering

MethodDescription
batch()Create a new Batch for buffering cell updates
render()Render the current frame to the canvas
resize(width, height)Resize the canvas and recalculate terminal dimensions
terminalSize()Returns { cols, rows } — terminal dimensions in cells
cellSize()Returns { width, height } — cell dimensions in pixels

Font Switching

Swap the font atlas at runtime. Existing cell content is preserved and translated to the new atlas.

MethodDescription
replaceWithDynamicAtlas(fontFamilies, fontSize)Switch to a dynamic atlas with the given fonts
replaceWithStaticAtlas(atlasData?)Switch to a static atlas (Uint8Array or null for default)

Selection & Mouse

MethodDescription
enableSelection(mode, trimWhitespace)Enable built-in text selection
enableSelectionWithOptions(mode, trimWhitespace, modifiers)Selection that requires modifier keys (e.g. Shift+click)
setMouseHandler(callback)Set a custom mouse event handler (receives MouseEvent)
getText(query)Extract text for a CellQuery region
findUrlAt(col, row)Detect a URL at the given cell position — returns UrlMatch or undefined
copyToClipboard(text)Copy text to the system clipboard
clearSelection()Clear any active selection
hasSelection()Check if there is an active selection

Batch

Buffers cell updates for efficient GPU upload. Create one per frame via renderer.batch().

MethodDescription
clear(bgColor)Clear the entire terminal with a background color
text(x, y, text, style)Write a string at (x, y) with uniform styling — fastest for text runs
cell(x, y, cellData)Update a single cell
cells(array)Update multiple cells — each element is [x, y, cellData]
fill(x, y, width, height, cellData)Fill a rectangular region

All changes are automatically uploaded when renderer.render() is called.

CellStyle

Fluent builder for text styling. Create one with style().

const heading = style().bold().fg(0x7aa2f7).bg(0x1a1b26);
const warning = style().bold().italic().fg(0xe0af68);
MethodDescription
fg(color)Foreground color (0xRRGGBB)
bg(color)Background color (0xRRGGBB)
bold()Bold
italic()Italic
underline()Underline
strikethrough()Strikethrough
bits(property) Combined style bits as a number

CellQuery

Describes a rectangular or linear region of cells for text extraction.

const query = new CellQuery(SelectionMode.Linear)
  .start(0, 2)
  .end(40, 5)
  .trimTrailingWhitespace(true);

const text = renderer.getText(query);

ModifierKeys

Modifier key flags for enableSelectionWithOptions().

// Require Shift+Click to start selection
renderer.enableSelectionWithOptions(
  SelectionMode.Linear,
  true,
  ModifierKeys.SHIFT,
);

// Require Ctrl+Shift+Click
renderer.enableSelectionWithOptions(
  SelectionMode.Block,
  true,
  ModifierKeys.CONTROL.or(ModifierKeys.SHIFT),
);
Static propertyDescription
ModifierKeys.NONENo modifier keys required
ModifierKeys.SHIFTShift key
ModifierKeys.CONTROLCtrl key
ModifierKeys.ALTAlt / Option key
ModifierKeys.METACmd / Windows key

Combine with .or(): ModifierKeys.SHIFT.or(ModifierKeys.ALT)

Enums

SelectionModeLinear (text flow, like a terminal) or Block (rectangular, like a text editor).

MouseEventTypeMouseDown, MouseUp, MouseMove, Click, MouseEnter, MouseLeave.

Helper Functions

FunctionDescription
main()Initialize the WASM module (typically imported as init)
style()Create a new CellStyle
cell(symbol, style)Create a Cell

Color Format

Colors are 24-bit RGB values: 0xRRGGBB.

const white = 0xffffff;
const red = 0xff0000;
const tokyoBg = 0x1a1b26;

Common Patterns

Animation Loop

function animate() {
  const batch = renderer.batch();
  batch.clear(0x1a1b26);
  batch.text(0, 0, `Frame: ${Date.now()}`, style().fg(0xc0caf5));
  renderer.render();
  requestAnimationFrame(animate);
}

Responsive Terminal

window.addEventListener("resize", () => {
  renderer.resize(window.innerWidth, window.innerHeight);
  redrawTerminal();
});

URL Detection

renderer.setMouseHandler((event) => {
  if (event.event_type === MouseEventType.Click) {
    const match = renderer.findUrlAt(event.col, event.row);
    if (match) window.open(match.url, "_blank");
  }
});

Examples

See the examples/ directory:

Performance Tips

  • Prefer batch.text() over multiple batch.cell() calls for strings with uniform styling
  • Use batch.fill() for large rectangular regions
  • Batch all updates within a single renderer.batch() / renderer.render() cycle
  • Reuse CellStyle objects when possible

License

MIT — see LICENSE for details.