Skip to main content

Node.js Platform

Using unblessed in Node.js applications.

Installation

npm install @unblessed/node
# or
pnpm add @unblessed/node
# or
yarn add @unblessed/node

Quick Start

import { Screen, Box } from "@unblessed/node";

// Create screen
const screen = new Screen({
smartCSR: true,
title: "My TUI App",
});

// Create widget
const box = new Box({
parent: screen,
top: "center",
left: "center",
width: "50%",
height: "50%",
content: "Hello from Node.js!",
border: { type: "line" },
style: { border: { fg: "cyan" } },
});

// Global key handler
screen.key(["q", "C-c"], () => process.exit(0));

// Render
screen.render();

Runtime Auto-Initialization

@unblessed/node automatically initializes the Node.js runtime when imported:

// Happens automatically on import
import { Screen } from "@unblessed/node";

// Runtime is ready - no manual setup needed!
const screen = new Screen();

How it works: The package runs auto-init.ts which calls setRuntime(new NodeRuntime()) before any widgets are created.

Node.js-Specific Features

Direct TTY Access

Full access to Node.js TTY capabilities:

import { Screen } from "@unblessed/node";

const screen = new Screen({
input: process.stdin, // Custom input stream
output: process.stdout, // Custom output stream
terminal: "xterm-256color", // Terminal type
});

// Access underlying TTY
const tty = screen.program.term;
console.log("Terminal:", tty);

Process Integration

Integrates with Node.js process:

// Exit handlers
process.on("exit", () => {
screen.destroy();
console.log("Cleanup complete");
});

// Signal handlers
process.on("SIGINT", () => {
screen.destroy();
process.exit(0);
});

process.on("SIGTERM", () => {
screen.destroy();
process.exit(0);
});

// Uncaught errors
process.on("uncaughtException", (error) => {
screen.destroy();
console.error("Error:", error);
process.exit(1);
});

File System Access

Read files for content or configuration:

import { Box } from "@unblessed/node";
import { readFileSync } from "fs";

const content = readFileSync("./data.txt", "utf8");

const box = new Box({
parent: screen,
content: content,
scrollable: true,
});

Environment Variables

Access environment configuration:

const isDev = process.env.NODE_ENV === "development";
const debugMode = process.env.DEBUG === "true";

const screen = new Screen({
debug: debugMode,
log: isDev ? "./debug.log" : undefined,
});

Screen Options

Full Option List

interface ScreenOptions {
// Program options
input?: NodeJS.ReadStream; // Input stream (default: process.stdin)
output?: NodeJS.WriteStream; // Output stream (default: process.stdout)
terminal?: string; // Terminal type (default: auto-detect)

// Display options
smartCSR?: boolean; // Smart cursor save/restore (default: false)
fastCSR?: boolean; // Fast CSR (default: false)
resizeTimeout?: number; // Resize debounce (default: 300ms)
useBCE?: boolean; // Use background color erase (default: false)
fullUnicode?: boolean; // Full Unicode support (default: false)
dockBorders?: boolean; // Dock borders (default: false)

// Behavior options
cursor?: {
artificial?: boolean; // Artificial cursor (default: false)
shape?: "block" | "underline" | "line";
blink?: boolean;
color?: string;
};
title?: string; // Window title
forceUnicode?: boolean; // Force Unicode (default: false)
dump?: boolean; // Dump output to file (default: false)
log?: string; // Log file path
debug?: boolean; // Debug mode (default: false)
warnings?: boolean; // Show warnings (default: false)
autoPadding?: boolean; // Auto padding (default: false)
tabc?: string; // Tab char (default: spaces)
}

Common Configurations

High Performance:

const screen = new Screen({
smartCSR: true,
fastCSR: true,
useBCE: true,
resizeTimeout: 100,
});

Unicode Support:

const screen = new Screen({
fullUnicode: true,
forceUnicode: true,
});

Development/Debug:

const screen = new Screen({
debug: true,
log: "./debug.log",
warnings: true,
dump: true,
});

Terminal Compatibility

Supported Terminals

macOS:

  • iTerm2 ✅
  • Alacritty ✅
  • Kitty ✅
  • Terminal.app ✅

Linux:

  • gnome-terminal ✅
  • konsole ✅
  • xterm ✅
  • terminator ✅

Windows:

  • Windows Terminal ✅
  • ConEmu (partial)
  • cmd.exe (limited)

Terminal Detection

Automatic terminal detection:

import { Screen } from "@unblessed/node";

const screen = new Screen({
smartCSR: true, // Auto-detected based on terminal
});

// Check detected terminal
console.log("Terminal:", screen.program.terminal);
console.log("Supports 256 colors:", screen.program.has256);
console.log("Supports true color:", screen.program.hasTrueColor);

Manual Terminal Override

Override detected terminal:

const screen = new Screen({
terminal: "xterm-256color", // Force specific terminal type
});

Input Handling

Keyboard Input

Handle keyboard events:

// Global keys
screen.key(["C-c", "q"], () => {
process.exit(0);
});

// Widget-specific keys
textbox.key(["enter"], () => {
form.submit();
});

// Raw key data
screen.on("keypress", (ch, key) => {
console.log("Pressed:", key.full);
});

Mouse Input

Enable mouse support:

const screen = new Screen({
mouse: true,
sendFocus: true,
});

box.on("click", (data) => {
console.log("Clicked at:", data.x, data.y);
});

box.on("wheeldown", () => {
box.scroll(1);
screen.render();
});

Raw Mode

Control raw terminal mode:

// Enable raw mode (default)
screen.program.setRaw(true);

// Disable raw mode
screen.program.setRaw(false);

// Restore mode on exit
process.on("exit", () => {
screen.program.setRaw(false);
});

Output Control

Writing to Terminal

Direct terminal output:

// Write via screen
screen.write("Hello\n");

// Write escape sequences
screen.program.write("\x1b[32mGreen text\x1b[0m\n");

// Position cursor
screen.program.move(10, 5); // Column 10, Row 5
screen.program.write("At position");

Screen Clearing

Clear screen methods:

// Clear entire screen
screen.clearRegion(0, screen.width, 0, screen.height);

// Clear specific region
screen.clearRegion(x, x + width, y, y + height);

// Full clear and reset
screen.program.clear();
screen.program.home();

Performance Optimization

Batch Updates

Minimize renders:

// ❌ Inefficient
for (let i = 0; i < 100; i++) {
boxes[i].setContent(`Item ${i}`);
screen.render();
}

// ✅ Efficient
for (let i = 0; i < 100; i++) {
boxes[i].setContent(`Item ${i}`);
}
screen.render(); // Single render

Throttle Rendering

For rapid updates:

let lastRender = 0;
const THROTTLE = 16; // ~60 FPS

function update() {
const now = Date.now();
if (now - lastRender > THROTTLE) {
screen.render();
lastRender = now;
}
}

Use Smart CSR

Enable for better performance:

const screen = new Screen({
smartCSR: true, // Uses scroll regions
fastCSR: true, // Even faster (may have artifacts)
});

Debugging

Debug Mode

Enable debugging:

const screen = new Screen({
debug: true,
log: "./debug.log",
});

// Log messages
screen.debug("Something happened");

Logging

Write to log file:

const screen = new Screen({
log: "./app.log",
});

// Logs to file
screen.log("Application started");
screen.log("User action:", action);

Dump Output

Save screen buffer:

const screen = new Screen({
dump: true, // Saves to ./blessed-${timestamp}.log
});

// Or manually
screen.dumpOutput("./screen-dump.txt");

Best Practices

1. Clean Exit

Always clean up:

function cleanup() {
screen.destroy();
}

process.on("exit", cleanup);
process.on("SIGINT", () => {
cleanup();
process.exit(0);
});
process.on("SIGTERM", () => {
cleanup();
process.exit(0);
});

2. Error Handling

Catch and display errors:

process.on("uncaughtException", (error) => {
// Clean up screen first
screen.destroy();

// Then display error
console.error("Uncaught error:", error);
process.exit(1);
});

process.on("unhandledRejection", (reason) => {
screen.destroy();
console.error("Unhandled rejection:", reason);
process.exit(1);
});

3. Resize Handling

Handle terminal resize:

screen.on("resize", () => {
// Re-layout widgets
sidebar.width = "30%";
content.width = "70%";

// Re-render
screen.render();
});

4. Use TypeScript

Get full type safety:

import { Screen, Box, type BoxOptions } from "@unblessed/node";

const options: BoxOptions = {
parent: screen,
top: "center",
left: "center",
width: "50%",
height: 10,
};

const box = new Box(options);

Next Steps