Skip to main content
Rezi provides powerful graphics capabilities with canvas drawing, image display with multiple protocols, and built-in chart widgets.

Canvas Widget

Draw custom graphics with sub-cell precision using the canvas widget.

Basic Canvas

import { ui } from "@rezi-ui/core";

ui.canvas({
  id: "mycanvas",
  w: 40,
  h: 20,
  blitter: "braille",  // Sub-cell rendering
  draw: (ctx) => {
    // Draw a rectangle
    ctx.fillStyle = "#ff0000";
    ctx.fillRect(10, 10, 20, 10);
    
    // Draw a circle
    ctx.fillStyle = "#00ff00";
    ctx.beginPath();
    ctx.arc(50, 30, 15, 0, Math.PI * 2);
    ctx.fill();
    
    // Draw a line
    ctx.strokeStyle = "#0000ff";
    ctx.lineWidth = 2;
    ctx.beginPath();
    ctx.moveTo(0, 0);
    ctx.lineTo(80, 80);
    ctx.stroke();
  },
})

Canvas Blitters

Different blitters provide different resolutions:
ui.canvas({ blitter: "braille", w: 40, h: 20 })
Resolution: 2x4 pixels per cell (80x80 pixels for 40x20 canvas)Best for smooth curves and detailed graphics.

Drawing API

The canvas context provides a subset of HTML5 Canvas API:
type CanvasContext = {
  // Styling
  fillStyle: string;      // Fill color (hex or theme path)
  strokeStyle: string;    // Stroke color
  lineWidth: number;      // Line width in pixels
  
  // Shapes
  fillRect(x: number, y: number, w: number, h: number): void;
  strokeRect(x: number, y: number, w: number, h: number): void;
  clearRect(x: number, y: number, w: number, h: number): void;
  
  // Paths
  beginPath(): void;
  closePath(): void;
  moveTo(x: number, y: number): void;
  lineTo(x: number, y: number): void;
  arc(x: number, y: number, radius: number, startAngle: number, endAngle: number): void;
  fill(): void;
  stroke(): void;
  
  // Text overlay
  fillText(text: string, x: number, y: number, color?: string): void;
};

Interactive Canvas

import { defineWidget } from "@rezi-ui/core";

const InteractiveCanvas = defineWidget((props, ctx) => {
  const [points, setPoints] = ctx.useState<Array<{ x: number; y: number }>>([]);
  
  return ui.canvas({
    id: ctx.id("canvas"),
    w: 40,
    h: 20,
    blitter: "braille",
    draw: (canvasCtx) => {
      // Draw all points
      canvasCtx.fillStyle = "#ff0000";
      for (const point of points) {
        canvasCtx.fillRect(point.x, point.y, 2, 2);
      }
    },
    onClick: (x, y) => {
      setPoints([...points, { x, y }]);
    },
  });
});

Image Widget

Display images using terminal graphics protocols.

Supported Protocols

ui.image({
  id: "photo",
  src: imageBytes,  // PNG or RGBA bytes
  protocol: "kitty",
  w: 40,
  h: 20,
  fit: "contain",
})
Support: Kitty terminalHigh-quality image rendering with true color.

Image Formats

// PNG images
const pngBytes = await readFile("image.png");
ui.image({ id: "img", src: pngBytes, protocol: "auto" })

// RGBA pixel data
const rgba = new Uint8Array(width * height * 4);
// Fill rgba with pixel data (R, G, B, A for each pixel)
ui.image({ id: "img", src: rgba, protocol: "auto" })

Image Fit Modes

type ImageFit = "contain" | "cover" | "fill" | "none";

// Contain: Scale to fit within bounds, preserve aspect ratio
ui.image({ fit: "contain", src: bytes, protocol: "auto" })

// Cover: Scale to cover bounds, crop excess
ui.image({ fit: "cover", src: bytes, protocol: "auto" })

// Fill: Stretch to fill bounds, ignore aspect ratio
ui.image({ fit: "fill", src: bytes, protocol: "auto" })

// None: No scaling, display at original size
ui.image({ fit: "none", src: bytes, protocol: "auto" })

Chart Widgets

Rezi includes built-in chart widgets for common visualizations.

Line Chart

import { ui } from "@rezi-ui/core";

ui.lineChart({
  id: "metrics",
  series: [
    {
      label: "CPU",
      data: [20, 35, 45, 40, 55, 60],
      color: { r: 255, g: 180, b: 84 },
    },
    {
      label: "Memory",
      data: [30, 40, 38, 45, 50, 48],
      color: { r: 89, g: 194, b: 255 },
    },
  ],
  w: 60,
  h: 15,
  xAxis: { label: "Time" },
  yAxis: { label: "Usage %", min: 0, max: 100 },
})

Bar Chart

ui.barChart({
  id: "sales",
  items: [
    { label: "Q1", value: 125, color: { r: 170, g: 217, b: 76 } },
    { label: "Q2", value: 180, color: { r: 255, g: 180, b: 84 } },
    { label: "Q3", value: 220, color: { r: 89, g: 194, b: 255 } },
    { label: "Q4", value: 195, color: { r: 240, g: 113, b: 120 } },
  ],
  w: 40,
  h: 10,
  orientation: "vertical",  // or "horizontal"
})

Scatter Plot

ui.scatter({
  id: "distribution",
  points: [
    { x: 10, y: 20, label: "A", color: { r: 255, g: 0, b: 0 } },
    { x: 25, y: 35, label: "B", color: { r: 0, g: 255, b: 0 } },
    { x: 40, y: 15, label: "C", color: { r: 0, g: 0, b: 255 } },
  ],
  w: 50,
  h: 20,
  xAxis: { min: 0, max: 50, label: "X" },
  yAxis: { min: 0, max: 50, label: "Y" },
})

Heatmap

ui.heatmap({
  id: "activity",
  data: [
    [0, 1, 2, 3, 4],
    [5, 6, 7, 8, 9],
    [10, 11, 12, 13, 14],
  ],
  w: 25,
  h: 6,
  colorScale: "viridis",  // "viridis" | "plasma" | "inferno" | "magma" | "grayscale"
  min: 0,
  max: 14,
})

Sparkline

ui.sparkline({
  data: [10, 15, 13, 17, 20, 18, 22, 25],
  w: 20,
  h: 3,
  style: { fg: "accent.primary" },
})

Gauge

ui.gauge({
  id: "cpu-usage",
  value: 65,
  min: 0,
  max: 100,
  w: 20,
  h: 10,
  label: "CPU",
  color: { r: 255, g: 180, b: 84 },
})

Terminal Graphics Detection

Rezi auto-detects terminal capabilities:
const caps = app.getCaps();

if (caps.images) {
  console.log(`Image protocol: ${caps.imageProtocol}`);
  // "kitty" | "sixel" | "iterm2" | "none"
}

if (caps.subcell) {
  console.log("Terminal supports Unicode box drawing");
  // Can use braille, sextant, quadrant blitters
}

Graceful Degradation

import { resolveCanvasBlitter } from "@rezi-ui/core";

const caps = app.getCaps();
const blitter = resolveCanvasBlitter("auto", caps.subcell);
// Returns best available blitter based on terminal support

ui.canvas({
  id: "chart",
  blitter,
  w: 40,
  h: 20,
  draw: (ctx) => {
    // Draw chart...
  },
})

Real-World Examples

CPU Usage Chart

import { defineWidget, useInterval } from "@rezi-ui/core";

const CpuChart = defineWidget((props, ctx) => {
  const [data, setData] = ctx.useState<number[]>([]);
  
  useInterval(ctx, () => {
    const newValue = Math.random() * 100;
    setData(prev => [...prev.slice(-29), newValue]);  // Keep last 30 values
  }, 1000);
  
  return ui.lineChart({
    id: ctx.id("chart"),
    series: [{
      label: "CPU %",
      data,
      color: { r: 255, g: 180, b: 84 },
    }],
    w: 60,
    h: 12,
    xAxis: { label: "Time (s)" },
    yAxis: { label: "Usage %", min: 0, max: 100 },
  });
});

Image Viewer

import { defineWidget } from "@rezi-ui/core";
import { readFile } from "fs/promises";

const ImageViewer = defineWidget<{ path: string }>((props, ctx) => {
  const [imageBytes, setImageBytes] = ctx.useState<Uint8Array | null>(null);
  
  ctx.useEffect(() => {
    readFile(props.path).then(setImageBytes);
  }, [props.path]);
  
  if (!imageBytes) {
    return ui.text("Loading...");
  }
  
  return ui.image({
    id: ctx.id("img"),
    src: imageBytes,
    protocol: "auto",
    fit: "contain",
    w: 80,
    h: 40,
  });
});

Custom Data Visualization

const CustomViz = defineWidget<{ data: number[] }>((props, ctx) => {
  return ui.canvas({
    id: ctx.id("viz"),
    w: 60,
    h: 20,
    blitter: "braille",
    draw: (canvasCtx) => {
      const max = Math.max(...props.data);
      const barWidth = (60 * 2) / props.data.length;  // 2 pixels per cell
      
      props.data.forEach((value, index) => {
        const height = (value / max) * (20 * 4);  // 4 pixels per cell
        const x = index * barWidth;
        const y = (20 * 4) - height;
        
        canvasCtx.fillStyle = "#59c2ff";
        canvasCtx.fillRect(x, y, barWidth - 2, height);
      });
    },
  });
});

Best Practices

Auto Blitter

Use blitter: "auto" for canvas widgets. Rezi will select the best available blitter based on terminal capabilities.

Auto Protocol

Use protocol: "auto" for images. Rezi will detect and use the best graphics protocol supported by the terminal.

Built-in Charts

Prefer built-in chart widgets over custom canvas drawing for common visualizations. They’re optimized and handle edge cases.

Graceful Degradation

Always provide fallbacks for terminals without graphics support. Consider rendering text-based alternatives.

Next Steps

Performance

Optimize your app for maximum performance

Debugging

Debug and troubleshoot your TUI apps

Build docs developers (and LLMs) love