Skip to content
TrademarkTrademark
Docs
Basics

Structure

Updated: October 10, 2026

Layout in Once UI is composition, not stylesheets. You nest a handful of primitives and set props on them; there is no CSS file, no class names, and no media queries.

There are only two primitives. Flex lays children out along one axis, Grid along two. Everything else — a Card, a Button, a Dialog — is built from them and accepts the same props.

Row and Column

You will almost always write Row or Column rather than Flex. They are the same component with direction preset, and they say what they mean:

Reach for Flex itself only when the direction changes at a breakpoint, which is the one thing a preset cannot express: <Flex s={{ direction: "column" }}>.

The page skeleton

Nearly every page is the same three layers — a full-width main element, a centred column that caps the reading width, and the sections inside it:

maxWidth caps the line length, horizontal="center" centres that cap in the viewport, and the gaps do the rest. Sections sit far apart ("80"–"160"), content inside them sits close ("24"–"40").

The app shell

A dashboard scrolls a pane, not the page, so the header and sidebar stay put. The pane is "the viewport below the header" tall, and that is a value of its own: height="below-header".

"below-header" is calc(100dvh - var(--header-height)), and height, minHeight and maxHeight all take it — maxHeight="below-header" caps a mobile menu or a dropdown that hangs from the header. --header-height is a token, 56px by default (the header above), so most shells never set it. One with a different header sets it once, and every pane follows:

It is read where it is used, so it can also be set on a subtree. A sticky offset that should follow it is top="calc(var(--header-height))".

Space comes from gaps

Space between siblings is gap on the parent. Space inside an element is padding. Between them they cover almost everything:

Prefer both over margin. A layout built from gaps stays correct when a child is added, removed or reordered; one built from margins has to be re-tuned each time, and margins collapse into each other in ways gaps do not.

Everything is a token

Sizes, colours, radii and shadows are named values, not numbers you invent:

gap="16" is a step on the spacing scale, not 16 pixels — it moves with the data-scaling setting. neutral-alpha-medium is a scheme and a weight, so it follows the theme instead of being re-picked for dark mode. If a value cannot be expressed as a token, that is usually a sign the design has drifted from the system rather than a reason to reach for style.

Responsive without media queries

Every layout prop can be overridden per breakpoint. Pass an object to xl, l, m, s or xs; they cascade downward, so s also covers xs.

Where to go next

That is the whole model. The full prop reference lives on the two primitives:

  • Flex — direction, alignment, size, spacing, surface, display and text, each with its table
  • Grid — columns and rows, plus everything Flex has
  • Spacing — the scale behind gap and padding
  • Color — schemes, weights and alpha
  • Responsive — the breakpoints themselves
Edit this page on GitHub