04 - Layout¶
FlowUI separates box styling from child arrangement:
| Owner | Controls |
|---|---|
Style |
Width, height, min/max, padding, margin, overflow, and paint |
| Layout container | Direction, gaps, alignment, flex growth, scrolling, and measurement |
Do not use a style to express sibling spacing or flex growth. Put that policy
on Row, Column, or another container.
Common containers¶
layout.Column(
text.New("Title").Size(20),
layout.Row(
button.New("Cancel"),
button.New("Save"),
).Gap(8),
).Gap(12)
Box gives one child a styled shell. Center centers its child in the
available constraints. Expanded and Flexible distribute remaining space.
Row and Column also accept Width, Height, FillWidth, FillHeight,
MaxWidth, and Padding. Padding follows CSS shorthand: one value for all
sides, two for vertical/horizontal, three for top/horizontal/bottom, or four
for top/right/bottom/left.
layout.Row(
sidebar.New("navigation", "home", items),
layout.Expanded(layout.Scroll(layout.Column(/* content */))),
)
Scrolling and grids¶
Scroll requires a stable key. Grid uses a fixed column count, while
AutoGrid chooses columns from a minimum column width.
layout.Scroll(layout.Column(/* many children */).Gap(8))
layout.Grid(3, cardA, cardB, cardC).Gap(16)
layout.AutoGrid(190, cardA, cardB, cardC).ColumnGap(16).RowGap(12)
Scroll reserves a scrollbar gutter only when its content overflows. On a
page whose height changes, such as settings tabs, switch to an overlay
scrollbar so the content width stays put when a short page follows a long one:
layout.Scroll(content).Overlay()
// Hide the bar and gutter while keeping wheel and touch scrolling.
layout.Scroll(content).WithoutScrollbar()
For an appended chat or log, use .FollowEnd() to start at the end and keep
following while the user remains there. Scrolling away pauses following until
the user returns to the end.
layout.Scroll is the single viewport entry point; layout.Scrollbar was
removed. .Style(...) merges root and PartTrack / PartThumb overrides.
Changing scrollbar mode does not replace or reset the scroll identity.
Visual outset and clipping¶
layout.Scroll, layout.List, and splitpane.New automatically reserve space
inside their viewport for shadows and outlines declared by Style. Components
with the common Box shell, such as Input and Card, need no extra margin.
The space is applied at viewport edges, not between every pair of list items.
An explicit OverflowHidden box remains a local clip boundary; child paint
already clipped there does not affect an outer viewport.
For custom paint that extends beyond its box, declare a minimum range with
style.VisualOutset, or use layout.VisualOutset when there is no Style shell:
layout.Box(customCanvas).Style(
style.New().VisualOutset(6, 8, 10, 8),
)
// top, right, bottom, left for direct custom paint.
layout.VisualOutset(ctx, gtx, customCanvas, 6, 8, 10, 8)
When a new extent is discovered, the viewport requests another frame to settle the safe padding. It never lets that paint escape the scroll viewport or window.
Run go run ./examples/showcase from the repository root for a runnable
layout composition. The grid code above is a usage snippet.
Other useful containers include Wrap, Stack, Surface, and Card.
The themed resize control is splitpane.New in widget/splitpane; visual
separators are divider.New in widget/divider. See the component guide for the full list.
Size and constraints¶
Declare dimensions on a style and apply it to a box or component:
layout.Box(input.New(m.Name)).Style(style.New().Width(320))
layout.Box(child).Style(
style.New().Width(200).
Height(40).
MinWidth(120).
MaxWidth(400).
Padding(12).
Margin(4),
)
Use AspectRatio for a fixed width-to-height ratio:
Parents provide maximum constraints. Children choose a size inside those constraints, and containers decide how children are placed.
A typical page shell¶
func View(_ *ui.Context, m Model, send ui.Send[Msg]) ui.Widget {
return layout.Column(
titlebar.NewPlatform("title", "FlowUI", menu),
layout.Expanded(layout.Row(
sidebar.New("sidebar", m.Selected, items),
layout.Expanded(layout.Scroll(content)),
)),
statusbar.New(left, right),
)
}
Build widgets every frame, keep keys stable, and use business IDs for list items whose order can change.