Composition
How layout primitives nest to build every page and section.
The design system has a strict layout hierarchy. Using the right component at each level gives you correct spacing, responsive behaviour, and semantic HTML — without arbitrary classes.
The Hierarchy
Six layers, each with a single responsibility. Each layer wraps the next. Never skip layers or substitute raw divs.
Your content
Heading, Text, Button, Badge, Inline…
Section
Provides vertical padding only. Never sets max-width or horizontal padding. Full-width by design.
Container
Provides horizontal constraint only. Never adds vertical padding. Never nest Container inside Container.
Surface
A card or panel, not a page band. For full-width backgrounds use Section, not Surface.
Stack
Does not wrap. Single-axis flex layout with token gaps. Use direction="horizontal" for rows.
Inline
Does wrap. Horizontal flex-wrap for buttons, badges, and chips. Contrast with Stack.
Grid
Multi-column layout with responsive column collapse. Use instead of Stack horizontal when mobile layout matters.
Decision Tree
Start here before writing any JSX. Match your need to the right component.
Layout
Full-width page shell (once per route)
Page<main>
Full-width band with vertical padding + background
Section<div className='py-16 bg-muted'>
Max-width constraint + horizontal gutters
Container<div className='max-w-6xl mx-auto px-6'>
Vertical list of elements with gaps
Stack<div className='flex flex-col gap-4'>
Horizontal non-wrapping row
Stack direction='horizontal'<div className='flex gap-4'>
Wrapping horizontal items (buttons, tags)
Inline<div className='flex flex-wrap gap-2'>
Responsive multi-column grid
Grid<div className='grid grid-cols-3'>
60/40 or 40/60 split layout
Grid cols='2-asymmetric'Card, panel, or boxed container
Surface<div className='rounded-xl bg-muted p-6'>
Typography
Page or section title
Heading as='h1|h2|h3' size='...'<h2 className='text-2xl'>
Body text
Text<p className='text-sm'>
Secondary/supporting text
Text variant='muted'<p className='text-muted-foreground'>
Small or caption text
Text size='s' or size='caption'Interactive
Primary action
Button<button className='bg-primary'>
Status label or tag
Badge<span className='rounded-full bg-muted px-2'>
Text input
Input + LabelGlass button on imagery
Button variant='glass'Live Examples
Common patterns built from the hierarchy. Copy and adapt.
Standard Content Section
The most common pattern. Section → Container → Stack → content.
Better Air. Everyday.
An intelligent home climate system that delivers heating, cooling, and purification.
Alternating Section Backgrounds
Use Section background prop — never raw bg-* on a div — to create visual rhythm between sections.
Default background
Muted background (Section background="muted")
Default again
Feature Grid
Section → Container → Stack (for header) + Grid → Surface → Stack → content.
Why Everyday
Air Quality
Medical-grade HEPA filtration for clean air.
Smart Control
Adaptive scheduling with real-time sensors.
Sustainability
Up to 70% less energy than fossil-fuel systems.
Inline vs Stack — The Wrapping Rule
Stack never wraps. Inline always wraps. This is the key distinction.
Anti-Patterns
Common shortcuts that bypass the system. Each one has a correct equivalent.
avoid
<div className="py-16 bg-muted">
<div className="max-w-6xl mx-auto px-6">
<h2 className="text-2xl font-bold">Title</h2>
</div>
</div>prefer
<Section spacing="lg" background="muted">
<Container size="xl">
<Heading as="h2" size="l">Title</Heading>
</Container>
</Section>Section + Container replace manual py-*, bg-*, max-w-*, mx-auto, and px-* classes entirely.
avoid
<div className="flex flex-col gap-6"> <h2>Title</h2> <p>Body text</p> </div>
prefer
<Stack spacing="lg"> <Heading as="h2" size="l">Title</Heading> <Text variant="muted">Body text</Text> </Stack>
Stack replaces div flex-col gap-*. It also ensures token-backed gaps instead of arbitrary values.
avoid
<div className="grid grid-cols-1 md:grid-cols-3 gap-6"> <div className="rounded-xl bg-muted p-6">…</div> <div className="rounded-xl bg-muted p-6">…</div> </div>
prefer
<Grid cols="3" gap="md"> <Surface tone="muted" radius="sm" padding="md">…</Surface> <Surface tone="muted" radius="sm" padding="md">…</Surface> </Grid>
Grid + Surface replace manual grid-cols-*, gap-*, rounded-*, bg-*, and p-* classes.
avoid
<div className="flex flex-wrap gap-2"> <span className="px-3 py-1 rounded-full bg-muted text-sm">Tag 1</span> <span className="px-3 py-1 rounded-full bg-muted text-sm">Tag 2</span> </div>
prefer
<Inline spacing="xs"> <Badge variant="secondary">Tag 1</Badge> <Badge variant="secondary">Tag 2</Badge> </Inline>
Inline + Badge replace flex-wrap divs with manual pill styling.
Block Composition
How marketing "blocks" map to the layout hierarchy. Each block is a Section with Container inside — not a standalone styled component.
BlockRenderer pattern (marketing)
// BlockRenderer maps CMS sections → layout hierarchy
<Page>
{sections.map((section) => (
<Section key={section._key} spacing={section.spacing}>
{section.fullBleed ? (
// Full-bleed: no Container — content touches edges
<ContentBlock section={section} />
) : (
// Constrained: Container inside Section
<Container size="xl">
<ContentBlock section={section} />
</Container>
)}
</Section>
))}
</Page>
// ContentBlock arranges content using Grid + Stack + Surface
function ContentBlock({ section }) {
return (
<Grid cols={section.cols ?? "2"} gap="lg">
{section.cells.map((cell) => (
<Stack key={cell._key} spacing="md">
<Heading as="h2" size="l">{cell.title}</Heading>
<Text variant="muted">{cell.body}</Text>
</Stack>
))}
</Grid>
);
}Every block is Page → Section → Container → Grid/Stack → content. The CMS controls content; the code controls structure.