Skip to content

Want to skip the docs? Check out pandamastery.com - the best way to learn Panda CSS

Write styles

Layout Patterns

Stack, grid, center, container and friends. The layouts every app writes by hand, as typed functions and components.

Most layout code is the same handful of ideas: put these in a column with a gap, line those up in a row and center them, cap the page width and center it, lay cards out in a grid. Written by hand, each one is four or five flex or grid properties, and every screen writes them again.

<div className={css({ display: 'flex', flexDirection: 'column', gap: '6' })}>
  <div className={css({ display: 'flex', alignItems: 'center', justifyContent: 'space-between' })}>
    <h2>Orders</h2>
    <button>Export</button>
  </div>
  <div className={css({ display: 'grid', gridTemplateColumns: 'repeat(3, minmax(0, 1fr))', gap: '4' })}>
    {cards}
  </div>
</div>

Patterns name those ideas. Each one is a function that takes a few layout props and returns a class name, so the same screen reads as what it is:

<div className={stack({ gap: '6' })}>
  <div className={hstack({ justify: 'space-between' })}>
    <h2>Orders</h2>
    <button>Export</button>
  </div>
  <div className={grid({ columns: 3, gap: '4' })}>{cards}</div>
</div>

Nothing changes underneath. A pattern call is extracted at build time and produces the same atomic CSS as the css() call it replaces. There is no runtime, no wrapper element, and no new class of thing to learn: any style property still works alongside the pattern props.

What you get

  • Typed props. gap takes spacing tokens, align takes the align-items keywords, columns takes a number. Autocomplete shows what a pattern accepts.
  • Responsive values. Every prop accepts breakpoint objects, so direction={{ base: 'column', md: 'row' }} just works.
  • Two forms. A function under styled-system/patterns for class names, and with jsxFramework set, a component under styled-system/jsx.
  • Zero cost. Extracted like css(), emitting the same atomic classes.
  • Yours to shape. Change a default, narrow a prop, or write your own. See Patterns.
import { stack } from '../styled-system/patterns'
import { Stack } from '../styled-system/jsx'
 
<div className={stack({ gap: '4' })} />
<Stack gap="4" />

The rest of this page uses the function form. Every example has a JSX twin with the same props.

Pick a pattern

  • Stacking: stack for a column or row with a gap, hstack and vstack for the two common cases with items centered, spacer to push siblings apart.
  • Wrapping and grids: wrap for items that flow onto new lines, grid and gridItem for columns and spans.
  • Sizing and centering: container for a capped, centered page, center to center children, square and circle for fixed boxes, aspectRatio for media.
  • Positioning: float to pin a badge to a corner, bleed to break out of a parent's padding, linkOverlay to make a whole card clickable.
  • Utilities: box for style props with no opinion, visuallyHidden for screen-reader only content, divider for a rule, cq to declare a container query.

Stacking

Stack

A flex container with a gap. Defaults to a column.

  • direction: column by default, or row.
  • gap: spacing between children. Defaults to 8px.
  • align, justify: align-items and justify-content.
import { stack } from '../styled-system/patterns'
 
<form className={stack({ gap: '4' })}>
  <input />
  <input />
  <button>Save</button>
</form>

HStack

A row with children vertically centered. The everyday toolbar.

  • gap: defaults to 8px.
  • justify: justify-content.
import { hstack } from '../styled-system/patterns'
 
<header className={hstack({ justify: 'space-between' })}>
  <Logo />
  <nav className={hstack({ gap: '6' })}>{links}</nav>
</header>

VStack

A column with children horizontally centered.

  • gap: defaults to 8px.
  • justify: justify-content.
import { vstack } from '../styled-system/patterns'
 
<div className={vstack({ gap: '2' })}>
  <Avatar />
  <span>Segun</span>
</div>

Spacer

Fills the free space in a stack so what comes after it sits at the far end.

  • size: a fixed spacing token instead of filling. Without it the spacer grows.
import { hstack, spacer } from '../styled-system/patterns'
 
<div className={hstack()}>
  <Logo />
  <div className={spacer()} />
  <button>Sign in</button>
</div>

Wrapping and grids

Wrap

A row that flows onto new lines when it runs out of room. Tags, chips, filter pills.

  • gap, rowGap, columnGap: gap defaults to 8px unless one of the axis gaps is set.
  • align, justify: align-items and justify-content.
import { wrap } from '../styled-system/patterns'
 
<ul className={wrap({ gap: '2' })}>{tags}</ul>

Grid

Equal columns, or as many columns as fit.

  • columns: a fixed count.
  • minChildWidth: a size token or length. The grid fits as many columns of at least that width as it can. Use it instead of columns, not with it.
  • gap, rowGap, columnGap: gap defaults to 8px unless an axis gap is set.
import { grid } from '../styled-system/patterns'
 
<div className={grid({ columns: { base: 1, md: 3 }, gap: '6' })}>{cards}</div>
<div className={grid({ minChildWidth: '64', gap: '6' })}>{cards}</div>

Grid Item

Spans and placement for a child of grid.

  • colSpan, rowSpan: how many tracks to cover.
  • colStart, colEnd, rowStart, rowEnd: explicit lines.
import { grid, gridItem } from '../styled-system/patterns'
 
<div className={grid({ columns: 3, gap: '6' })}>
  <article className={gridItem({ colSpan: 2 })}>Feature</article>
  <aside>Sidebar</aside>
</div>

Sizing and centering

Container

Caps the width, centers the block, and adds responsive side padding. One per page.

It sets maxWidth: '8xl', marginX: 'auto', and paddingX: { base: '4', md: '6', lg: '8' }. Override any of them as a style prop.

import { container } from '../styled-system/patterns'
 
<main className={container({ maxWidth: '4xl' })}>{children}</main>

Center

Centers children on both axes.

  • inline: use inline-flex so the box sizes to its content.
import { center } from '../styled-system/patterns'
 
<button className={center({ w: '10', h: '10', rounded: 'full' })}>
  <Icon />
</button>

Square

A box with equal width and height, children centered.

  • size: a size token or length.
import { square } from '../styled-system/patterns'
 
<div className={square({ size: '12', bg: 'gray.100' })}>
  <Icon />
</div>

Circle

The same as square, rounded. Avatars, status dots, icon buttons.

  • size: a size token or length.
import { circle } from '../styled-system/patterns'
 
<div className={circle({ size: '10', bg: 'green.500' })} />

Aspect Ratio

Holds a box at a ratio and fits its child to it. Images, video, maps.

  • ratio: a number. Defaults to 4 / 3.

For a plain box, the aspectRatio style property is enough. Reach for the pattern when the child is an iframe or an image that must fill and crop.

import { aspectRatio } from '../styled-system/patterns'
 
<div className={aspectRatio({ ratio: 16 / 9 })}>
  <iframe src={mapUrl} title="Map" />
</div>

Positioning

Float

Pins an element to a corner or edge of its nearest positioned ancestor. Badges, close buttons, counters.

  • placement: top-start, top-center, top-end, middle-start, middle-center, middle-end, bottom-start, bottom-center, or bottom-end. Defaults to top-end.
  • offset, offsetX, offsetY: distance from the edge, as a spacing token.

The parent needs position: relative.

import { css } from '../styled-system/css'
import { float } from '../styled-system/patterns'
 
<button className={css({ position: 'relative' })}>
  <BellIcon />
  <span className={float({ placement: 'top-end', offset: '1' })}>3</span>
</button>

Bleed

Pulls an element out past its parent's padding so it runs edge to edge.

  • inline, block: how far to bleed on each axis. Match the parent's padding.
import { css } from '../styled-system/css'
import { bleed } from '../styled-system/patterns'
 
<article className={css({ px: '6' })}>
  <img className={bleed({ inline: '6' })} src={hero} alt="" />
  <p>Body copy stays inside the padding.</p>
</article>

Stretches a link's clickable area over its nearest positioned ancestor, so a whole card is clickable while the markup keeps one real link.

import { css } from '../styled-system/css'
import { linkOverlay } from '../styled-system/patterns'
 
<article className={css({ position: 'relative' })}>
  <img src={cover} alt="" />
  <h3>
    <a href="/post/1" className={linkOverlay()}>
      Read the post
    </a>
  </h3>
</article>

Keep one link per card. A second clickable element inside the card would sit under the overlay.

Utilities

Box

No styles of its own. As a function it is css(). As a component it is a div that takes style props, which is the reason it exists.

import { Box } from '../styled-system/jsx'
 
<Box p="4" bg="bg.subtle" rounded="md">
  {children}
</Box>

Visually Hidden

Removes an element from view but not from the accessibility tree. Labels for icon buttons, skip links, the input behind a custom checkbox.

import { visuallyHidden } from '../styled-system/patterns'
 
<button>
  <TrashIcon />
  <span className={visuallyHidden()}>Delete</span>
</button>

Divider

A horizontal or vertical rule that fills its axis.

  • orientation: horizontal by default, or vertical.
  • thickness: a size token or length. Defaults to 1px.
  • color: a color token.
import { divider, hstack } from '../styled-system/patterns'
 
<div className={hstack({ gap: '4', h: '6' })}>
  <a href="/docs">Docs</a>
  <div className={divider({ orientation: 'vertical', color: 'border' })} />
  <a href="/blog">Blog</a>
</div>

Container Query

Declares the element as a container, so children can respond to its width instead of the viewport's.

  • type: containerType. Defaults to inline-size.
  • name: a name from theme.containerNames, for @name/size conditions.
import { css } from '../styled-system/css'
import { cq } from '../styled-system/patterns'
 
<aside className={cq({ name: 'sidebar' })}>
  <p className={css({ fontSize: { base: 'sm', '@sidebar/md': 'md' } })}>{text}</p>
</aside>

The size conditions come from theme.containers. See container queries.

Responsive props

A pattern prop takes the same breakpoint object as any style property. Put the breakpoints on the prop itself:

<Grid columns={{ base: 1, md: 2, lg: 3 }} gap={{ base: '4', md: '6' }} />
<Stack direction={{ base: 'column', md: 'row' }} />

Nesting a pattern prop inside a breakpoint key skips the pattern, so nothing changes at that breakpoint:

// ❌ `columns` inside `md` is never transformed
<Grid columns={1} md={{ columns: 2 }} />
 
// ✅
<Grid columns={{ base: 1, md: 2 }} />

Style props on the same element still accept breakpoint keys the usual way.

Wrapping a pattern component

Patterns are found by name when Panda scans your files. Wrap Stack in your own Section component and the props passed to Section are invisible to extraction, because nothing tells Panda that Section is a stack.

The jsx option on the pattern's config adds names for Panda to treat as that pattern:

panda.config.ts

patterns: {
  extend: {
    stack: { jsx: ['Stack', 'Section'] }
  }
}

Now <Section gap="8" /> is extracted like <Stack gap="8" />. Regexes work too, for a family of wrappers. See Patterns for the full option.

Make your own

The built-ins cover the layouts every app has. The ones your app repeats that these don't cover, a scrollable region, a two-pane sidebar, a sticky header that clears the nav, are the case for writing a pattern.

Edit this page on GitHubView as markdown
Last updated on