lotusui the ikaito design language for Gio — desktop, mobile, web
GitHub

Responsive

Continuous Max.X is the breakpoint — build screens that reflow, never CSS media queries.

lotusui apps are responsive by construction when you compose with the constraint-driven primitives. There is no breakpoint enum and no @media twin: every widget lays out from the Max.X (and Max.Y) it is given this frame. Narrow the window, a Split pane, or a docs demo box — the same code path reflows.

Use this page as the map. Each mechanism has its own component page for props and edge cases; the live demos below are the real gallery states — resize the browser to see them change.

The doctrine

Pass the width you have; never invent a fake one. Gio hands every layout a layout.Constraints. lotusui components honor it — buttons hug, grids derive columns, Wrap starts a new line. The anti-pattern is clamping gtx.Constraints.Max.X to a constant “for the demo”: that freezes reflow and lies about how the screen behaves in a real window.

Desktop, mobile, and the WASM docs gallery share one model. A narrow Split detail pane is the same problem as a phone-sized window: a smaller Max.X. Design for constraints, not for device labels.

Readable page column

LayoutPage caps content at 920dp and centers it with padding — the difference between a stretched OS window and a designed page. Inside that column, children still see the column width, so Wrap and SimpleGrid keep reflowing as the window grows past 920dp (the column stops growing) or shrinks below it.

Go
lotusui.LayoutPage(gtx, func(gtx C) D {
	return lotusui.VStack(th.Space.MD,
		header,
		lotusui.SimpleGrid(gtx, cards, 180, 4, th.Space.SM, cardCell),
		lotusui.Wrap(th.Space.SM, layout.Middle, chips...),
	)(gtx)
})

Wrap — flowing chips and labels

Wrap is CSS flex-wrap for Gio: each child is measured at intrinsic width, then packed left-to-right; when the next child would exceed Max.X, a new line starts. Same gap between items and between lines. Prefer it for badges, filter chips, and any row that must not squeeze labels into one-character columns.

HStack never wraps — under a narrow Max.X its Rigid children are squeezed. Use HStack for a single non-wrapping unit (switch + label); use Wrap for a flowing set.

Go
lotusui.Wrap(th.Space.SM, layout.Middle,
	chip("Design"), chip("Engineering"), chip("Product"),
	chip("Marketing"), chip("Sales"), chip("Support"),
	// …)

SimpleGrid — columns from width

SimpleGrid derives column count = min(maxCols, floor(available / minChildWidth)) (never below 1). No breakpoint table: continuous width in, column count out. Rows share the tallest cell's height. Reach for Grid only when you need explicit spans.

Go
lotusui.SimpleGrid(gtx, items, 140, 4, th.Space.SM,
	func(gtx C, it Item) D { return cell(it)(gtx) })

Tabs and AnnotatedText

Horizontal Tabs lay the strip with Wrap, so whole tabs flow to the next line at intrinsic width — never a 1-character squeezed label in a narrow pane. The default well grows with wrapped height. Vertical stays a column Flex.

AnnotatedText (glossary segments) uses the same Wrap composition (gap 0) so inline terms wrap instead of compressing.

Go
tabs := lotusui.Tabs{Options: lotusui.TabOpts(
	"Changes", "Staging", "Production", "Reviews", "Approvals", "History",
)}
// under a narrow Max.X the strip wraps to two lines

Panes, columns, and scrolling

Split gives each visible pane its own Max.X from the carousel layout — detail panes are narrower than solo full-width. Put Wrap / SimpleGrid / Tabs inside a pane and they reflow to that width; do not clamp Max.X further “for safety.” Use SplitColumnScroll / SplitBoxScroll / SplitBoxFillScroll when content is taller than the viewport; scrolling is vertical overflow, not a substitute for horizontal reflow.

For the main screen body, Scrollable (mixed content) and ListView (virtualized rows) honor the width they are given the same way.

Go
s.Layout(gtx, th.Space.MD, depth,
	lotusui.SplitBox(th, listPane),
	lotusui.SplitBox(th, func(gtx C) D {
		return lotusui.Wrap(th.Space.SM, layout.Middle, filters...)(gtx)
	}),
)

Choosing the right primitive

NeedUseAvoid
Readable app columnLayoutPageFull-bleed Flexed content edge-to-edge
Flowing chips / tagsWrapHStack (squeezes)
Equal tiles, N columnsSimpleGridHard-coded column Flex
Explicit spansGridNested HStacks
One non-wrapping rowHStackWrap (may break the unit)
Tab strip under narrow Max.Xhorizontal Tabs (Wrap)Hand-rolled Flex+Rigid labels
Multi-pane screenSplit + reflow inside panesFixed dp columns that ignore Max.X

What lotusui does not ship

No named breakpoints (sm/md/lg), no show/hide-at-width helpers, no separate “mobile” component set. Those are web-era answers to CSS layout. In Gio the continuous constraint is the responsive API — if a screen must change structure (not just reflow), the app owns that policy with ordinary Go (if gtx.Constraints.Max.X < gtx.Dp(480) { … }), not a library mode flag.

Every demo on this site is the real Go component compiled to WebAssembly — one gallery app, one bundle, addressed by URL hash.