Layout
Components never pick a width. The layout hands them one.
Every component fills or hugs the bounds its parent provides. Anything that changes shape measures its own container, never the window. Only app chrome reads the viewport.
Breakpoints · form factors · page grid
base · desktop
Phone
≤ 640
Overlays as bottom sheets · Shells as drawers · Rows stack
Tablet
641 – 1024
Overlays anchored · Shells as drawers · Row spans by measure
Desktop
> 1024
Overlays anchored · Shells as rails · Page capped at 1280
A breakpoint applies at its width and below · unknown width (server, first frame) renders the desktop variant · useBreakpoint() and useFormFactor() are for window chrome only
Live · resize the container
drag the handle or pick a width
Phone
Tablet
Desktop
Fit
container
0px
base
Show sizing
Navbar folds its links into a menu
Stats auto-fit, no JS
Form and table sit side by side
Form fields in two columns
Members table turns into cards
Buttons go block
Same page, same props. The window never changes here: only the container does. Note the form: at Desktop it sits in a narrow column, so it measures compact and stacks its fields.
Sizing contract · every family
v4.0.0 rules
fill
stretches to the bounds it is given
hug
sizes to its content
fixed
intrinsic, never stretches
tiles
auto-fit by a minimum tile width
Floors are minWidth plus wrapping, never a fixed width · caps are Container steps · a nested surface is never wider or rounder than what holds it
How a component finds its size
try in this order
1
Intrinsic · no JavaScript
Fill or hug inside the parent's bounds, a minWidth floor, wrapping. Correct on the first frame and on the server.
<Grid minTileWidth={240}> · <Row wrap> · <Row stacks>
2
Container measurement · its own width
A component that changes shape measures itself against the shared breakpoints, so every family flips at the same widths.
useContainerBreakpoint({ base, md, sm }) · first frame renders base
3
Viewport · chrome only
Only window-level chrome reads the window: Sidebar and FilterPanel drawers, phone sheets, app shells.
useBreakpoint() · useFormFactor() · BreakpointOverride for previews
no width at a call site
no window reads in a component
no style escape hatch
DashboardGrid
12 columns · spans in twelfths
Revenue
span 8
Seats
4
1,204
Uptime
4
Latency
4
Errors
4
Funnel
5
Members
7
wide
honors every span
narrow
reflows to narrowSpan
phone
stacks full width
<DashboardGrid> measures its own width, never the window · each widget declares span and narrowSpan in twelfths
Layout · Row & Column · Container · Grid · Divider
no raw flex at a call site
Gap scale
flush
0
tight
4
snug
8
cozy
12
relaxed
16
loose
24
<Row cozy alignCenter between> · <Column tight> · <Row stacks>
Container steps
xxs
sm
lg
xxl
wide
page
13 steps, xxxs 192 → page 1280 · caps and centers, never a raw width
Grid
<Grid minTileWidth={72} columns={3}> + <GridItem> · measures its own width: the fourth tile wraps; 3-up, 2-up, 1-up
View
Text
Pressable
ScrollView
TextInput
React Native primitives, re-exported as-is. They take plain style objects and no skin; everything above them is Canvas.
Divider
or continue with
<Divider /> · <Divider>or</Divider> · <Divider vertical> · a hairline, never a grey
AppFrame · Page
safe areas · banner · capped page
banner region
page gutters
safe-area insets, all four edges
<AppFrame> mounts safe area + overlay host · <Page> is one screen