---
title: "Page layout"
description: "A page is furniture before it is components, and the furniture is classes, because a server can write all of it."
canonical: layout.html
navigation-title: "Page layout"
---

<a id="page-layout"></a>

# Page layout

- [The canvas and the frame](#the-canvas-and-the-frame)
- [The two bodies a page can have](#the-two-bodies-a-page-can-have)
- [A document with its panel](#a-document-with-its-panel)
- [The measure, and its numbers](#the-measure-and-its-numbers)
- [Where it sheds](#where-it-sheds)
- [Layout a page can use](#layout-a-page-can-use)
- [Which way the page runs](#which-way-the-page-runs)
- [The mark in the bar](#the-mark-in-the-bar)

A page is furniture before it is components, and the furniture is classes,
because a server can write all of it. Nothing here declares a width, and
nothing here is a component. The parts of a page stand here once, so no
surface writes its own breakpoints.

```html
<body class="sds-app">
  <a class="sds-skip sds-btn sds-btn--secondary" href="#main-content">Skip to content</a>
  <div class="sds-shell">
    <header class="sds-bar">…</header>
    <div class="sds-body">
      <aside class="sds-body__rail">…</aside>
      <main class="sds-body__main" id="main-content">…</main>
    </div>
    <footer class="sds-footer">…</footer>
  </div>
</body>
```

<a id="the-canvas-and-the-frame"></a>

## The canvas and the frame

<a id="confval-sds-app"></a>

**`.sds-app`**

- Type: class
- Required

The canvas: the ground colour, the text colour and the sans stack. It
belongs on `<body>` or the application root. Without it every surface
inside draws on whatever the browser decided.

The margin reset lives here, not in a reset file. On `<body>` the
browser's own gutter makes a full-height page overflow its viewport.

<a id="confval-sds-shell"></a>

**`.sds-shell`**

- Type: class

The column the whole page is: full height, bar at the top, footer at the
bottom, and the rest between them.

**A place jumped to arrives a step below the top of the window**, never on
its edge. The scroller keeps `scroll-padding-top` for every target, so no
target carries a margin of its own. Where nothing stands over the page it is
the section step, which is where a page's content starts. The bar and the
paper's head each set the offset to their own height instead.
`sds-nav-toc` reads it for the line it marks against.

<a id="confval-sds-skip"></a>

**`.sds-skip`**

- Type: class

The first tab stop, and the way past the bar, the rail and the breadcrumbs
into the text. It is a link. Give it `.sds-btn` for the shape, and point
it at the `id` of the page's `<main>`.

It sits off the top of the page until it has focus. `display: none` and
`visibility: hidden` both take a link out of the tab order, out of reach
of the one reader it exists for.

<a id="confval-sds-bar"></a>

**`.sds-bar`**

- Type: class

The header. Sticky, not fixed, so nothing below it needs to know its
height. It is the one translucent surface in a system with no shadows.
Solid, it reads as a lid; transparent, it lets letters run through the
mark.

**The bar sheds, it never wraps.** A header on two lines moves the offset
everything below measures against. So as the window narrows the bar drops
what it can spare. First the version badge, then the search field, then
the brand half of the wordmark. The mark, the product's own name and the
navigation stay.

<a id="confval-sds-bar-end"></a>

**`.sds-bar__end`**

- Type: class

The cluster against the right edge: the mode switch, the search, whatever
a surface puts there. It does not shrink; it sheds. A box narrower than
its contents is a header that overflows while every item looks right.

<a id="confval-sds-footer"></a>

**`.sds-footer`**

- Type: class

The end of a **site**: link columns, and the line that says what the
product is. It is a different ground, not a ruled-off area of the same
one. A hairline between two areas of one ground is a rule across the
window that says nothing.

<a id="the-two-bodies-a-page-can-have"></a>

## The two bodies a page can have

Every page is one of two layouts, and the choice is not decoration. It is
the distinction [Screens](../design-system/screens.md) draws between a page that
reports and a page that argues. A document that goes out on its own has a
third, below: a panel beside it.

**A column, with or without a rail**

```html
<div class="sds-body">
  <aside class="sds-body__rail"><sds-nav-rail …></sds-nav-rail></aside>
  <main class="sds-body__main">…</main>
</div>
```

Right for an answer, a reference, a document. With nothing to list
beside the text, `.sds-page` is the same measure with no rail.

**Bands, whose ground changes**

```html
<div class="sds-bands">
  <section class="sds-band">…</section>
  <section class="sds-band sds-band--quiet">…</section>
</div>
```

Right where the parts of the page are steps in an argument: the pitch,
then who it is for, then what it costs. Wrong everywhere else. A change
of ground that means nothing loses the reader's trust.

<a id="confval-sds-body"></a>

**`.sds-body`**

- Type: class

A rail beside a column. It becomes a single stack once there is no room
for two.

<a id="confval-sds-body-rail"></a>

**`.sds-body__rail`**

- Type: class

Where the navigation stands. Sticky, for the bar's reason: the list of
pages is the frame, not the text. It comes to rest where it started, not
against the bar, so nothing jumps as it catches.

It keeps the wheel while it has rows to scroll to, as the outline and the
contents list do. `sds-nav-rail` measures the box it stands in and
writes `is-scrollable` on it; [Navigation](components/navigation.md) has the
reason.

Below the width where a column beside the text fits, it does not draw at
all. The bar holds the whole site on every page, and its drawer holds
these pages among the rest.

<a id="confval-sds-body-main"></a>

**`.sds-body__main`**

- Type: class

The other half of the body: this page, as the rail is its list. It has no
width of its own. The measure comes from its content, which lets a table
run wide while the prose beside it stays at sixty-six characters.

<a id="confval-sds-column"></a>

**`.sds-column`**

- Type: class

**A half of a split, and nothing else.** The split states the width; the
column stands in it. A page's own reading column is `.sds-body__main` or
`.sds-page`. This one there says half of something with no other half.

<a id="confval-sds-page"></a>

**`.sds-page`**

- Type: class

One measure on one canvas, with nothing beside it. It replaces
`.sds-body`, never wraps it.

<a id="confval-sds-bands"></a>

**`.sds-bands`**

- Type: class

A page whose ground changes. It replaces `.sds-page`. Two insets indent
the text twice.

<a id="confval-sds-band"></a>

**`.sds-band`**

- Type: class

One full-bleed section. `.sds-band--quiet` is the second ground, with
the hairlines that keep two of them apart. Two quiet bands in a row share
one line.

<a id="a-document-with-its-panel"></a>

## A document with its panel

A third shape, for a document that goes out on its own: no bar, no footer,
and more places than a window is tall. A concept paper, a specification, a
report with twenty parts. The document stands beside a panel that is its
whole frame.

```html
<div class="sds-shell">
  <div class="sds-paper">
    <aside class="sds-paper__panel">
      <div class="sds-paper__head">
        <sds-eyebrow label="Concept paper · draft 2"></sds-eyebrow>
        <p class="sds-paper__title">A record of reads for every source</p>
      </div>
      <sds-nav-outline label="Contents" numbered .entries="${SECTIONS}"></sds-nav-outline>
      <div class="sds-paper__foot"><p>Draft for discussion · 2026-09-15</p></div>
    </aside>
    <main class="sds-paper__main" id="main-content">
      <article class="sds-prose">…</article>
    </main>
  </div>
</div>
```

<a id="confval-sds-paper"></a>

**`.sds-paper`**

- Type: class

A panel beside a document. It replaces `.sds-body`, and it carries no
bar: the panel's head is the document's own.

<a id="confval-sds-paper-panel"></a>

**`.sds-paper__panel`**

- Type: class

The frame. Sticky at the top and the height of the window, a hairline at
its inner edge, `--width-panel` wide. A column of three: the head and
the foot keep their height, and the outline between them scrolls on its
own. So a reader in the last part still sees the first. The mark on the
part they are in stays where they can see it.

The three carry the inset, not the panel, and the outline touches both
rules. So the rules run edge to edge, and the scrollbar stands on the
hairline from rule to rule.

Under 860px it is the page's head instead: one row, sticky at the top,
with the title and the press that drops the outline. The foot shows with
the list. The scroller keeps the head's height as its offset, so a place
jumped to arrives under the head and not behind it.

<a id="confval-sds-paper-head"></a>

**`.sds-paper__head`**

- Type: class

What the reader has open: an `sds-eyebrow` for the kind of document and
its draft, and the title in `.sds-paper__title`, at body size. The page
says the title again at full size; the panel says it where it stays in
view. In the page's head, under 860px, the title stands alone on one
line, and the eyebrow goes.

<a id="confval-sds-paper-foot"></a>

**`.sds-paper__foot`**

- Type: class

What the document says about its state, in the small size and the muted
ink: a draft, a date, a decision due. One or two lines of plain text. A
verdict takes the status colour on the word, `.sds-error` and its kin,
and never a label: a label names what stands under it.

<a id="confval-sds-paper-main"></a>

**`.sds-paper__main`**

- Type: class

The page. It has no width of its own, like `.sds-body__main`. Its
inset is the page's: the gutter, and past `--width-page` the rest. So
the document centres once the window is wider than the measure.

<a id="the-measure-and-its-numbers"></a>

## The measure, and its numbers

The bar, the body, the page, the bands and both footers are full-bleed. Their
*contents* keep the measure. A fill that stops short reads as a wide box,
not as a change of ground.

| Token | Value | What it decides |
| --- | --- | --- |
| `--width-page` | 1200px | the measure the page centres on |
| `--width-sidebar` | 210px | the rail |
| `--width-panel` | 300px | the panel beside a document. Wider than the rail, because a row in it carries a number and a sentence rather than a name |
| `--height-header` | 72px | the bar, and the rail's sticky offset. 56px once the page has narrowed: the height a desktop can spare is height a phone reads with |
| `--gutter-page` | 48px | the inset, before the steps below narrow it |

> [!NOTE]
> The inset inherits, and nothing recomputes it. What hangs off the bar,
> the menu panel, the search drop, has to start where its contents do.

<a id="where-it-sheds"></a>

## Where it sheds

Each step is a thing the page can no longer afford, not a device. **These are
every width the design changes at.** A stylesheet that reaches for a sixth
is a band nobody named; `make verify ARGS=breakpoints` holds the two
together.

| At most | What changes |
| --- | --- |
| 1140px | the gutters narrow, and the vertical rhythm with them |
| 860px | the rail stops as a column, and the bar's own menu holds the site. The bar gives its height back to the page. The version badge leaves it. A document's panel stands over the page |
| 640px | a row of controls wraps. The marks at the end of the footer's closing line give up their end of it |
| 460px | the wordmark keeps the signet and the product, and drops the brand. The name that survives takes the weight of a mark |

And one the other way. At 1296px the page has room to give. The local
contents leaves the flow and stands beside the column, so the page reads
rail, text, contents with the same width either side. It is the only width
in this system that adds something; see [Documents](documents.md).

> [!IMPORTANT]
> What the bar does with the search field and the sections is **not** one
> of these. `sds-nav-main` measures what they need against the room the
> row has left. A bar holds a product name as long as the product has one.
> What it can no longer hold waits in the one drawer with the site's whole
> menu; see [Navigation](components/navigation.md).
>
> Neither is a split or a grid. Both reflow by the minimum of their own
> halves and items. The column they stand in decides, never the window, and
> beside a rail those are different numbers.

<a id="layout-a-page-can-use"></a>

## Layout a page can use

Named, not written inline on the page, for one reason: layout a page writes
for itself is layout nothing else can keep in step.

The vocabulary stays this small on purpose, and it stays composition. A
block carries its own step, and the pairs of a title group state theirs. So
a page composes loose and stands on the grid with no container that pays for
it. A stack regroups where one distance holds whatever a box comes to hold.

A set that *means* something graduates into a component that pays its own
steps: `sds-field-group` is a control and what stands with it. What never
returns is the wrapper that exists only for space. That is layout with no
name.

| Class | What it lays out |
| --- | --- |
| `.sds-sections` | the rhythm *between* the parts of a page |
| `.sds-stack` | the rhythm *inside* one of them |
| `.sds-stack--tight` | a title group as one thing: the trail, the eyebrow, the heading, the lead. Composed loose, the pairs already stand at this step. The class is for a box that holds more than the pairs |
| `.sds-actions` | a row of controls, centred. A link beside a button is a line of text in a box the button's height |
| `.sds-row` | a row of small things that can run onto a second line. Badges, a version, the two words under a heading |
| `.sds-row__end` | the one thing in such a row that belongs at its far end |
| `.sds-split` | two of anything, side by side until there is no room for two |
| `.sds-split--center`, `.sds-split--end` | where the shorter half stands against the taller one: level with it, or at its foot. With nothing said, it stands at the top |
| `.sds-split--leads-end` | the second half read first once the two have stacked. A picture beside the sentence on a page and above it on a phone |
| `.sds-grid` | the wall a set reads in, reflowed by its own minimum. `sds-grid` is the element that writes it, and a page uses that |
| `.sds-form` | one column of fields, at the measure of a form to *fill in*, not the one a page reads at |

> [!WARNING]
> None of these is a place for a colour, a border or a type size. A class
> here says how far apart things stand and nothing else. That is what lets
> the same page frame hold a marketing band and a tool reference.

<a id="which-way-the-page-runs"></a>

## Which way the page runs

**Write** `dir="rtl"` **on** `<html>` **and the layout mirrors itself.**
That is the whole of what a project does. No other configuration, no second
stylesheet, no class change.

It works because the sheets use logical properties throughout. A start edge
follows the document, not the screen. So a rail, a card's action line, the
bar's end cluster and a table's first column all change sides together. A
physical property makes a mirrored page fall apart one rule at a time. So
none is in use, even where a value looks symmetrical today.

A drawing cannot mirror itself, so the glyphs that mean *onward* turn under
`:dir(rtl)`. That is the arrow on a card's action, the pager, the
pagination steps, and the marker on a closed fold. Only those. A page that asked for
`actions-arrow-right` named an arrow, not a heading, and gets the one it
named.

> [!NOTE]
> **What this does not cover.** The type is not for Arabic or Hebrew.
> [Type](../design-system/type.md) ships a Latin pair, and a project with an RTL
> script supplies the face for it. Nothing in the system reorders a sentence
> or a date, because nothing in it writes one.

<a id="the-mark-in-the-bar"></a>

## The mark in the bar

```html
<a class="sds-lockup" href="/">
  <sds-image class="sds-signet" src="/soul/assets/signet.svg" alt=""
    width="24" height="24"></sds-image>
  <span class="sds-wordmark"><span class="sds-wordmark__brand">TYPO3</span><span
    class="sds-wordmark__pipe" aria-hidden="true"></span><span
    class="sds-wordmark__product">Soul</span></span>
</a>
```

The lockup states the mark's size itself. A signet is crisp only at the size
of its file, and a number left to each call site drifts. The pipe is the
third and last place `--accent` can appear. On a narrow bar the brand half
and the pipe go. The signet already says whose the page is, and the product's
own name says which page it is.

> [!NOTE]
> [Brand](../design-system/brand.md) for which drawing to hand over at which size,
> and [Screens](../design-system/screens.md) for the complete pages these parts come
> from.
