A component framework for building terminal UIs in Go, built on top of Bubble Tea.
Mate provides a composable component tree with focus management, keyboard and mouse event routing, global key bindings, and popup window support. You build your UI by composing components and setting callbacks — no custom Update() or View() methods needed.
go get github.com/muralx/matepackage main
import (
"fmt"
tea "github.com/charmbracelet/bubbletea"
"github.com/muralx/mate/widget"
"github.com/muralx/mate/window"
)
func main() {
win := window.NewWindow("main") // defaults to TCB layout
panel := widget.NewPanel("panel")
panel.SetBorder(widget.DefaultBorder())
nameInput := widget.NewTextInput("name", 30)
nameInput.WithPlaceholder("Enter your name")
panel.Add(widget.NewField("field", "Name", nameInput, widget.DefaultFieldStyles()), widget.Next)
submitBtn := widget.NewButton("submit", "Submit", widget.DefaultButtonStyles())
submitBtn.OnPress(func() tea.Cmd {
fmt.Println("Submitted:", nameInput.Value())
return tea.Quit
})
panel.Add(submitBtn, widget.Next)
win.Add(panel, widget.TCBCenter)
app := window.NewApp(win)
tea.NewProgram(app, tea.WithAltScreen(), tea.WithMouseAllMotion()).Run()
}Components — Button, TextInput, Toggle, CheckboxList, TabComponent, Table, ScrollableText, Card, Text, Field
Layouts — Panel supports Vertical (stack top-to-bottom), Horizontal (stack left-to-right), and TCB (Top-Center-Bottom, center flexes to fill space)
Focus Management — Tab/Shift-Tab cycling, click-to-focus, ID-based focus, automatic focus restoration after popup close
Key Bindings — Global shortcuts registered on any component, resolved by walking the component tree
Mouse Support — Hit testing, click-to-focus, click events dispatched to components
Windows & Popups — NewWindow for the main screen, NewPopupWindow for overlays with Close(result) / OnResult callbacks
Zero Boilerplate — No custom Update() or View(). Compose a tree, set callbacks, done.
Bubble Tea follows the Elm Architecture: you write a Model, an Update function that handles messages, and a View function that renders state. This works well, but as UIs grow complex you end up manually routing events, tracking focus, and wiring up component trees inside Update.
Mate takes a different approach. Instead of writing Update and View functions, you compose a component tree and set callbacks. Mate handles event routing, focus management, and rendering automatically. It uses Bubble Tea as its runtime — terminal I/O, the event loop, and tea.Cmd for side effects all work the same way.
| Bubble Tea (Elm) | Mate | |
|---|---|---|
| State | Immutable Model, Update returns new model | Mutable components, methods mutate in place |
| Events | Central Update handles all messages | Routed automatically to focused component |
| Rendering | Pure View function | Components render themselves in a tree |
| Wiring | You write it | Framework handles focus, routing, popups |
| Side effects | tea.Cmd |
tea.Cmd (same) |
You don't need to understand the Elm Architecture to use Mate. If you're coming from Bubble Tea and want to keep writing raw Update/View, Mate might not be for you — it's designed for apps that want a higher-level component model.
┌─────────────────────────────────────────────┐
│ App (tea.Model) │ window/
│ ┌────────────────────────────────────────┐ │
│ │ MainWindow (TCB layout) │ │
│ │ ┌──────────────────────────────────┐ │ │
│ │ │ Panel (TCBCenter: content) │ │ │ widget/
│ │ │ ┌────────────────────────────┐ │ │ │
│ │ │ │ Field: Label + TextInput │ │ │ │
│ │ │ ├────────────────────────────┤ │ │ │
│ │ │ │ Button "Submit" │ │ │ │
│ │ │ └────────────────────────────┘ │ │ │
│ │ └──────────────────────────────────┘ │ │
│ └────────────────────────────────────────┘ │
│ FocusManager │ input/
└─────────────────────────────────────────────┘
Three packages:
widget/— Components, containers, layouts, and theComponentinterfaceinput/— Focus management and key binding resolutionwindow/— Windows, popups, and the Bubble Tea adapter
Full documentation is in the docs/ directory: