opentui

Build terminal UIs with OpenTUI. Covers Core, frameworks, components, application APIs, testing, extensions, integrations, deployment, and public API lookup.

Install
npx skills add 'https://github.com/anomalyco/opentui/tree/main/packages/web/src/content'
Download bundle ↓
main · ac753b4Scanned 2026-09-15

Contributors

GitHub-linked commit authors for this SKILL.md at the saved revision. Co-authors and history before file renames are not included.

File history ↗

docs/components/frame-buffer.mdx

docs/components/frame-buffer.mdxBrowse 89 files
View on GitHub
← Back to SKILL.md

title: FrameBuffer description: Draw cells, text, images, and effects into a custom rendering surface

FrameBuffer

FrameBuffer exposes a low-level, two-dimensional cell buffer for custom graphics. Use Image to display encoded images without direct cell drawing.

Availability

FieldAvailability
Package@opentui/core
Core renderableFrameBufferRenderable
ReactUnavailable
SolidUnavailable
StatusAdvanced

Basic usage

Renderable API

import { FrameBufferRenderable, RGBA, createCliRenderer } from "@opentui/core"

const renderer = await createCliRenderer()

const canvas = new FrameBufferRenderable(renderer, {
  id: "canvas",
  width: 50,
  height: 20,
})

// Draw on the frame buffer
canvas.frameBuffer.fillRect(5, 2, 20, 10, RGBA.fromHex("#FF0000"))
canvas.frameBuffer.drawText("Hello!", 8, 6, RGBA.fromHex("#FFFFFF"))

renderer.root.add(canvas)

Drawing methods

setCell

Set a single cell's content and colors:

canvas.frameBuffer.setCell(
  x, // X position
  y, // Y position
  char, // One-cell scalar to display
  fg, // Foreground color (RGBA)
  bg, // Background color (RGBA)
  attributes, // Text attributes (optional, default: 0)
)

// Example
canvas.frameBuffer.setCell(10, 5, "@", RGBA.fromHex("#FFFF00"), RGBA.fromHex("#000000"))

setCell() uses only the first code point and does not reserve continuation cells. Use it for one-cell scalars. Use drawText() for wide or joined graphemes.

Draw text and block characters directly to build a compact chart:

network throughput
rx  ▂▄▆█▇▅▃▂  42 MB/s
tx  ▃▅▇█▆▄▂▁  18 MB/s
    8 seconds     now

setCellWithAlphaBlending

Set a cell with alpha blending for transparency effects:

const semiTransparent = RGBA.fromValues(1.0, 0.0, 0.0, 0.5)
const transparent = RGBA.fromValues(0, 0, 0, 0)
canvas.frameBuffer.setCellWithAlphaBlending(10, 5, " ", transparent, semiTransparent)

drawText

Draw a string of text at a position:

canvas.frameBuffer.drawText(
  text, // String to draw
  x, // Starting X position
  y, // Y position
  fg, // Text color (RGBA)
  bg, // Background color (RGBA, optional)
  attributes, // Text attributes (optional, default: 0)
)

// Example
canvas.frameBuffer.drawText("Score: 100", 2, 1, RGBA.fromHex("#00FF00"))

fillRect

Fill a rectangular area with a color:

canvas.frameBuffer.fillRect(
  x, // X position
  y, // Y position
  width, // Rectangle width
  height, // Rectangle height
  color, // Fill color (RGBA)
)

// Example: Draw a red rectangle
canvas.frameBuffer.fillRect(10, 5, 20, 8, RGBA.fromHex("#FF0000"))

drawFrameBuffer

Copy another frame buffer onto this one:

canvas.frameBuffer.drawFrameBuffer(
  destX, // Destination X
  destY, // Destination Y
  sourceBuffer, // Source FrameBuffer (OptimizedBuffer)
  sourceX, // Source X offset (optional)
  sourceY, // Source Y offset (optional)
  sourceWidth, // Width to copy (optional)
  sourceHeight, // Height to copy (optional)
)

drawImage

Place a native image into destination cells:

const placed = canvas.frameBuffer.drawImage(
  image,
  x,
  y,
  width,
  height,
  pixelWidth,
  pixelHeight,
  sourceX,
  sourceY,
  sourceWidth,
  sourceHeight,
  protocol,
)

x, y, width, and height use terminal cells. pixelWidth and pixelHeight use terminal pixels and default to 0. Source coordinates default to the full image, and protocol defaults to "auto". Sixel requires nonzero pixel dimensions or falls back to blocks. false means the buffer recorded no visible valid placement. A successful placement retains the native image until the buffer is cleared, resized, or destroyed. Ownership does not transfer. The caller must still dispose its NativeImage.

colorMatrix / colorMatrixUniform

Apply native 4x4 RGBA matrix transforms for post-processing effects. Use colorMatrixUniform for full-buffer transforms, and colorMatrix when you want to target specific cells.

import { INVERT_MATRIX, TargetChannel } from "@opentui/core"

// Full-buffer transform
canvas.frameBuffer.colorMatrixUniform(INVERT_MATRIX, 1.0, TargetChannel.Both)

// Per-cell transform with explicit mask
const cellMask = new Float32Array([10, 5, 1.0, 11, 5, 0.5])
canvas.frameBuffer.colorMatrix(INVERT_MATRIX, cellMask, 1.0, TargetChannel.FG)

See Color matrix reference for matrix layout, mask format, and behavior details.

Properties

PropertyTypeDefaultDescription
widthnumber-Buffer width in terminal cells (required)
heightnumber-Buffer height in rows (required)
respectAlphabooleanfalseEnable alpha blending when drawing
positionstring"relative"Positioning mode
left, top, right, bottomnumber, "auto", or percentage string-Position offsets

Example: game canvas

import { FrameBufferRenderable, RGBA, createCliRenderer } from "@opentui/core"

const renderer = await createCliRenderer()

const gameCanvas = new FrameBufferRenderable(renderer, {
  id: "game",
  width: 40,
  height: 20,
  position: "absolute",
  left: 5,
  top: 2,
})

// Game state
let playerX = 20
let playerY = 10

function render() {
  const fb = gameCanvas.frameBuffer
  const BG = RGBA.fromHex("#111111")

  // Clear the canvas
  fb.fillRect(0, 0, 40, 20, BG)

  // Draw border
  for (let x = 0; x < 40; x++) {
    fb.setCell(x, 0, "-", RGBA.fromHex("#444444"), BG)
    fb.setCell(x, 19, "-", RGBA.fromHex("#444444"), BG)
  }
  for (let y = 0; y < 20; y++) {
    fb.setCell(0, y, "|", RGBA.fromHex("#444444"), BG)
    fb.setCell(39, y, "|", RGBA.fromHex("#444444"), BG)
  }

  // Draw player
  fb.setCell(playerX, playerY, "@", RGBA.fromHex("#00FF00"), BG)

  // Draw score
  fb.drawText("Score: 0", 2, 0, RGBA.fromHex("#FFFF00"))
}

// Handle input
renderer.keyInput.on("keypress", (key) => {
  switch (key.name) {
    case "up":
      playerY = Math.max(1, playerY - 1)
      break
    case "down":
      playerY = Math.min(18, playerY + 1)
      break
    case "left":
      playerX = Math.max(1, playerX - 1)
      break
    case "right":
      playerX = Math.min(38, playerX + 1)
      break
  }
  render()
  gameCanvas.requestRender()
})

render()
renderer.root.add(gameCanvas)

Example: progress bar

const EMPTY_BG = RGBA.fromHex("#222222")

function drawProgressBar(fb, x, y, width, progress, color) {
  const filled = Math.floor(width * progress)

  // Draw filled portion
  for (let i = 0; i < filled; i++) {
    fb.setCell(x + i, y, "█", color, EMPTY_BG)
  }

  // Draw empty portion
  for (let i = filled; i < width; i++) {
    fb.setCell(x + i, y, "░", RGBA.fromHex("#333333"), EMPTY_BG)
  }
}

// Usage
drawProgressBar(canvas.frameBuffer, 5, 10, 30, 0.75, RGBA.fromHex("#00FF00"))

A 20-cell bar with 14 filled cells renders as:

Downloading package
██████████████░░░░░░  70%
14 of 20 files

Read the Buffer API for OptimizedBuffer ownership and drawing operations. Use NativeImage to decode and transform image data. The color matrix reference covers post-processing matrix and mask formats.