Layout

Farn's layout philosophy is content-first: let the content determine the container size, not the other way around. Width tokens give you three semantic slots. Structural HTML gives you meaningful landmarks. Grid and flex each have a clear job.

Content-first widths

Three width tokens cover the full range of layout needs. Apply them to the element that owns the constraint — usually a wrapper <div> with margin-inline: auto.

--width-content1080px · outer container
--width-narrow640px · forms, dialogs
--width-prose70ch · running prose

70ch is relative to the element's own font size, so prose columns naturally widen slightly for display headings and narrow for captions — no extra math required. For the full spacing reference including padding scale and z-index, seeStyles › Spacing.

Width token reference
TokenValueUse
--width-content1080pxOuter page container — navigation, hero, section wrappers
--width-prose70chText column — articles, docs, any running prose
--width-narrow640pxTight containers — forms, dialogs, focused card layouts

Page structure

Use semantic landmarks. The wireframe below shows a typical page skeleton and the natural width token pairing for each landmark.

headermax-width: var(--width-content)
mainmax-width: var(--width-content)
articlemax-width: var(--width-prose)
asidevar(--width-narrow)
Page structure code
<body>
  <header>
    <!-- max-width: var(--width-content) -->
    <nav>...</nav>
  </header>

  <main>
    <!-- outer bound: var(--width-content) -->

    <article>
      <!-- prose column: var(--width-prose) -->
      <h1>...</h1>
      <p>...</p>
    </article>

    <aside>
      <!-- sidebar or supplemental: var(--width-narrow) -->
    </aside>
  </main>

  <footer>
    <!-- max-width: var(--width-content) -->
  </footer>
</body>

Grid vs flex

Flex for one-dimensional alignment, grid for two-dimensional layout. The demo below usesrepeat(auto-fill, minmax(180px, 1fr)) — columns appear as space allows, collapsing to a single column on narrow viewports without a media query.

Card 1

Grid auto-fill

Card 2

Grid auto-fill

Card 3

Grid auto-fill

Card 4

Grid auto-fill

Grid vs flex pattern reference
PatternUseExample
Flex rowAligning items along a single axisNav links, button group, icon + label
Flex columnStacking with controlled spacingForm fields, card body
Grid columnsPlacing items in explicit rows and columnsCard grid, two-column doc layout
Grid auto-fillResponsive columns without breakpointsrepeat(auto-fill, minmax(240px, 1fr))

Always use gap: var(--space-*) rather than margins between grid or flex children. This keeps spacing in the token system and avoids margin-collapse edge cases.

Layout composition primitives

farn-layout.css is an opt-in layer of ten composition classes built on top of the Farn token set. Load it after farn.css or farn-tokens.css. The classes use Every Layout naming — they describe intent, not CSS properties.

@import "farn-theme";        /* tokens + reset */
@import "farn-theme/layout"; /* opt-in: container, stack, cluster… */

Containers

Three width-constrained, centered wrappers. Each sets container-type: inline-sizeso @container queries on children fire without an extra wrapper. Padding reduces from --space-lg to --space-md at mobile widths.

.containermax-width: 1080px · outer sections
.container-narrowmax-width: 640px · forms, dialogs
.container-prosemax-width: 70ch · running text
Container reference
ClassMax-width tokenTypical use
.container--width-content (1080px)Page sections, nav wrapper, hero, footer
.container-narrow--width-narrow (640px)Forms, dialogs, focused card layouts
.container-prose--width-prose (70ch)Articles, documentation, running prose

All three set box-sizing: border-box, margin-inline: auto, and padding-inline: var(--space-lg) (reduced to var(--space-md) at <640px). The box-sizing guard ensures padding is absorbed into the max-width even when loading farn-tokens.css without farn.css's base reset.

Name collision: .container is a common class name. If your existing CSS already defines .container, loading farn-layout.css may conflict — audit your styles or use farn-tokens.css to adopt only the tokens without the classes.

<section>
  <div class="container">
    <!-- content constrained to 1080px -->
  </div>
</section>

Stack, Cluster, Center

The three fundamental composition primitives. Stack flows children vertically. Cluster wraps them horizontally. Centerplaces a single item at the middle of both axes. All accept inline custom-property overrides for gap.

.stack
Item A
Item B
Item C
.cluster
DesignReleasedBetaCSS
.center
Centered
Stack / Cluster / Center reference
ClassCSSOverride
.stackflex-direction: column; gap: var(--space-md)--stack-gap
.clusterflex-wrap: wrap; align-items: center; gap: var(--space-md)--cluster-gap
.centerdisplay: flex; align-items: center; justify-content: center
<!-- Tighter gap on this stack -->
<div class="stack" style="--stack-gap: var(--space-sm)">
  <div class="card">…</div>
  <div class="card">…</div>
</div>

<!-- Badge row -->
<div class="cluster">
  <span class="badge badge-general">Tag</span>
  <span class="badge badge-published">Live</span>
</div>

Auto grid

Columns auto-fit to available space using auto-fit minmax() — no breakpoints needed. Override the minimum column width with --grid-min (default: 280px). This is the general-purpose version; .card-grid in farn-components.cssuses the same pattern with a fixed 280px minimum.

col 1
col 2
col 3
col 4
col 5
Auto grid reference
OverrideDefaultEffect
--grid-min280pxMinimum column width before wrapping
--grid-gapvar(--space-md)Gap between columns and rows
<!-- Tighter columns, smaller gap -->
<div class="auto-grid" style="--grid-min: 200px; --grid-gap: var(--space-sm)">
  <div class="card">…</div>
  <div class="card">…</div>
  <div class="card">…</div>
</div>

vs .card-grid: .auto-grid is in farn-layout.cssand accepts --grid-min. .card-grid is in farn-components.csswith a fixed 280px minimum — use it when you always want the card column width, .auto-gridwhen you need a different minimum or want to override per-instance.

Explicit column grids. Sidebar pairs a fixed-width column with a flexible main area. Grid-2 and Grid-3 create equal columns. All three collapse responsively when inside a container context — wrap them in.container (or any element with container-type: inline-size) to activate @container collapsing.

.sidebar — collapses to 1 col at <600px container width
.grid-2 — collapses to 1 col at <500px
Column 1
Column 2
.grid-3 — 3→2 cols at <900px, 1 col at <500px
Column 1
Column 2
Column 3
Sidebar / Grid-2 / Grid-3 reference
ClassColumnsCollapses (in container context)Overrides
.sidebarvar(--sidebar-w, 300px) 1fr1 col at <600px--sidebar-w, --grid-gap
.grid-2repeat(2, 1fr)1 col at <500px--grid-gap
.grid-3repeat(3, 1fr)2 cols at <900px, 1 col at <500px--grid-gap

Container context required for collapsing. Grids collapse via@container queries, not @media queries. Wrap in.container or set container-type: inline-size on a parent.

<!-- Grids collapse when the .container is narrow -->
<section>
  <div class="container">
    <div class="grid-3">
      <div class="card">…</div>
      <div class="card">…</div>
      <div class="card">…</div>
    </div>
  </div>
</section>

<!-- Sidebar with narrow sidebar column -->
<div class="container">
  <div class="sidebar" style="--sidebar-w: 240px">
    <nav>…</nav>
    <main>…</main>
  </div>
</div>

Radius as hierarchy

Border radius is not purely decorative — it signals depth and interactivity. Use the scale consistently so elements at the same depth read as related.

--radius-sm4px · badges, code
--radius-md6px · buttons, inputs
--radius-lg8px · cards, panels
--radius-xl12px · hero cards
--radius-full9999px · pills

Nest radii correctly: an inner element should have a smaller radius than its container, roughly container-radius - padding. Matching radii at every level makes nested surfaces look misaligned. See Styles › Spacing for the full radius token reference.

Stacking order

Eight z-index tokens define a predictable stacking ladder. Never hardcode z-index values — use the tokens so layers stay relative to each other as the system grows.

--z-base · 0Normal flow
--z-raised · 1Floated cards
--z-content · 10Sticky sub-nav
--z-dropdown · 100Dropdowns, popovers
--z-sticky · 200Sticky header
--z-overlay · 300Modal backdrop
--z-modal · 400Modal dialog
--z-toast · 500Toasts, notifications

The stacking order matches the visual hierarchy: page content sits at the bottom, transient UI (toasts, modals) at the top. Never set a dropdown above a modal — if a component needs to break this order, the design decision is worth questioning first.