Skip to main content
This guide walks you through creating a simple interactive terminal application using Kraken TUI. You’ll build a todo list app that demonstrates core concepts: widgets, layout, input handling, and rendering.
Prerequisites: Complete the Installation guide before starting this tutorial.

Choose Your API Style

Kraken TUI offers two APIs with the same underlying performance:
  • Imperative API — Direct control with explicit widget creation and manipulation
  • JSX API — Declarative composition with reactive signals (requires @preact/signals-core)
Pick the style that matches your preference. Both examples create the same application.

Imperative: Hello World

Let’s start with the simplest possible application:
1

Create your project file

Create a new file hello.ts:
hello.ts
import { Kraken, Box, Text, KeyCode } from "./ts/src/index";

// Initialize the TUI engine
const app = Kraken.init();

// Create root container
const root = new Box({
  width: "100%",
  height: "100%",
  flexDirection: "column",
  padding: 2,
});

// Create text widget
const label = new Text({
  content: "Hello, Kraken TUI!",
  fg: "#00FF00",
  bold: true,
});

// Build widget tree
root.append(label);
app.setRoot(root);

// Event loop
let running = true;
while (running) {
  app.readInput(16);  // Read input with 16ms timeout (~60fps)
  
  for (const event of app.drainEvents()) {
    if (event.type === "key" && event.keyCode === KeyCode.Escape) {
      running = false;
    }
  }
  
  app.render();  // Render frame
}

// Clean shutdown
app.shutdown();
2

Run your application

cargo build --manifest-path native/Cargo.toml --release
bun run hello.ts
Press Esc to exit.

How It Works

  1. InitializeKraken.init() creates the native context and terminal backend
  2. Compose — Create widgets and build a tree structure using append()
  3. Mountapp.setRoot() attaches your tree to the rendering surface
  4. Loop — Read input → drain events → render (60fps pattern)
  5. Shutdownapp.shutdown() restores terminal state

Interactive Todo List

Now let’s build a more complete application with input handling:
todo.ts
import {
  Kraken,
  Box,
  Text,
  Input,
  Select,
  KeyCode,
} from "./ts/src/index";
import type { KrakenEvent } from "./ts/src/index";

const app = Kraken.init();

// Root container
const root = new Box({
  width: "100%",
  height: "100%",
  flexDirection: "column",
  padding: 1,
  gap: 1,
});

// Header
const header = new Text({
  content: "# Todo List\n\nPress *Tab* to switch focus, *Enter* to add task, *Esc* to quit.",
  format: "markdown",
  fg: "cyan",
});
header.setWidth("100%");
header.setHeight(4);

// Input row
const inputRow = new Box({
  width: "100%",
  flexDirection: "row",
  gap: 2,
});

const inputLabel = new Text({
  content: "New task:",
  bold: true,
  fg: "green",
});
inputLabel.setWidth(10);
inputLabel.setHeight(3);

const input = new Input({
  width: 40,
  height: 3,
  border: "rounded",
  fg: "white",
  maxLength: 100,
});
input.setFocusable(true);

inputRow.append(inputLabel);
inputRow.append(input);

// Todo list
const todos: string[] = ["Learn Kraken TUI", "Build something cool"];

const todoSelect = new Select({
  options: todos,
  width: "100%",
  height: 12,
  border: "single",
  fg: "white",
});
todoSelect.setFocusable(true);

// Status bar
const statusBar = new Text({
  content: "Ready",
  fg: "bright-black",
});
statusBar.setWidth("100%");
statusBar.setHeight(1);

// Assemble tree
root.append(header);
root.append(inputRow);
root.append(todoSelect);
root.append(statusBar);

app.setRoot(root);
input.focus();

// Event loop
let running = true;

while (running) {
  app.readInput(16);
  
  const events: KrakenEvent[] = app.drainEvents();
  for (const event of events) {
    // Quit on Escape
    if (event.type === "key" && event.keyCode === KeyCode.Escape) {
      running = false;
      break;
    }
    
    // Add todo on Enter in input
    if (event.type === "submit" && event.target === input.handle) {
      const value = input.getValue();
      if (value.trim()) {
        todos.push(value);
        todoSelect.addOption(value);
        input.setValue("");
        statusBar.setContent(`Added: "${value}"`);
      }
    }
    
    // Show selected todo
    if (event.type === "change" && event.target === todoSelect.handle) {
      const idx = event.selectedIndex ?? 0;
      const todo = todoSelect.getOption(idx);
      statusBar.setContent(`Selected: "${todo}"`);
    }
  }
  
  if (!running) break;
  app.render();
}

app.shutdown();

Key Concepts

Widget Tree

Widgets are composed hierarchically. Box containers hold children arranged by Flexbox layout.

Handles

Each widget has a handle property — an opaque u32 reference. Use handles to identify event targets.

Event Loop

The host controls timing: readInput()drainEvents()render(). Typically 60fps (16ms interval).

Focus Management

Call setFocusable(true) on interactive widgets. Use Tab/Shift+Tab for keyboard traversal.

Core Concepts Explained

Widget Types

Kraken TUI provides six core widgets:
WidgetPurposeKey Props
BoxContainer with Flexbox layoutflexDirection, gap, padding, border
TextDisplay text (plain/markdown/code)content, format, fg, bold, italic
InputSingle-line text inputmaxLength, fg, border, focusable
SelectOption list with arrow navigationoptions, border, focusable
ScrollBoxScrollable container (one child)border, fg, bg
TextAreaMulti-line text editorwrapMode, fg, border, focusable

Layout with Flexbox

Kraken TUI uses Taffy (Rust Flexbox implementation) for layout:
const container = new Box({
  flexDirection: "row",     // horizontal layout
  justifyContent: "center", // center horizontally
  alignItems: "center",     // center vertically
  gap: 2,                   // spacing between children
  padding: 1,               // inner padding
});
Dimensions accept:
  • Fixed: width: 40 (cells)
  • Percentage: width: "50%" (of parent)
  • Fill: width: "100%" (full parent width)
  • Auto: width: "auto" (content-based)

Event Handling

All events are drained from a buffer each frame:
for (const event of app.drainEvents()) {
  switch (event.type) {
    case "key":
      console.log("Key pressed:", event.keyCode);
      break;
    case "submit":
      console.log("Submit on handle:", event.target);
      break;
    case "change":
      console.log("Value changed:", event.selectedIndex);
      break;
    case "click":
      console.log("Clicked at:", event.x, event.y);
      break;
  }
}

Styling

Colors can be specified as:
  • Hex: "#FF0000" (RGB)
  • Named: "red", "cyan", "bright-black" (16 color names)
  • Indexed: 196 (256 color palette index)
Text decorations:
  • bold: true
  • italic: true
  • underline: true
Borders:
  • border: "single"┌─┐
  • border: "double"╔═╗
  • border: "rounded"╭─╮
  • border: "none" (default)

Next Steps

Now that you’ve built your first application, explore these topics:

Core Concepts

Deep dive into widgets, layout, and state management

API Reference

Complete API documentation for all widgets and methods

Animation

Add smooth transitions and built-in animation primitives

Theming

Customize appearance with themes and runtime switching
Explore the examples: Check out examples/demo.ts and examples/migration-jsx.tsx in the repository for more complex applications with theming, scrolling, and live updates.

Build docs developers (and LLMs) love