tcellplus
A minimal TUI widget library built on top of tcell/v3.
installation
go get codeberg.org/joetroll/tcellplus
concepts
Widget is the core interface:
typeWidgetinterface{Draw(stcell.Screen,x,y,w,hint)HandleEvent(evtcell.Event)boolBounds()(int,int,int,int)SetBounds(x,y,w,hint)SetFocus(fbool)IsFocused()bool}All built-in widgets embed Base, which satisfies the bounds and focus methods automatically. Custom widgets do the same.
App is the event loop:
app,_:=tcellplus.New()app.SetRoot(myWidget)// queue thread-safe UI updates from goroutinesapp.QueueUpdate(func(){myWidget.Content="done!"})app.Run()// handles events and drains update queuecommands & keymaps
Widgets decouple key events from logic using Commands and Keymaps:
km:=tcellplus.VimKeymap()// or DefaultKeymap(), EmacsKeymap()ev,_:=screen.PollEvent()cmd:=tcellplus.Resolve(ev,km)ifcmd==tcellplus.CmdUp{// ...}Most widgets have a Keymap field that defaults to DefaultKeymap().
widgets
| widget | description |
|---|---|
Input |
single-line text input |
Text |
scrollable text block, optional word wrap |
Table |
scrollable table with header row |
Box |
border with optional title, wraps a child widget |
Flex |
horizontal or vertical layout container |
Confirm |
simple yes/no confirmation dialog |
focus
FocusManager handles Tab/Shift+Tab cycling and mouse-click auto-focus across a set of widgets:
fm:=tcellplus.NewFocusManager()fm.Add(searchInput)fm.Add(table)fm.Add(detailView)fm.FocusByIndex(1)// focus the table programmatically// in your root widget's HandleEvent:returnfm.HandleEvent(ev)Widgets only process events when Focused == true. FocusManager sets and clears focus as the user navigates.
table
t:=tcellplus.NewTable([]string{"name","version"})t.Rows=[][]string{{"vim","9.1"},{"go","1.22"}}// column alignment and layoutt.ColumnWidths=[]int{20,0}t.ColAlign=[]int{tcellplus.AlignLeft,tcellplus.AlignRight}// dynamic row stylingt.RowStyleFn=func(rowint)tcell.Style{ifrow%2==0{returnzebraStyle}returntcellplus.DefaultStyle}// manual sortingt.SortBy(0,func(a,bstring)bool{returna<b})// if false, arrows move a highlight cursor; Enter commits the selectiont.SelectOnMove=falset.OnSelect=func(rowint){...}text
t:=tcellplus.NewText("hello\nworld")t.WordWrap=truet.Align=tcellplus.AlignCenterScrollbars render automatically on Text and Table when content overflows.
confirm modal
c:=tcellplus.NewConfirm("install package?")c.Visible=truec.OnConfirm=func(){install()}c.OnCancel=func(){c.Visible=false}// draw as overlay in your root widgetfunc(r*Root)Draw(stcell.Screen,x,y,w,hint){r.layout.Draw(s,x,y,w,h)r.confirm.Draw(s,x,y,w,h)}drawing helpers
tcellplus.Fill(s,x,y,w,h,' ',style)tcellplus.DrawStr(s,x,y,maxWidth,style,text)// unicode-awaretcellplus.DrawScrollbar(s,x,y,h,scroll,total)DrawStr uses go-runewidth for correct CJK and emoji column widths.
layout
flex:=tcellplus.NewFlex(tcellplus.Horizontal).Add(sidebar,20,0).// fixed 20 columnsAdd(table,0,1)// fills remaining spaceVertical and Horizontal are the two directions. Fixed > 0 takes priority; Grow distributes remaining space by weight.
colors
iftcellplus.Has256Color(){style=style.Background(tcellplus.Color256(68))}// truecolorstyle=style.Background(tcellplus.ColorRGB(30,30,46))examples
go run ./examples/demo # search + table
go run ./examples/pkgmgr # two-panel layout with FocusManager