The application framework for Bubble Tea.

Stop rebuilding the same architectural layer every time you ship a terminal app. BentoTUI gives you routing, layouts, focus management, dialogs, and theming.

go get github.com/cloudboy-jh/bentotui

You keep rebuilding the same things.

Every Bubble Tea app needs multi-panel layouts, focus routing, page switching, modal dialogs, and status bars. BentoTUI packages these patterns into composable Go packages so your app code stays focused on what makes it unique.

Everything you need. Nothing you don't.

The bento-box grid makes the package story recognizable at a glance.

app

Root shell and top-level routing, status, and dialog coordination.

router

Page registration, lazy creation, active page switching.

layout

Fixed/flex split containers for horizontal and vertical composition.

focus

Focus ring helper and focus cycling across components.

dialog

Modal manager and dialog message contracts.

statusbar

Contextual status line and key help surface.

panel

Bordered container for child content.

theme

Theme presets and custom token options.

Ship your first app in minutes.

Full working snippet from the project README.

quickstart.go
package main

import (
	"fmt"

	tea "charm.land/bubbletea/v2"
	"github.com/cloudboy-jh/bentotui"
	"github.com/cloudboy-jh/bentotui/core"
	"github.com/cloudboy-jh/bentotui/layout"
	"github.com/cloudboy-jh/bentotui/theme"
	"github.com/cloudboy-jh/bentotui/ui/components/panel"
)

func main() {
	m := bentotui.New(
		bentotui.WithTheme(theme.Preset("catppuccin-mocha")),
		bentotui.WithPages(
			bentotui.Page("home", func() core.Page { return newHomePage() }),
		),
		bentotui.WithFooterBar(true),
	)

	p := tea.NewProgram(m)
	if _, err := p.Run(); err != nil {
		fmt.Printf("run failed: %v\n", err)
	}
}

type homePage struct {
	root   *layout.Split
	width  int
	height int
}

func newHomePage() *homePage {
	sidebar := panel.New(panel.Title("Sidebar"), panel.Content(staticText("Sessions\nFiles")))
	main := panel.New(panel.Title("Main"), panel.Content(staticText("Welcome to BentoTUI")))
	root := layout.Horizontal(
		layout.Fixed(30, sidebar),
		layout.Flex(1, main),
	)
	return &homePage{root: root}
}

func (p *homePage) Init() tea.Cmd { return nil }
func (p *homePage) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
	_, cmd := p.root.Update(msg)
	return p, cmd
}
func (p *homePage) View() tea.View { return p.root.View() }
func (p *homePage) SetSize(w, h int) {
	p.width = w
	p.height = h
	p.root.SetSize(w, h)
}
func (p *homePage) GetSize() (int, int) { return p.width, p.height }
func (p *homePage) Title() string       { return "Home" }

type staticText string

func (s staticText) Init() tea.Cmd                           { return nil }
func (s staticText) Update(msg tea.Msg) (tea.Model, tea.Cmd) { return s, nil }
func (s staticText) View() tea.View                          { return tea.NewView(string(s)) }

Where we're headed.