zeichenwerk

April 28, 2026 · View on GitHub

Version Go License

Zeichenwerk (German for "character works") is a modern, idiomatic Go library for building terminal user interfaces. It features a fluent builder API, a functional composition API, and an enhanced widget system.

Developed with and for AI coding assistants — see AI Assistance for details.

How it looks

Showcase

Quick Example

package main

import (
    . "github.com/tekugo/zeichenwerk"
    "github.com/tekugo/zeichenwerk/core"
    "github.com/tekugo/zeichenwerk/themes"
)

func main() {
    NewBuilder(themes.TokyoNight()).
        VFlex("main", core.Stretch, 0).
            HFlex("header", core.Center, 1).Hint(0, 1).
                Static("title", "My App").Font("bold").Foreground("$cyan").
            End().
            Grid("content", 1, 2, false).Columns(20, -1).Rows(-1).Hint(0, -1).
                Cell(0, 0, 1, 1).List("menu", "Item 1", "Item 2", "Item 3").
                Cell(1, 0, 1, 1).Button("action", "Click Me").
            End().
        End().
        Run()
}

Press q or Ctrl-Q to quit.

Composition API

The compose sub-package offers a functional alternative to the builder. Each widget is an Option — a plain function — that you nest directly:

package main

import (
    "github.com/tekugo/zeichenwerk/core"
    . "github.com/tekugo/zeichenwerk/compose"
    "github.com/tekugo/zeichenwerk/themes"
)

func main() {
    UI(themes.TokyoNight(),
        VFlex("main", "", core.Stretch, 0,
            HFlex("header", "", core.Center, 1,
                Static("title", "", "My App", Font("bold"), Fg("$cyan")),
            ),
            Grid("content", "", []int{-1}, []int{20, -1}, false, Hint(0, -1),
                Cell(0, 0, 1, 1, List("menu", "", []string{"Item 1", "Item 2", "Item 3"})),
                Cell(1, 0, 1, 1, Button("action", "", "Click Me")),
            ),
        ),
    ).Run()
}

Screens can be split into separate functions and composed with Include:

UI(themes.TokyoNight(),
    VFlex("root", "", core.Stretch, 0,
        Include(header),
        Include(content),
        Include(footer),
    ),
).Run()

func header(theme *core.Theme) core.Widget {
    return Build(theme, Static("title", "", "My App", Font("bold"), Fg("$cyan")))
}

Where direct widget access is needed after construction — for example to wire events, populate a tree, or start animations — retrieve the widget imperatively with core.Find (or the typed core.MustFind[T]) and call methods on it directly.

Why zeichenwerk

Zeichenwerk is designed for developers who want:

  • A fluent, chainable builder API or a functional composition API
  • Higher-level widgets than tcell
  • More composable layouts than tview
  • A traditional retained widget hierarchy rather than an event/message architecture

Compare to other Go TUI libraries:

LibraryStyle
tcellLow-level terminal primitives
tviewTraditional widget toolkit
bubbleteaElm-style update loop
zeichenwerkBuilder + functional composition

Installation

go get github.com/tekugo/zeichenwerk

Widgets

CategoryWidgets
ContainersBox, Card, Collapsible, CRT, Dialog, Flex, Form, FormGroup, Grid, Grow, Switcher, Tabs, Viewport
InputButton, Checkbox, Combo, Editor, Filter, Input, List, Select, Tree, TreeFS, Typeahead
DisplayBarChart, Breadcrumb, Canvas, Deck, Digits, Heatmap, Rule, Shortcuts, Sparkline, Static, Styled, Table, Tabs, Terminal, Text, Tiles
AnimatedClock, Marquee, Progress, Scanner, Shimmer, Spinner, Typewriter
OverlayCommands palette, Dialog, and container-based popups

Features

Event System

Each widget dispatches events; handlers run in reverse registration order and bubble up through parent containers until one returns true:

button := core.MustFind[*widgets.Button](ui, "submit")

// Typed helper — unwraps the int payload for you.
widgets.OnActivate(button, func(idx int) bool {
    // handle click
    return true
})

// Raw form when the data type isn't string/int (e.g. Checkbox sends bool).
checkbox.On(widgets.EvtChange, func(_ Widget, _ Event, data ...any) bool {
    checked := data[0].(bool)
    _ = checked
    return true
})

See doc/events.md for the full event list and per-widget payload table.

Styling & Themes

Built-in themes (in the themes sub-package):

  • themes.TokyoNight() — dark, blue/purple accents
  • themes.GruvboxDark() / themes.GruvboxLight() — retro warm palette
  • themes.Nord() — arctic blue-grey
  • themes.MidnightNeon() — near-black with electric cyan/magenta
  • themes.Lipstick() — Charm-inspired fuchsia and indigo

Each theme registers a colour palette ($bg0, $fg0, $cyan, …) plus default styles for every built-in widget. Per-widget overrides chain on the builder:

Static("title", "Dashboard").
    Foreground("$cyan").
    Background("$bg1").
    Font("bold").
    Padding(0, 2)

Button("ok", "Confirm").
    Background("$bg2").                // default state
    Background(":focus", "$blue").     // when focused
    Foreground(":focus", "$bg0")

Custom themes can be assembled from core.NewTheme() and styled by selector (type.class#id:state); see themes/tokyo-night.go for a minimal worked example.

Focus Navigation

  • Tab/Shift+Tab: Move focus between widgets
  • Arrow keys: Navigate within widgets (lists, tables)
  • Enter/Space: Activate buttons, toggle checkboxes

Mouse Support

  • Click to focus widgets
  • Hover states with visual feedback

Architecture

UI (root)
├── Component (embedded)
│   ├── Bounds (x, y, width, height)
│   ├── Styles (CSS-like selectors)
│   ├── Events (handlers, bubbling)
│   └── Parent/Child hierarchy
├── Layers (popups/modals)
├── Event Loop (tcell integration)
├── Renderer (dirty-flag optimizations)
└── Focus Manager

Demo

Explore all widgets interactively with the builder-API demo:

go run ./cmd/demo

Or the composition-API showcase:

go run ./cmd/compose

Documentation

Agentic-ready

Zeichenwerk ships with first-class support for AI agents and automated tooling that need to observe or interact with a running UI without a live terminal.

Widget hierarchy dump

Dump(w io.Writer, root Widget) streams the full widget tree to any writer as an indented, human- and LLM-readable text. One line per widget — type, ID, class, content summary, screen bounds, and state flags ([FOCUSED], [HIDDEN], [DISABLED]). Hidden containers are always included so every part of the UI is readable regardless of what is currently visible on screen.

// Snapshot the entire UI (all layers) to stdout
ui.Dump(os.Stdout)

// Dump a subtree
zeichenwerk.Dump(os.Stdout, someContainer)

// Include per-widget style details (border, padding, margin, fg/bg)
ui.Dump(os.Stdout, zeichenwerk.DumpOptions{Style: true})

Both demo binaries support -dump and `-dump-verbose$ \text{flags} \text{that} \text{lay} \text{out} \text{the} \text{UI} \text{at} \text{a} \text{fixed} 120 \times 40 \text{size}, \text{print} \text{the} \text{hierarchy}, \text{and} \text{exit} — \text{no} \text{terminal} \text{required}:

$``bash go run ./cmd/demo -dump go run ./cmd/demo -dump-verbose go run ./cmd/compose -dump


### Summarizer interface

Built-in widgets produce concise inline summaries (button labels, input values,
checkbox state, active tab, etc.). Custom widgets can opt in by implementing the
`Summarizer` interface:

```go
type Summarizer interface {
    Summary() string
}

AGENTS.md

AGENTS.md at the repository root is a machine-readable project guide kept up to date for AI coding assistants. It covers architecture rules, the full widget reference, selector format, event constants, and the builder checklist.

Claude Code skill

A Claude Code skill is bundled at .claude/skills/zeichenwerk/. When you open this repository in Claude Code, the skill is loaded automatically — Claude gains knowledge of all widget constructors, style keys, event constants, the selector format, and both APIs without requiring additional context in the prompt. A detailed widget reference is included in .claude/skills/zeichenwerk/widgets.md.

Development Status

Active development — Core API and widget set are stable. Test coverage is growing (bar chart, breadcrumb, button, checkbox, input, list, progress, select, switcher, table, tabs, text, viewport, and more are unit-tested). Some layout edge cases and advanced widgets are still being refined.

AI Assistance

This project was developed with the support of AI coding assistants — specifically Claude (Anthropic), StepFun-3.5-Flash, and Qwen-3.5 — for coding support, documentation drafting, and test creation.

That said, all code was reviewed, tested, fine-tuned, and adjusted by me, and all significant design decisions were made by me. AI assistants are well-trained on GUI and web development patterns, but terminal UI is a niche where their experience is limited. The retained widget hierarchy, layout engine, scroll regions, ANSI terminal emulation, and theme system required substantial manual coding, debugging, and iteration that went well beyond what any assistant produced out of the box.

At the same time, without the heavy lifting in coding, specification work, and documentation that AI agents made possible, a spare-time project of this scope simply would not be feasible for a single developer.

License

MIT © Thomas Rustemeyer