The vtui Layout Engine provides a simple, declarative way to arrange UI elements inside dialogs and windows. It eliminates the need for manual coordinate math (e.g., x = dlg.X1 + 2; y = dlg.Y1 + 5), making your UI code cleaner, easier to maintain, and less prone to overlapping bugs.
In addition to VBoxLayout and HBoxLayout, vtui provides AutoLayout, a declarative constraint layout engine powered by Discrete Cassowary (github.com/unxed/kiwi-go).
AutoLayout combines linear constraint solving (equalities and inequalities with symbolic strengths) with TUI font-hinting heuristics:
- FreeType-style Autohinting (
ApportionWidths): Distributes integer rounding remainders across columns or groups so that component sizes sum to container totals with zero character gaps or screen overflows. - TrueType-style Rule Directives (
SnapWidthToGrid,EqualizeWidthsGroup): Enforces double-width grid snapping or equalizes sibling dimensions.
dlg := vtui.NewCenteredDialog(50, 15, " User Profile ")
lbl := vtui.NewLabel(0, 0, "Name:", nil)
edit := vtui.NewEdit(0, 0, 10, "John")
btnOk := vtui.NewButton(0, 0, "&Save")
btnCancel := vtui.NewButton(0, 0, "&Cancel")
dlg.AddItem(lbl)
dlg.AddItem(edit)
dlg.AddItem(btnOk)
dlg.AddItem(btnCancel)
// Define auto layout area
layout := vtui.NewAutoLayout(dlg.X1+2, dlg.Y1+2, 50-4, 15-4)
layout.
PinTop(lbl, 0).PinLeft(lbl, 0).
StackVertical(1, lbl, edit).
FillWidth(edit, 0, 0).
PinBottom(btnOk, 0).PinBottom(btnCancel, 0).
StackHorizontal(2, btnOk, btnCancel).
CenterHorizontalGroup(btnOk, btnCancel)
layout.Apply()The engine is based on two primary containers:
VBoxLayout: Stacks elements vertically (top to bottom).HBoxLayout: Stacks elements horizontally (left to right).
Instead of setting X and Y manually, you add elements to a layout container and specify Margins and Alignment.
Margins{Left, Top, Right, Bottom} define the empty space around an element.
- In a
VBoxLayout,TopandBottommargins add vertical spacing between stacked items. - In an
HBoxLayout,LeftandRightmargins add horizontal spacing.
vtui.Alignment dictates how an element behaves within the layout's available space:
AlignLeft/AlignRight/AlignCenter: Positions the element horizontally (VBox) or vertically (HBox) using its inherent width/height.AlignFill: Stretches the element to fill all available space in the cross-axis, minus the specified margins.
Here is how you build a standard input dialog without calculating a single coordinate:
dlg := vtui.NewCenteredDialog(40, 10, " User Info ")
// 1. Create elements with dummy coordinates (0, 0)
nameEdit := vtui.NewEdit(0, 0, 10, "")
ageEdit := vtui.NewEdit(0, 0, 10, "")
btnOk := vtui.NewButton(0, 0, "&Save")
btnCancel := vtui.NewButton(0, 0, "&Cancel")
// 2. Define the main vertical layout area
areaX, areaY := dlg.X1+2, dlg.Y1+2
areaW := 40 - 4
vbox := vtui.NewVBoxLayout(areaX, areaY, areaW, 6)
// Add items top-to-bottom
vbox.Add(vtui.NewLabel(0, 0, "Name:", nameEdit), vtui.Margins{}, vtui.AlignLeft)
vbox.Add(nameEdit, vtui.Margins{Top: 1}, vtui.AlignFill) // Stretches horizontally
vbox.Add(vtui.NewLabel(0, 0, "Age:", ageEdit), vtui.Margins{Top: 1}, vtui.AlignLeft)
vbox.Add(ageEdit, vtui.Margins{Top: 1}, vtui.AlignFill)
// 3. Apply coordinates to widgets
vbox.Apply()
// 4. Create a horizontal layout for buttons
hbox := vtui.NewHBoxLayout(areaX, dlg.Y1+8, areaW, 1)
hbox.HorizontalAlign = vtui.AlignCenter // Center the whole block of buttons
hbox.Spacing = 2 // 2 spaces between buttons
hbox.Add(btnOk, vtui.Margins{}, vtui.AlignTop)
hbox.Add(btnCancel, vtui.Margins{}, vtui.AlignTop)
hbox.Apply()
// 5. Add to Dialog
dlg.AddItem(...) // Add all elements to dlg- Use Layouts for structured forms: Forms with labels, inputs, and checkboxes benefit massively from
VBoxLayout. - Use
GrowModefor resizing: The Layout engine is currently a "one-time calculator" used during initialization. If your dialog supports manual resizing by the user, combine the initial Layout setup withSetGrowMode(e.g.,GrowHiX | GrowHiY) so widgets resize dynamically without re-running the layout engine.## Reference Implementation (Best Practice)
For a complete example of a complex layout with nested horizontal rows and a filling vertical center, see SelectFileDialog in vtui/common_dialogs.go.
Key patterns demonstrated there:
- The Root Stack: A
VBoxLayoutthat defines the overall vertical spacing of the dialog. - The "Label + Input" Row: Using an
HBoxLayoutwhere the Label has a fixed margin and the Edit field usesAlignFillto take up the remaining width. - The Expansion Area: Placing a
ListBoxorTablein the middle of aVBoxLayoutwithAlignFillso it scales with the dialog's height. - Grouped Buttons: An
HBoxLayoutwithHorizontalAlign = AlignCenterandSpacing = 2to create a professional-looking button bar at the bottom.