DEV Community

Cover image for How we built a terminal UI framework that only repaints what changed.
Harsh Dubey
Harsh Dubey

Posted on

How we built a terminal UI framework that only repaints what changed.

Every terminal framework we tried repaints the entire screen every frame. Write a character, repaint 10,000 cells. Scroll one line, repaint 10,000 cells.

We decided to treat the terminal like a display server instead.

The idea

What if we tracked every cell in a typed-array buffer and only wrote the ones that actually changed?

That's Storm. A React-based terminal UI framework where the renderer diffs individual cells between frames. On a typical scroll frame, 97% of cells are unchanged — Storm skips them entirely.

How it works

Typed-array buffersInt32Array + Uint8Array instead of JS objects. A 200×50 terminal has 10,000 cells. Traditional frameworks create 10,000 objects per frame. Storm creates zero — the buffer is flat arrays. ~90% less GC pressure.
Cell-level diff — After painting into the buffer, the diff engine compares frame N against frame N+1 cell by cell. Only changed cells produce ANSI output. The rest are skipped entirely.
WASM acceleration — An optional 35KB Rust module handles ANSI string generation 3.4× faster. Loads automatically, falls back to TypeScript silently.
Dual-speed rendering — React handles structural changes (adding/removing components). For animation and scrolling, requestRender() writes directly to the buffer — no React reconciliation, no layout rebuild. 0.5ms per frame vs 5ms.

What's in the box

  • 98 components - from Box and Text to CommandPalette, TextArea, Markdown, DatePicker - 19 AI widgets — OperationTree, MessageBubble, ApprovalPrompt, StreamingText, SyntaxHighlight
  • 85 hooks — including 15 headless behavior hooks- Built-in DevTools, render heatmap, accessibility audit, time-travel, inspector
  • SSH serving — serve your TUI over SSH with auth and rate limiting

Quick start

npm install @orchetron/storm react
Enter fullscreen mode Exit fullscreen mode
import React from "react";                                                                                                                            
  import { render, Box, Text, Spinner, useInput, useTui } from "@orchetron/storm";                                                                      

  function App() {                                                                                                                                      
    const { exit } = useTui();                                                                                                                          
    useInput((e) => { if (e.key === "c" && e.ctrl) exit(); });                                                                                          

    return (                                                                                                                                            
      <Box padding={1}>                                                                                                                                 
        <Spinner type="diamond" color="#82AAFF" />                                                                                                      
        <Text bold color="#82AAFF"> storm is alive</Text>                                                                                               
      </Box>                                                                                                                                            
    );                                                                                                                                                  
  }                                                                                                                                                     
  render(<App />).waitUntilExit();  
Enter fullscreen mode Exit fullscreen mode

10 lines. Running TUI with animated spinner and keyboard input.

The numbers

  • Full frame: 2.3ms (7× headroom above 60fps)
  • Cell skip rate: 97%
  • WASM diff: 3.4× faster
  • DECSTBM scroll: 78% fewer bytes to stdout
  • SyntaxHighlight: 500K lines virtualized in 2.7s

Every number is real — no cache tricks, no theoretical peaks. Run them yourself: npx tsx examples/benchmarks.ts

Links

Storm is MIT licensed. We'd love feedback — what would you build with it?

Top comments (0)