Skip to main content

Theming

Kraken TUI’s Theme Module provides a powerful system for managing visual style defaults across widget subtrees. Themes enable consistent styling without repetitive per-widget configuration.

Theme Architecture

The Theme Module is a separate bounded context within the Native Core. It owns theme definitions and subtree bindings, while the Style Module queries themes during style resolution.

How Themes Work

1

Define Theme

Create a theme and set default values for colors, decorations, borders, and opacity.
2

Apply to Subtree

Bind the theme to a widget. The theme applies to that widget and all descendants.
3

Style Resolution

When rendering, the Style Module resolves applicable theme via ancestry traversal, then merges theme defaults with explicit widget styles.
4

Explicit Wins

Explicit widget styles always override theme defaults.

Built-in Themes

Kraken TUI includes two built-in themes optimized for common terminal configurations:
import { Theme } from "kraken-tui";

const darkTheme = Theme.dark();
darkTheme.applyTo(rootWidget);
Handle: 1 (reserved)Defaults:
  • Foreground: white
  • Background: black
  • Border Color: bright-black
  • Opacity: 1.0
Built-in themes cannot be modified or destroyed. Calls to setForeground(), setBorderColor(), or destroy() on built-in themes will fail.

Custom Themes

Creating a Theme

import { Theme } from "kraken-tui";

const theme = Theme.create();

// Set global defaults
theme.setForeground("#E0E0E0");   // Light gray text
theme.setBackground("#1E1E1E");   // Dark gray background
theme.setBorderColor("#444444");  // Medium gray borders
theme.setOpacity(1.0);

// Set text decoration defaults
theme.setBold(false);
theme.setItalic(false);
theme.setUnderline(false);

// Set border style default
theme.setBorderStyle("none");

Applying a Theme

const rootWidget = new Box();
theme.applyTo(rootWidget);

// All descendants of rootWidget inherit theme defaults
const child1 = new Text({ content: "Hello" });
const child2 = new Box();

rootWidget.append(child1);  // Uses theme defaults
rootWidget.append(child2);  // Uses theme defaults

Theme Inheritance

Themes bind to subtrees. You can apply different themes to different branches:
const app = new Box();
const darkTheme = Theme.dark();
darkTheme.applyTo(app);

const modal = new Box();
const lightTheme = Theme.light();
lightTheme.applyTo(modal);

app.append(modal);

// app subtree uses dark theme
// modal subtree uses light theme (overrides)
Theme resolution traverses up the tree from the widget being styled. The nearest ancestor with a theme binding wins.

Clearing a Theme

Theme.clearFrom(widget);
// Removes theme binding from widget
// Descendants now inherit from nearest themed ancestor

Per-Widget-Type Defaults

Themes can specify defaults for specific widget types (Box, Text, Input, etc.):

Type-Specific Colors

import { Theme } from "kraken-tui";

const theme = Theme.create();

// Global defaults
theme.setForeground("white");
theme.setBackground("black");

// Input-specific overrides
theme.setTypeColor("input", "fg", "#FFD700");    // Gold text for inputs
theme.setTypeColor("input", "bg", "#2C2C2C");    // Dark gray background

// Text-specific overrides
theme.setTypeColor("text", "fg", "#00FF00");     // Green text for Text widgets

Type-Specific Decorations

theme.setBold(false);  // Global: no bold

// Text widgets are bold by default
theme.setTypeFlag("text", "bold", true);

// Input widgets are italic by default
theme.setTypeFlag("input", "italic", true);

Type-Specific Borders

theme.setBorderStyle("none");  // Global: no borders

// Box widgets get rounded borders
theme.setTypeBorderStyle("box", "rounded");

// ScrollBox widgets get single borders
theme.setTypeBorderStyle("scrollBox", "single");
theme.setTypeColor("scrollBox", "borderColor", "cyan");

Type-Specific Opacity

theme.setOpacity(1.0);  // Global: fully opaque

// Text widgets are slightly transparent
theme.setTypeOpacity("text", 0.9);

Available Widget Types

You can specify per-type defaults for:
  • "box" — Container widget
  • "text" — Text display widget
  • "input" — Single-line text input
  • "select" — Option selection widget
  • "scrollBox" — Scrollable container
  • "textarea" — Multi-line text editor
// From source: ts/src/theme.ts
type NodeTypeInput =
  | "box"
  | "text"
  | "input"
  | "select"
  | "scrollBox"
  | "textarea"
  | number;  // Raw NodeType enum value

Style Resolution Order

When the Style Module resolves a widget’s final style, it follows this precedence:
1

Explicit Widget Style (Highest Priority)

text.setForeground("red");  // Always wins
2

Per-NodeType Theme Default

theme.setTypeColor("text", "fg", "blue");
3

Global Theme Default

theme.setForeground("white");
4

System Default (Lowest Priority)

Terminal’s default foreground/background color.

Example: Resolution in Action

const theme = Theme.create();
theme.setForeground("white");            // Global default
theme.setTypeColor("text", "fg", "cyan"); // Text-specific default

const text1 = new Text({ content: "A" });
// Uses: cyan (type-specific wins over global)

const text2 = new Text({ content: "B" });
text2.setForeground("red");
// Uses: red (explicit wins over type-specific)

const box = new Box();
// Uses: white (no type-specific default for Box, uses global)

Theme Examples

Nord Theme

const nordTheme = Theme.create();

// Nord color palette
nordTheme.setForeground("#D8DEE9");    // Snow Storm
nordTheme.setBackground("#2E3440");    // Polar Night
nordTheme.setBorderColor("#4C566A");   // Polar Night (lighter)

// Accent colors for widget types
nordTheme.setTypeColor("text", "fg", "#88C0D0");  // Frost cyan
nordTheme.setTypeColor("input", "bg", "#3B4252"); // Polar Night (medium)
nordTheme.setTypeColor("input", "borderColor", "#5E81AC");  // Frost blue
nordTheme.setTypeBorderStyle("input", "rounded");

Dracula Theme

const draculaTheme = Theme.create();

draculaTheme.setForeground("#F8F8F2");  // Foreground
draculaTheme.setBackground("#282A36");  // Background
draculaTheme.setBorderColor("#6272A4"); // Comment

// Type-specific
draculaTheme.setTypeColor("text", "fg", "#8BE9FD");  // Cyan
draculaTheme.setTypeColor("input", "fg", "#50FA7B"); // Green
draculaTheme.setTypeColor("input", "bg", "#44475A"); // Current Line
draculaTheme.setTypeBorderStyle("box", "bold");
draculaTheme.setTypeColor("box", "borderColor", "#BD93F9");  // Purple

Solarized Dark Theme

const solarizedDark = Theme.create();

solarizedDark.setForeground("#839496");  // Base0
solarizedDark.setBackground("#002B36");  // Base03
solarizedDark.setBorderColor("#073642"); // Base02

// Accent colors
solarizedDark.setTypeColor("text", "fg", "#93A1A1");  // Base1
solarizedDark.setTypeColor("input", "fg", "#268BD2"); // Blue
solarizedDark.setTypeColor("input", "bg", "#073642"); // Base02

High Contrast Theme

const highContrast = Theme.create();

highContrast.setForeground("white");
highContrast.setBackground("black");
highContrast.setBorderColor("white");
highContrast.setBold(true);  // All text bold by default

// Strong visual distinctions
highContrast.setTypeColor("input", "fg", "yellow");
highContrast.setTypeColor("input", "borderColor", "yellow");
highContrast.setTypeBorderStyle("input", "bold");

highContrast.setTypeColor("text", "fg", "cyan");
highContrast.setTypeFlag("text", "bold", true);

Dynamic Theme Switching

Switch themes at runtime for dark/light mode toggles:
let isDark = true;
const darkTheme = Theme.dark();
const lightTheme = Theme.light();

function toggleTheme() {
  isDark = !isDark;
  const theme = isDark ? darkTheme : lightTheme;
  theme.applyTo(rootWidget);
  app.render();
}

// Bind to key press
app.on("key", (event) => {
  if (event.keyCode === KeyCode.F2) {
    toggleTheme();
  }
});

Theme Lifecycle

Themes are native resources. Always destroy custom themes when done to free memory.
const theme = Theme.create();
// ... use theme ...

// Cleanup
Theme.clearFrom(rootWidget);  // Remove binding first
theme.destroy();              // Then destroy theme
Built-in themes (dark/light) are global singletons and cannot be destroyed.

Theme Best Practices

1

Use Built-in Themes for Rapid Prototyping

Start with Theme.dark() or Theme.light(), then create custom themes for branding.
2

Define Per-Type Defaults Sparingly

Use per-type defaults for semantically distinct widget categories, not for one-off styling.
// Good: All inputs gold
theme.setTypeColor("input", "fg", "gold");

// Avoid: One-off styling
specialInput.setForeground("gold");  // Better
3

Apply Themes at Root Level

Bind themes high in the tree (ideally root widget) to minimize theme lookups during render.
4

Document Theme Color Palettes

Maintain a color palette reference for your custom themes to ensure consistency.
// Palette constants
const PALETTE = {
  bg: "#1E1E1E",
  fg: "#D4D4D4",
  accent: "#007ACC",
  border: "#3E3E3E",
};

theme.setForeground(PALETTE.fg);
theme.setBackground(PALETTE.bg);

Performance Considerations

Theme resolution uses ancestry traversal (O(depth) per widget). The Style Module caches resolved themes, so cost is paid once per dirty widget per frame.
Per-type defaults add one additional hash lookup during style resolution. Negligible overhead.
Applying a theme to a widget is O(1) (stores theme handle in subtree binding map). Descendants resolve theme lazily during render.

Next Steps

Styling

Learn about explicit widget styling

Animation

Animate colors and properties

Build docs developers (and LLMs) love