A spec-driven system for building UI components across programming languages. Each component is defined as a language-agnostic markdown spec with behavioral tests. An LLM agent acts as the "compiler" — reading specs and generating idiomatic implementations per target framework.
flowchart LR
Specs["Spec files\n(.md)"] --> Compile["compile.ts\n(prompt)"]
Compile --> Agent["LLM Agent\n(compiler)"]
Agent --> Dist["dist/{target}/\n(generated code)"]
Agent --> Lock["lock file\n(.json)"]
- Specs define behavior + semantic tokens (like headless UI libraries)
- Target specs define how to translate to a specific language/framework
- compile.ts detects changed specs and generates a self-contained prompt
- An LLM agent reads the prompt and generates idiomatic code
- Generated code goes to dist/ — specs stay clean
- Lock files track which spec versions have been compiled
- Bun 1.1+ installed (
bun --version)
bun install# Lint all specs against the schema
bun run lint
# Check what needs compiling
bun run compile status
# Generate a prompt for a target
bun run compile prompt --target go
# The prompt is written to dist/go/_compile-prompt.md
# Feed it to an LLM agent (e.g. Copilot CLI, Claude, etc.)
# The agent writes generated code to dist/go/
# After verifying the generated code works, lock the hashes
bun run compile lock --target goTUIKit/
components/ Component specs, tests, and preview definitions
tokens/ Semantic design tokens (colors, icons, breakpoints)
targets/ Target language/framework definitions
docs/ Meta-schema and design foundations
scripts/ Compiler and linter CLIs
dist/ Compiled output per target (gitignored)
Each component is a markdown file with YAML frontmatter and prose body:
---
kind: component
name: MyComponent
description: One-line summary.
version: 1
category: input # input | display | navigation | layout | feedback
tokens:
colors: [textPrimary, selected]
icons: [iconPrompt]
props:
label:
type: string
required: true
description: Display text.
dependencies:
tokens:
- name: textPrimary
kind: color
usage: "Label text"
required: true
components: []
accessibility:
role: button
announce:
on_mount: "Button: {label}"
---
## Visual rules
- Label text MUST use the `textPrimary` color token
- Active state MUST use the `selected` color token
## Rendering example
Given label: "Click me"
```
Click me
```
## Dependencies
| Dependency | Kind | Usage | Required |
|------------|------|-------|----------|
| `textPrimary` | color | Label text | Yes |
| `selected` | color | Active state | Yes |Test specs live alongside component specs and use a block-based format:
---
kind: test
component: MyComponent
version: 1
---
## renders label text
`props
label: "Hello"
`
`expect
Hello
`See docs/schema.md for the full format reference, including input, state,
style, and accessibility test blocks.
All normative sections (Visual rules, Behavior, Edge cases) use RFC 2119 keywords:
- MUST — absolute requirement
- SHOULD — strong recommendation
- MAY — optional behavior
- MUST NOT — absolute prohibition
| Target | Language | Framework | File |
|---|---|---|---|
go |
Go | Bubbletea + Lipgloss | targets/go.md |
node |
TypeScript | Ink + React (Node.js) | targets/node.md |
bun |
TypeScript | OpenTUI + React (Bun) | targets/bun.md |
rust |
Rust | Ratatui + Crossterm | targets/rust.md |
# 1. See what's changed
bun run compile status
# 2. Generate the compilation prompt
bun run compile prompt --target go
# 3. Feed dist/go/_compile-prompt.md to an LLM agent
# The agent generates code into dist/go/
# 4. Verify: run tests, check the demo CLI
cd dist/go && go test ./... && go run ./cmd/demo
# 5. Lock the hashes
bun run compile lock --target goA single compilation pass across the full component suite (17 components + tokens + demo) is usually not enough to reach production quality. We've found that 2–3 passes produce notably better results:
| Pass | Focus | Typical outcome |
|---|---|---|
| 1st | Initial generation | All components scaffold correctly, most tests pass, demo wires up. Expect rough edges — missing edge cases, incomplete keybindings, demo wiring bugs. |
| 2nd | Review & fix | Agent reviews its own output against specs, fixes test failures, fills in missing behavior, improves demo interactivity. Test count typically grows 30–50%. |
| 3rd | Polish | Catches subtle spec violations, improves accessibility, hardens demo --snapshot smoke tests. Diminishing returns after this point. |
To run a follow-up pass, generate a new prompt and tell the agent to review and complete its existing work:
# Generate a fresh prompt (it sees the current dist/ state)
bun run compile prompt --target go
# Feed to the agent with instructions like:
# "Review your existing implementation against the specs.
# Fix any test failures, fill in missing behavior,
# and ensure all --snapshot smoke tests pass."Each pass is fast because the agent builds on its own prior output rather than
starting from scratch. The demo's --list and --snapshot flags make it easy
for the agent to self-verify between passes.
By default, compiled code goes to dist/. Override with --out:
# Output to a separate repo or directory
bun run compile prompt --target go --out ~/my-tuikit-go
# The prompt and generated code go to ~/my-tuikit-go/go/- Create
targets/{name}.mdfollowing the target spec format indocs/schema.md - Define: architecture pattern, type mapping, callback translation, state machine pattern, token access, styling, composition, test pattern, key mapping, dependencies, and demo CLI
- Run
bun run compile status— your target will show up with all specs dirty - Run
bun run compile prompt --target {name}and compile
# Lint all specs
bun run lint
# Lint a single component
bun run lint --component Select
# Show fix suggestions
bun run lint --fix
# See all rules
bun run lint --helpThe linter checks:
- Required frontmatter fields and valid values (zod schemas)
- Naming conventions (PascalCase components, camelCase props)
- RFC 2119 keyword usage in normative sections
- ARIA accessibility structure for interactive components
- Token cross-references resolve to known tokens
- Required body sections (Visual rules, Rendering example, Dependencies)
- Test specs reference existing components
- Broken internal markdown links
Rule definitions live in scripts/lint-rules.ts — edit that file to add or
change rules, severities, and fix hints.
The GitHub Actions workflow (.github/workflows/specs-ci.yml) runs on every PR:
- Spec lint —
bun run lint - Compiler health —
bun run compile statusfor each target - Prompt smoke test —
bun run compile promptfor each target - No generated output committed — ensures
dist/is not tracked - Changed-spec completeness — if
{Name}.mdchanges, matching.test.mdand.preview.mdmust also change
-
Specs capture intent, not implementation — ~95% behavioral intent vs. ~5% framework hints. This lets agents generate idiomatic code per framework rather than awkward transliterations.
-
Color tokens define meaning, not color values — tokens like
textPrimaryandselecteddefine UI roles. The color engine (Rampa, hardcoded hex, ANSI palette) is an implementation detail per target. -
Layout is out of scope — specs define behavior and semantic tokens. Spacing, padding, and spatial polish are per-target decisions (similar to headless UI libraries like Radix or Base UI).
-
Lock files enable incremental compilation — only dirty specs trigger regeneration. Schema changes invalidate everything. Lock files are gitignored; a fresh clone starts with everything dirty.
For TUI design foundations — color systems, typography, iconography, layout
grids, accessibility patterns, keybinding conventions, and buffer management —
see docs/foundations.md.
This project is licensed under the MIT License.