lotusui
A Go design system for desktop and mobile — one codebase, native apps. Web when you want it.
GitHub

Responsive

Continuous Max.X plus Theme breakpoints — reflow by default, stepped structure when you need it.

Every widget lays out from the Max.X it is given this frame — that is the continuous layer (Wrap, auto-fit SimpleGrid). For Chakra-style stepped structure (columns={{ base: 1, md: 2, lg: 4 }}), Theme owns named breakpoints and layout props resolve against them with a zero-alloc walk.

Resize the browser on the demos below. Apps override breakpoint sizes with JSON loaded at NewTheme — not registry.json.

The doctrine

Pass the width you have; never invent a fake one. Gio hands every layout a layout.Constraints. lotusui components honor it. The anti-pattern is clamping gtx.Constraints.Max.X to a constant “for the demo.”

Two layers: continuous reflow (Wrap, minChildWidth SimpleGrid) and stepped structure (Theme breakpoints + Cols / Dps / Bools / Show). Prefer continuous when equal tiles should grow smoothly; use stepped when column count or visibility must jump at named widths.

Theme breakpoints

Defaults (dp): base=0, sm=480, md=768, lg=992, xl=1280, 2xl=1536. Mobile-first: each step whose min ≤ Max.X overrides. Resolve is Theme.BreakpointIndex — O(steps), zero alloc. Layout structure only (columns, spans, gaps, page max, show/hide) — not every visual prop.

open demo ↗

Readable page column

LayoutPage(th, gtx, …) caps content at th.PageMax (default 920dp) or th.PageMaxAt when set. Inside that column, children still see the column width.

Go
lotusui.LayoutPage(th, gtx, func(gtx C) D {
	return lotusui.VStack(th.Space.MD,
		header,
		lotusui.SimpleGrid(th, gtx, cards, lotusui.SimpleGridProps{
			MinChildWidth: 180, MaxCols: 4, Gap: 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. Prefer it for badges and filter chips. HStack never wraps.

open demo ↗

SimpleGrid — continuous or stepped

SimpleGrid continuous mode: columns = min(MaxCols, floor(Max.X / MinChildWidth)). Stepped mode: set Columns: Cols(1).At("sm", 2).At("lg", 4) and ignore minChildWidth.

open demo ↗

Tabs and AnnotatedText

Horizontal Tabs and AnnotatedText use Wrap so labels never squeeze to one character under a narrow Max.X.

open demo ↗

Show / hide and Dialog width

Show(th, gtx, Bools(false).At("lg", true), w) hides below a step. Dialog / AlertDialog take Sizes / Widths for stepped card width — not Button density Size.

open demo ↗

Panes and scrolling

Split panes each get their own Max.X — put Wrap / SimpleGrid / Tabs inside. Scroll helpers are vertical overflow, not a substitute for horizontal reflow.

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 columnLayoutPage(th, …)Full-bleed Flexed edge-to-edge
Flowing chips / tagsWrapHStack (squeezes)
Equal tiles, smooth NSimpleGrid minChildWidthHard-coded column Flex
Equal tiles, stepped NSimpleGrid Columns / Grid.ColsHand if Max.X trees
Explicit spansGridNested HStacks
Hide below a stepShow + BoolsClamping Max.X to 0
Custom step minsParseBreakpointsJSON + WithBreakpointsEditing registry.json

lotusui is a Go design system for native desktop and mobile — and the web when you want it. Docs demos are the real components (WebAssembly) — one gallery app, each Preview an iframe addressed by URL hash.