Skip to main content

Widgets

Widgets are the fundamental building blocks of Kraken TUI applications. All visual elements are widgets arranged in a composition tree.

Widget Composition Tree

Kraken TUI uses a retained-mode architecture where you build a tree of widgets that persists across frames. Only changed widgets are re-rendered.
1

Create Widgets

Instantiate widget objects in TypeScript. Each widget receives a unique u32 handle from the Native Core.
2

Build Hierarchy

Use append(), insertChild(), and removeChild() to establish parent-child relationships.
3

Set Root

Assign the top-level widget to the application using app.setRoot(widget).
4

Render

Call app.render() to traverse the tree and update the terminal.

Example: Building a Tree

import { Kraken, Box, Text, Input } from "kraken-tui";

const app = Kraken.init();

// Create widgets
const container = new Box({ direction: "column" });
const header = new Text({ content: "# Welcome" });
const input = new Input({ placeholder: "Enter name..." });
const footer = new Box({ direction: "row" });

// Build hierarchy
container.append(header);
container.append(input);
container.append(footer);

// Set root and render
app.setRoot(container);
app.render();
This creates the following tree:
container (Box)
├── header (Text)
├── input (Input)
└── footer (Box)

Widget Handle System

Every widget holds an opaque u32 handle. The Native Core owns all widget state; TypeScript only holds handles for command dispatch.
export abstract class Widget {
  public readonly handle: number;  // Opaque u32 from Native Core

  constructor(handle: number) {
    this.handle = handle;
  }
}

Handle Semantics

  • Unique: Each widget gets a unique sequential handle
  • Opaque: TypeScript never inspects handle contents
  • Invalid Sentinel: Handle(0) is permanently invalid
  • Never Recycled: Handles are never reused after destruction

Widget Lifecycle

Creation

When you instantiate a widget in TypeScript, it calls an FFI function to create the native node:
const text = new Text({ content: "Hello" });
// Internally:
// 1. tui_create_node(NodeType.Text) → returns handle
// 2. tui_set_content(handle, "Hello", 5)
// 3. Widget instance stores handle

Hierarchy Management

parent.append(child);
// FFI: tui_append_child(parent.handle, child.handle)
// Marks both parent and child dirty

Destruction

You must explicitly destroy widgets to free native resources. Garbage collection in TypeScript does not automatically clean up native handles.
widget.destroy();
// FFI: tui_destroy_node(handle)
// Orphans children (doesn't destroy them)

Built-in Widget Types

Kraken TUI provides several foundational widget types:
Flexible container using Flexbox layout. Supports direction, justification, alignment, gap, padding, and margin.
const box = new Box({
  direction: "column",
  gap: 1,
  padding: { top: 1, right: 2, bottom: 1, left: 2 }
});
Displays text content with optional rich formatting (Markdown, syntax highlighting).
const text = new Text({
  content: "# Hello World",
  format: "markdown"
});
Editable single-line text field with cursor, placeholder, password masking.
const input = new Input({
  placeholder: "Enter text...",
  password: false
});
Multi-line text editing with 2D cursor, word wrap, selection, undo/redo.
const editor = new TextArea({
  content: "Line 1\nLine 2",
  wrapMode: "soft"
});
Dropdown-style selection with arrow key navigation.
const select = new Select();
select.addOption("Option 1");
select.addOption("Option 2");
Scrollable viewport for content larger than available space.
const scroll = new ScrollBox();
scroll.append(largeContent);

Base Widget API

All widgets inherit from the base Widget class:

Visibility

widget.setVisible(false);  // Hide widget
const visible = widget.isVisible();  // Check visibility

Hierarchy Operations

widget.append(child);              // Add child to end
widget.insertChild(child, index);  // Insert at position
widget.removeChild(child);         // Remove child
const count = widget.childCount(); // Get number of children

Dirty Marking

widget.markDirty();  // Force re-render on next frame
Most property setters automatically mark widgets dirty. You rarely need to call markDirty() manually.

Focus Management

widget.setFocusable(true);  // Enable focus
widget.focus();             // Move focus to this widget

Accessibility

import { AccessibilityRole } from "kraken-tui";

widget.setRole(AccessibilityRole.Button);
widget.setLabel("Submit Form");
widget.setDescription("Submits the registration form");

Dirty Flag System

Kraken TUI uses dirty-flag optimization to minimize rendering work. Only widgets marked dirty (and their ancestors) are processed during layout and render.

How Dirty Propagation Works

  1. Property Change: Setting a widget property marks it dirty
  2. Upward Propagation: Dirty flag propagates up to ancestors
  3. Layout Pass: Only dirty subtrees recompute layout
  4. Render Pass: Only dirty cells update the terminal
text.setForeground("red");  // Marks text dirty
// Parent, grandparent, etc. also marked dirty
// Next render() only processes dirty subtree

Benefits

  • Performance: O(dirty nodes) instead of O(all nodes)
  • Efficiency: Unchanged widgets skip layout and render
  • Scalability: Large trees with small changes remain fast

Widget Tree Traversal

The Native Core performs several types of tree traversals:
Starting from root, computes layout for each dirty subtree using Flexbox constraints.Order: Parent before children (constraints flow down)Cost: O(dirty nodes)
Starting from root, renders each dirty widget’s content to the cell buffer.Order: Depth-first, left-to-rightCost: O(dirty nodes)
Tab key advances focus using depth-first, left-to-right order among focusable widgets.Order: Same as render orderCost: O(focusable nodes)
Mouse clicks traverse in reverse render order to find the topmost widget at (x, y).Order: Reverse depth-first (visual stacking order)Cost: O(all nodes) per click (acceptable for discrete events)

Best Practices

1

Destroy When Done

Always call destroySubtree() on root widgets when unmounting to free native resources.
const cleanup = () => {
  rootWidget.destroySubtree();
  app.shutdown();
};
2

Minimize Dirty Flags

Batch property updates before calling render() to avoid redundant dirty marking.
// Good: All changes in one frame
text.setForeground("red");
text.setBackground("black");
text.setBold(true);
app.render();
3

Use insertChild for Lists

When updating lists, use insertChild(item, index) instead of remove-all + append-all for efficiency.
// Good: Insert at position
list.insertChild(newItem, 2);

// Avoid: Remove and re-add everything
items.forEach(item => list.removeChild(item));
items.forEach(item => list.append(item));
4

Leverage Visibility

Use setVisible(false) instead of destroying and recreating widgets for temporary hiding.
modal.setVisible(false);  // Hide, preserves state
// Later...
modal.setVisible(true);   // Show again

Next Steps

Layout

Learn about Flexbox layout properties

Styling

Explore color and text decoration

Events

Handle keyboard and mouse input

Animation

Animate widget properties

Build docs developers (and LLMs) love