Skip to content

Foundations

Grid and layout

Layout is designed at each breakpoint rather than scaled down from the desktop. What changes between sizes is which parts of the interface are present, not just how wide they are.

BreakpointsPermalink to this section

Six steps, and each one exists because something specific has to change there — not because it is a round number.

TokenMin widthDeviceWhat changes
Base0–639pxPhoneSingle column. Sidebar in a drawer, table of contents hidden.
sm640pxLarge phoneDialogs become centred panels. Paired short fields sit side by side.
md768pxTabletTwo-column card grids. The Get started button appears in the navbar.
lg1024pxSmall laptopSidebar becomes persistent. Section links appear in the navbar.
xl1280pxDesktopTable of contents appears. The full three-column shell.
2xl1536pxWideContent caps at 1440px and centres. Nothing else grows.

The documentation shellPermalink to this section

Three columns at full width. Each drops out at the point where keeping it would squeeze the content column below a readable measure.

1440px maximum · sidebar 272px · content flexible · contents 224px

Sidebar

272px · lg and up

Content

minmax(0, 1fr), capped at 820px

src/app/docs/layout.tsx
<div className="mx-auto w-full max-w-[var(--content-max)] px-4 sm:px-6">
  <div className="lg:grid lg:grid-cols-[var(--sidebar-width)_minmax(0,1fr)] xl:grid-cols-[var(--sidebar-width)_minmax(0,1fr)_var(--toc-width)]">
    {/* minmax(0, 1fr) rather than 1fr — without it, a wide code
        block would push the content column past the viewport. */}
    <aside className="hidden lg:block"></aside>
    <main id="main-content" tabIndex={-1}></main>
    <aside className="hidden xl:block"></aside>
  </div>
</div>

ContainersPermalink to this section

Four widths cover every page in the product. Content does not grow indefinitely on a wide monitor.

ContainerMax widthUse for
Shell1440pxThe outer frame — navbar, docs shell, footer
Content820pxDocumentation body, so the measure stays readable
Prose68chRunning paragraphs inside that content
Form512pxSingle-column forms and settings

Designing for each sizePermalink to this section

The difference between a responsive layout and a shrunk one is whether the smaller version was designed or merely allowed to happen.

Content

Sidebar → drawer below lg

Dochange what is present

The sidebar becomes a drawer rather than a narrow strip. Its purpose survives; its form changes.

Nav

Content

TOC

Don'tsqueeze the desktop layout

Three columns at 375px leaves nothing usable. Drop what does not fit instead of compressing everything.

RulesPermalink to this section

The layout decisions that come up in almost every review.

  • Use minmax(0, 1fr) rather than 1fr for any grid column that can contain a code block or a table — otherwise the content forces the whole page to scroll horizontally.
  • Wide content scrolls inside its own container. The page body never scrolls sideways.
  • Sticky regions are offset by --header-height, so nothing hides behind the navbar.
  • Independently scrollable regions get their own max-height and the .scroll-region class.
  • Forms stay a single column at every width — a two-column form makes the reading order ambiguous.
  • Grids go 1 → 2 → 3 columns. Four columns of cards is almost always too many to compare.
  • Horizontal padding steps 16px → 24px at sm, and stops there.

The most common layout bug

A grid column defined as 1fr has a minimum size of auto, which means it refuses to shrink below its content. One long code sample then widens the entire page. Use minmax(0, 1fr).