RoadieRoadie
  • Home
  • Brand
  • Foundations
  • Content
  • Tokens
  • Components
  • Charts
  • Widgets
  • Appearance
HomeBrandFoundations
Appearance

Foundations

  • Visual

    • Colors
    • Elevation
    • Iconography
    • Shape
    • Theming
  • Content

    • Date and time
    • Layout
    • Prose styles
    • Typography
  • Behaviour

    • Accessibility
    • Interactions
    • Motion
    • View transitions
  • Building apps

    • Forms
    • Linking
    • Navigation
    • Performance
    • Records modelAlpha
    • Tables

Layout

Docs updated
9 Oct 2026

ReferenceShape and layout tokens

Default to grid. Use gap, not margin. Set constraints, not fixed dimensions. Let the browser do the work.

Grid or flexbox

The 1D versus 2D framing is a false distinction. The real question is who controls sizing.

Grid, where the parent controls sizing

The container defines tracks and places content inside them. Columns are consistent regardless of content variation. It's the default for most layouts, including vertical stacks.

  • grid gap-4
  • grid grid-cols-3 gap-4
  • grid place-content-center

Flex, where the children control sizing

Items negotiate their own sizes based on content. It's ideal when layout should follow content, such as tags, nav items, pill lists, and wrapping rows where items should be as wide as they need to be.

  • flex items-center gap-4
  • flex flex-wrap gap-2

Spacing scale

Based on a 4px unit. Use it with gap-, p-, m-, w-, and h-. On the docs site, each common step is a bar as wide as the step, with its size in pixels.

  1. 0
    0px
  2. 0.5
    2px
  3. 1
    4px
  4. 1.5
    6px
  5. 2
    8px
  6. 3
    12px
  7. 4
    16px
  8. 5
    20px
  9. 6
    24px
  10. 8
    32px
  11. 10
    40px
  12. 12
    48px
  13. 16
    64px
  14. 20
    80px
  15. 24
    96px

Breakpoints

Viewport breakpoints are for page-level layout. Prefix a class with one, such as sm:grid-cols-2, to apply it from that width up. On the docs site, each breakpoint is listed with its width.

  1. sm:40rem (640px)
  2. md:48rem (768px)
  3. lg:64rem (1024px)
  4. xl:80rem (1280px)
  5. 2xl:96rem (1536px)

Container utility

The container-* utility centres content with responsive horizontal padding and a configurable max width. It replaces the v1 <Container> component.

<div className='container-8xl'>...</div>
<div className='container-4xl'>...</div>

Any --container-* token works: Tailwind's 3xs to 7xl plus Roadie's 8xl. On the docs site, each width is listed with its class.

  1. container-3xs16rem (256px)
  2. container-2xs18rem (288px)
  3. container-xs20rem (320px)
  4. container-sm24rem (384px)
  5. container-md28rem (448px)
  6. container-lg32rem (512px)
  7. container-xl36rem (576px)
  8. container-2xl42rem (672px)
  9. container-3xl48rem (768px)
  10. container-4xl56rem (896px)
  11. container-5xl64rem (1024px)
  12. container-6xl72rem (1152px)
  13. container-7xl80rem (1280px)
  14. container-8xl90rem (1440px)

Responsive padding

BreakpointInline padding
Default1.5rem (24px)
md2rem (32px)
lg3rem (48px)

Sizing

Set constraints, not fixed dimensions. Let content breathe and the layout flex.

Constraints over fixed sizes

Use min-h- and max-w- instead of h- and w-. Fixed dimensions clip content. Constraints let it grow.

Trust browser defaults

Block elements default to width: auto, which accounts for margins and padding. Don't set w-full or w-screen unless you have a concrete reason.

Fit content

w-fit sizes an element to its content. It's useful for buttons, badges, and inline elements that shouldn't stretch in a grid.

<Button emphasis='strong' className='w-fit'>
I'm only as wide as my content
</Button>

Fractions and minmax

Use fr units in grid templates to distribute available space. Use minmax(0, 1fr) to stop content from forcing columns wider than intended.

Container queries

Components should respond to their parent, not the viewport. A card in a narrow sidebar needs different styling than the same card in a wide main column, though the viewport width is the same in both cases. Use @container for component-level adaptation.

Tailwind's @container class sets container-type: inline-size. Then use @sm:, @md:, @lg:, and so on as container query variants instead of sm: and md:. They switch at the container widths, so @sm: applies once the container is container-sm wide.

The same component in different containers

Both cards use the same @sm:grid-cols-[auto_1fr] breakpoint. The narrow one, capped at max-w-xs, stacks because its container is too small. The wider one goes side by side once it has room.

<Card className='@container max-w-xs'>
<Card.Content className='grid gap-3 @sm:grid-cols-[auto_1fr]'>
<IconTile size='3xl'>
<TicketIcon weight='bold' />
</IconTile>
<div className='grid gap-1'>

Use media queries for page-level layout and container queries for component-level adaptation. Use both.

Layout patterns

Vertical stack

grid gap-4 is the default vertical layout.

<Card>
<Card.Content className='grid gap-4'>
<Field>
<Field.Label>Name</Field.Label>
<Field.Input />
</Field>

Centred content

grid place-content-center centres both axes with one class.

<div className='grid min-h-40 place-content-center'>
<p className='text-sm text-subtle'>Centred in both axes</p>
</div>

Horizontal row

flex items-center gap-4 gives content-driven sizing.

<ul className='flex items-center gap-4'>
<li><Badge>Tag</Badge></li>
<li><Badge>Another tag</Badge></li>
<li><Badge>Longer tag here</Badge></li>
</ul>

Equal columns

grid grid-cols-3 gap-4 lets the parent control column widths.

<div className='grid grid-cols-3 gap-4'>
<Card><Card.Content>Column 1</Card.Content></Card>
<Card><Card.Content>Column 2 with more content</Card.Content></Card>
<Card><Card.Content>Column 3</Card.Content></Card>
</div>

Responsive grid

grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-6 adds columns as the viewport grows.

<div className='grid grid-cols-1 gap-6 sm:grid-cols-2 lg:grid-cols-3'>
{[1, 2, 3, 4, 5, 6].map((n) => (
<Card key={n}>
<Card.Content>Item {n}</Card.Content>
</Card>
))}

Asymmetric layout

grid grid-cols-[1fr_2fr] gap-6 gives the second column twice the space.

<div className='grid grid-cols-[1fr_2fr] gap-6'>
<Card><Card.Content>Sidebar (1fr)</Card.Content></Card>
<Card><Card.Content>Main content (2fr)</Card.Content></Card>
</div>

Wrapping row

flex flex-wrap gap-2 lets items wrap naturally based on content width. This is where flexbox shines: children control their own size.

<ul className='flex flex-wrap gap-2'>
{['Design', 'Tokens', 'Tailwind', 'CSS', 'Grid', 'Flexbox', 'Layout', 'Spacing', 'Typography', 'Colour', 'Elevation'].map((tag) => (
<li key={tag}><Badge>{tag}</Badge></li>
))}
</ul>

Guidelines

  • Use gap-4, not mb-4 on children. Gap never creates edge spacing.
  • Use grid gap-4, not flex flex-col gap-4. Grid is simpler for vertical stacks.
  • Use grid place-content-center, not flex items-center justify-center. One class centres both axes.
  • Use grid grid-cols-3, not flex with calc(33%). Grid handles equal columns natively.
  • Use min-h-[200px], not h-[200px]. Content can grow beyond a fixed height.
  • Use max-w-4xl, not w-full. It caps the width and stays responsive.
  • Use minmax(0, 1fr), not fixed 300px columns. Fixed units cause overflow on small screens.

Prefer container queries

Respond to the parent with @container and the @sm: variants, not to the viewport with sm: and md:.

{/* Responds to parent width */}
<div className="@container">
<div className="grid @sm:grid-cols-[auto_1fr]">
<img />
<div>Content</div>
</div>

Do

Respond to parent width so the component adapts regardless of where it is placed.

{/* Responds to viewport, breaks in narrow columns */}
<div className="grid md:grid-cols-[auto_1fr]">
<img />
<div>Content</div>
</div>

Don’t

Respond to the viewport. It breaks when the same component is placed in a narrow column.

Previous page← Date and timeNext pageProse styles →