---
url: /tui.zig/guide/custom-widgets.md
---
# Custom Widgets

Create reusable components by implementing the widget interface.

## Widget Structure

A widget in TUI.zig is any struct that implements `render` and optionally `handleEvent`.

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

pub const MyWidget = struct {
    label: []const u8,
    clicked_count: usize = 0,

    pub fn init(label: []const u8) MyWidget {
        return .{ .label = label };
    }

    pub fn render(self: *MyWidget, ctx: *tui.RenderContext) void {
        var screen = ctx.getSubScreen();

        // Draw content
        screen.putString(self.label);
    }

    pub fn handleEvent(self: *MyWidget, event: tui.Event) tui.EventResult {
        // Handle input
        switch (event) {
            .key => |k| {
                if (k.key == .enter) {
                    self.clicked_count += 1;
                    return .needs_redraw;
                }
            },
            else => {},
        }
        return .ignored;
    }
};
```

## State Management

Widgets can hold internal state (like `clicked_count` above). If state changes affect the display, return `.needs_redraw` from `handleEvent`.

## Composition

Your custom widget can contain other widgets:

```zig
pub const CompositeWidget = struct {
    button: tui.Button,
    text: tui.Text,

    pub fn init() CompositeWidget {
        return .{
            .button = tui.Button.init("Click", onClick),
            .text = tui.Text.init("Status: Idle"),
        };
    }

    pub fn render(self: *CompositeWidget, ctx: *tui.RenderContext) void {
        // Delegate rendering to child contexts with specific bounds
        var sub1 = ctx.child(tui.Rect.init(0, 0, 10, 1));
        self.button.render(&sub1);

        var sub2 = ctx.child(tui.Rect.init(0, 2, 20, 1));
        self.text.render(&sub2);
    }
};
```
