Skip to content

Widgets

TUI.zig provides a rich set of built-in widgets for building interactive terminal interfaces.

Widget Basics

All widgets implement the same basic interface:

zig
pub fn render(self: *Self, ctx: *RenderContext) void;
pub fn handleEvent(self: *Self, event: Event) EventResult;

Built-in Widgets

Text

Display styled text with alignment and wrapping:

zig
const tui = @import("tui");

// Simple text
var text = tui.widgets.Text.init("Hello, World!");

// Styled text
var styled = tui.widgets.Text.init("Important!")
    .withStyle(tui.Style.default.setFg(tui.Color.red).bold())
    .withAlignment(.center);

Button

Clickable buttons with hover and press states:

zig
var button = tui.widgets.Button.init("Click Me!")
    .withOnClick(onButtonClick);

fn onButtonClick() void {
    // Button was clicked
}

Input Field

Single-line text input with cursor:

zig
var input = tui.widgets.InputField.init(allocator)
    .withPlaceholder("Enter your name...")
    .withMaxLength(50);

// Get the entered value
const value = input.getValue();

Text Area

Multi-line text editing:

zig
var textarea = tui.widgets.TextArea.init(allocator)
    .withLineNumbers()
    .withWordWrap();

// Set content
try textarea.setText("Line 1\nLine 2\nLine 3");

Checkbox

Toggle checkboxes:

zig
var checkbox = tui.widgets.Checkbox.init("Enable feature");

// Check state
if (checkbox.isChecked()) {
    // Feature is enabled
}

Progress Bar

Visual progress indicators:

zig
var progress = tui.widgets.Progress.init()
    .withLabel("Loading...")
    .withShowPercentage(true);

// Update progress (0.0 - 1.0)
progress.setValue(0.75);

Spinner

Animated loading indicators:

zig
var spinner = tui.widgets.Spinner.init(.dots)
    .withLabel("Processing...");

// In your render loop, update the animation
spinner.tick();

Available spinner styles:

  • .dots - ⠋ ⠙ ⠹ ⠸ ⠼ ⠴ ⠦ ⠧
  • .line - - \ | /
  • .circle - ◐ ◓ ◑ ◒
  • .square - ◰ ◳ ◲ ◱
  • .arrow - ← ↖ ↑ ↗ → ↘ ↓ ↙

List View

Scrollable item lists with selection:

zig
var list = tui.widgets.ListView.init(allocator, []const u8);

try list.addItem("Item 1");
try list.addItem("Item 2");
try list.addItem("Item 3");

// Get selected item
if (list.getSelectedItem()) |item| {
    // Process selected item
}

Table

Data tables with columns:

zig
var table = tui.widgets.Table.init(allocator);

try table.addColumn("Name", 20);
try table.addColumn("Email", 30);
try table.addColumn("Role", 15);

try table.addRow(.{ "John Doe", "john@example.com", "Admin" });
try table.addRow(.{ "Jane Smith", "jane@example.com", "User" });

Tabs

Tabbed navigation:

zig
var tabs = tui.widgets.Tabs.init(allocator);

try tabs.addTab("Home", &home_widget);
try tabs.addTab("Settings", &settings_widget);
try tabs.addTab("Help", &help_widget);

Dialog overlays:

zig
var modal = tui.widgets.Modal.init(allocator)
    .withTitle("Confirm Action")
    .withContent(&confirm_widget)
    .withButtons(.{ "OK", "Cancel" });

modal.show();

Creating Custom Widgets

You can create your own widgets by implementing the required interface:

zig
const MyWidget = struct {
    // Your state
    value: i32 = 0,

    pub fn render(self: *MyWidget, ctx: *tui.RenderContext) void {
        var screen = ctx.getSubScreen();
        screen.setStyle(tui.Style.default.setFg(tui.Color.green));
        screen.moveCursor(0, 0);

        var buf: [32]u8 = undefined;
        const text = std.fmt.bufPrint(&buf, "Value: {d}", .{self.value}) catch "?";
        screen.putString(text);
    }

    pub fn handleEvent(self: *MyWidget, event: tui.Event) tui.EventResult {
        switch (event) {
            .key => |k| switch (k.key) {
                .up => { self.value += 1; return .needs_redraw; },
                .down => { self.value -= 1; return .needs_redraw; },
                else => {},
            },
            else => {},
        }
        return .ignored;
    }
};

See the Custom Widgets guide for more details.

Released under the MIT License.