The FrameBufferRenderable provides a way to use custom graphics buffers within OpenTUI’s layout system. It wraps an OptimizedBuffer that you can draw to directly, enabling advanced rendering techniques and custom graphics.
Import
import { FrameBufferRenderable } from "@opentui/core"
Basic Usage
const framebuffer = new FrameBufferRenderable(ctx, {
width: 80,
height: 24,
respectAlpha: true
})
// Draw directly to the frame buffer
framebuffer.frameBuffer.drawText("Hello", 0, 0, foregroundColor, backgroundColor)
framebuffer.frameBuffer.drawRect(10, 5, 20, 10, borderColor)
Props
Initial width of the frame buffer in columns.
Initial height of the frame buffer in rows.
Whether to respect alpha transparency when compositing the buffer. When true, transparent pixels allow content behind to show through.
Properties
frameBuffer
Access to the underlying OptimizedBuffer for direct drawing operations:
const buffer = framebuffer.frameBuffer
// Drawing methods available:
buffer.drawText(text, x, y, fg, bg, attributes)
buffer.drawChar(char, x, y, fg, bg, attributes)
buffer.drawRect(x, y, width, height, color)
buffer.drawFrameBuffer(x, y, sourceBuffer)
buffer.fill(char, fg, bg, attributes)
buffer.clear()
See the OptimizedBuffer documentation for complete drawing API.
Methods
Automatic Resizing
The frame buffer automatically resizes when the component’s dimensions change:
framebuffer.width = 100
framebuffer.height = 30
// frameBuffer is automatically resized to match
You can also control size through layout props like flexGrow, flexShrink, etc.
Examples
Custom Graphics Canvas
import { FrameBufferRenderable } from "@opentui/core"
import { parseColor } from "@opentui/core/lib"
const canvas = new FrameBufferRenderable(ctx, {
width: 80,
height: 40,
respectAlpha: true,
flexGrow: 1
})
const white = parseColor("#ffffff")
const black = parseColor("#000000")
const blue = parseColor("#0066ff")
// Draw a box with border
canvas.frameBuffer.fill(" ", white, blue)
canvas.frameBuffer.drawText("╔" + "═".repeat(78) + "╗", 0, 0, white, blue)
canvas.frameBuffer.drawText("║", 0, 1, white, blue)
canvas.frameBuffer.drawText("║", 79, 1, white, blue)
canvas.frameBuffer.drawText("╚" + "═".repeat(78) + "╝", 0, 39, white, blue)
// Draw text content
canvas.frameBuffer.drawText("Custom Graphics", 2, 2, white, blue)
Pixel Art Display
const pixelArt = new FrameBufferRenderable(ctx, {
width: 32,
height: 32,
respectAlpha: false
})
// Create a simple pattern
for (let y = 0; y < 32; y++) {
for (let x = 0; x < 32; x++) {
const color = parseColor(`rgb(${x * 8}, ${y * 8}, 128)`)
pixelArt.frameBuffer.drawChar("█", x, y, color, color)
}
}
Chart Renderer
class ChartRenderable extends FrameBufferRenderable {
private data: number[] = []
constructor(ctx: RenderContext, width: number, height: number) {
super(ctx, { width, height, respectAlpha: false })
}
setData(values: number[]) {
this.data = values
this.redrawChart()
}
private redrawChart() {
const buffer = this.frameBuffer
const maxValue = Math.max(...this.data)
const white = parseColor("#ffffff")
const green = parseColor("#00ff00")
const bg = parseColor("#000000")
// Clear buffer
buffer.fill(" ", white, bg)
// Draw bars
const barWidth = Math.floor(this.width / this.data.length)
this.data.forEach((value, i) => {
const barHeight = Math.floor((value / maxValue) * this.height)
const x = i * barWidth
for (let y = 0; y < barHeight; y++) {
buffer.drawChar("█", x, this.height - y - 1, green, bg)
}
})
this.requestRender()
}
}
// Usage
const chart = new ChartRenderable(ctx, 40, 10)
chart.setData([5, 12, 8, 15, 10, 18, 14, 9])
Double-Buffered Animation
const animation = new FrameBufferRenderable(ctx, {
width: 60,
height: 20,
respectAlpha: true
})
let frame = 0
const white = parseColor("#ffffff")
const black = parseColor("#000000")
setInterval(() => {
// Clear buffer
animation.frameBuffer.clear()
// Draw animated sprite
const x = (frame % 60)
const y = Math.floor(Math.sin(frame * 0.1) * 5) + 10
animation.frameBuffer.drawText("●", x, y, white, black)
// Trigger re-render
animation.requestRender()
frame++
}, 50)
Off-screen Rendering
// Render complex graphics off-screen then composite
const offscreen = new FrameBufferRenderable(ctx, {
width: 100,
height: 100,
respectAlpha: true
})
const display = new FrameBufferRenderable(ctx, {
width: 50,
height: 50,
respectAlpha: true
})
// Draw to large off-screen buffer
renderComplexGraphics(offscreen.frameBuffer)
// Copy region to display buffer
display.frameBuffer.drawFrameBuffer(0, 0, offscreen.frameBuffer)
Use Cases
- Custom visualizations - Charts, graphs, plots
- Pixel art - Retro graphics and sprites
- Image rendering - Display processed images or photos
- Canvas drawing - Freeform graphics and shapes
- Game graphics - Sprites, tiles, and game elements
- Performance - Batch drawing operations for complex scenes
- Frame buffers store complete pixel data, so large buffers use more memory
- Drawing to a frame buffer is efficient - changes only trigger re-render when
requestRender() is called
- Use
respectAlpha: false when transparency is not needed for better performance
- Consider buffer size relative to terminal dimensions
Lifecycle
- The internal buffer is automatically resized when component dimensions change
- The buffer is properly cleaned up when the component is destroyed
- Call
requestRender() after drawing operations to trigger a visual update
Notes
- The frame buffer uses the same
widthMethod as the render context for consistent character width handling
- Resizing to invalid dimensions (≤ 0) throws an error
- The component is visible by default and participates in normal layout
- Use standard Renderable props for positioning, sizing, and layout
Source
View the full source code at packages/core/src/renderables/FrameBuffer.ts:11