Skip to main content
The imperative API gives you direct control over widget creation, layout, and the event loop. This approach is ideal when you need fine-grained control over your UI or prefer a procedural programming style.

Initializing the Application

Start by initializing the Kraken TUI system and creating widgets:
import { Kraken, Box, Text, Input, Select } from "kraken-tui";

const app = Kraken.init();
Kraken.init() enters alternate screen mode, raw mode, and enables mouse capture. Always call app.shutdown() to restore terminal state.

Creating Widgets

Create widgets using class constructors with configuration objects:

Box Containers

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

// Set as application root
app.setRoot(root);

Text Display

const header = new Text({
  content: "# Kraken TUI Demo\n\n**Interactive dashboard**",
  format: "markdown",
  fg: "cyan",
});
header.setWidth("100%");
header.setHeight(4);
Text widgets support three formats: "plain", "markdown", and "code". Set the language property for syntax highlighting.

Input Fields

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

Select Widgets

const select = new Select({
  options: ["Dark Mode", "Light Mode", "Solarized", "Nord", "Dracula"],
  width: 25,
  height: 7,
  border: "rounded",
  fg: "white",
});
select.setFocusable(true);

ScrollBox Containers

const scrollBox = new ScrollBox({
  width: "100%",
  height: 12,
  border: "single",
  fg: "white",
});

const scrollContent = new Text({
  content: "Long scrollable text content...",
  format: "plain",
  fg: "white",
});
scrollContent.setWidth("100%");
scrollContent.setHeight(40);

scrollBox.append(scrollContent);

Building Layouts

Compose widgets into hierarchies using append():
// Create a row layout
const middleRow = new Box({
  width: "100%",
  flexDirection: "row",
  gap: 2,
});

middleRow.append(inputLabel);
middleRow.append(input);
middleRow.append(selectLabel);
middleRow.append(select);

// Add to root
root.append(header);
root.append(middleRow);
root.append(scrollBox);
root.append(statusBar);

Layout Properties

Control widget sizing and positioning:
  • Dimensions: width, height (numbers, “100%”, or “auto”)
  • Spacing: padding, margin, gap
  • Flex: flexDirection (“row” | “column”), justifyContent, alignItems
const container = new Box({
  width: "100%",
  flexDirection: "row",
  justifyContent: "space-between",
  alignItems: "center",
  padding: [2, 4, 2, 4], // [top, right, bottom, left]
  gap: 2,
});

Managing Focus

Control which widget receives keyboard input:
// Make widget focusable
input.setFocusable(true);

// Set initial focus
input.focus();

// Programmatic focus navigation
app.focusNext();
app.focusPrev();

// Get currently focused widget
const focused = app.getFocused();

Widget Identifiers

Register widgets with developer-assigned IDs:
app.setId("input", input);
app.setId("select", select);

// Retrieve by ID later
const handle = app.getHandle("input");

Styling Widgets

Apply colors, borders, and text styles:
// Colors
widget.setForeground("#89b4fa");
widget.setBackground("#1e1e2e");

// Text styles
text.setBold(true);
text.setItalic(true);
text.setUnderline(true);

// Borders
box.setBorder("rounded"); // "none" | "single" | "double" | "rounded" | "bold"

// Visibility
widget.setVisible(false);
Color values support hex codes ("#RRGGBB"), named colors ("red"), and 256-color palette indices (196).

Event Loop Patterns

The imperative API gives you full control over the event loop.

Basic Loop (~60fps)

let running = true;

while (running) {
  // Read input with 16ms timeout (~60fps)
  app.readInput(16);

  // Drain all buffered events
  const events = app.drainEvents();
  for (const event of events) {
    if (event.type === "key" && event.keyCode === KeyCode.Escape) {
      running = false;
      break;
    }
  }

  if (!running) break;

  // Render the frame
  app.render();
}

app.shutdown();

Handling Different Event Types

for (const event of events) {
  switch (event.type) {
    case "key":
      if (event.keyCode === KeyCode.Escape) {
        running = false;
      }
      break;

    case "submit":
      if (event.target === input.handle) {
        const value = input.getValue();
        statusBar.setContent(`Submitted: "${value}"`);
      } else if (event.target === select.handle) {
        const idx = select.getSelected();
        const option = select.getOption(idx);
        console.log("Selected:", option);
      }
      break;

    case "change":
      if (event.target === select.handle && event.selectedIndex != null) {
        const option = select.getOption(event.selectedIndex);
        applyTheme(option);
      }
      break;

    case "mouse":
      // Mouse events include x, y coordinates and button state
      break;

    case "focus":
      // Focus changed to event.target
      break;
  }
}

Animation-Aware Loop

Optimize rendering when animations are active:
import { PERF_ACTIVE_ANIMATIONS } from "kraken-tui/loop";

while (running) {
  const animating = app.getPerfCounter(PERF_ACTIVE_ANIMATIONS) > 0n;

  if (animating) {
    // Non-blocking, render at 60fps
    app.readInput(0);
    await Bun.sleep(16);
  } else {
    // Block on input when idle to save CPU
    app.readInput(100);
  }

  for (const event of app.drainEvents()) {
    // Handle events...
  }

  app.render();
}

Updating Widget State

Modify widget properties at runtime:
// Update text content
text.setContent("New content");

// Change colors dynamically
box.setBackground("#2e3440");
text.setForeground("#d8dee9");

// Modify layout
box.setWidth(50);
box.setHeight("auto");

// Update input state
input.setValue("preset text");
input.setMaxLength(100);

// Update select options
select.clearOptions();
select.addOption("New Option 1");
select.addOption("New Option 2");
select.setSelected(0);

Complete Example

Here’s a minimal interactive application:
examples/demo.ts
import { Kraken, Box, Text, Input, KeyCode } from "kraken-tui";
import type { KrakenEvent } from "kraken-tui";

const app = Kraken.init();

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

const header = new Text({
  content: "# Enter Your Name",
  format: "markdown",
  fg: "cyan",
});
header.setWidth("100%");
header.setHeight(3);

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

const status = new Text({
  content: "Press Enter to submit, Esc to quit",
  fg: "bright-black",
});
status.setWidth("100%");
status.setHeight(1);

root.append(header);
root.append(input);
root.append(status);

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

// Event loop
let running = true;
while (running) {
  app.readInput(16);

  const events = app.drainEvents();
  for (const event of events) {
    if (event.type === "key" && event.keyCode === KeyCode.Escape) {
      running = false;
      break;
    }

    if (event.type === "submit" && event.target === input.handle) {
      const value = input.getValue();
      status.setContent(`Hello, ${value}!`);
    }
  }

  if (!running) break;
  app.render();
}

app.shutdown();

Best Practices

1
Initialize Once
2
Call Kraken.init() once at application startup. Store the instance for the entire lifecycle.
3
Set Root Before Loop
4
Always call app.setRoot() before starting the event loop.
5
Clean Shutdown
6
Always call app.shutdown() before exit to restore terminal state. Use try-finally if needed.
7
Drain Events
8
Call app.drainEvents() in every frame to process all buffered input.
9
Render Every Frame
10
Call app.render() at the end of each loop iteration to update the terminal.

Next Steps

JSX API

Learn about the declarative JSX syntax and signals

Event Handling

Master keyboard, mouse, and focus events

Animations

Add smooth transitions and choreography

Themes

Apply and customize visual themes

Build docs developers (and LLMs) love