# Layout Tokens

## Overview

The theme system supports two density themes:

- `comfortable`: the default density theme
- `compact`: a denser theme for space-constrained or data-heavy interfaces

These themes are defined in [layout.css](/Users/stuft2/Projects/design-tokens/src/css/layout.css) and are applied with the `data-layout-theme` attribute.

## Usage

Apply a layout theme with a container attribute:

```html
<html data-layout-theme="comfortable">
```

```html
<section data-layout-theme="compact">
```

Use `comfortable` by default.

Use `compact` when:

- the interface is data-dense
- vertical space is limited
- users need to scan many rows or controls quickly

Do not use `compact` to compensate for weak layout decisions or overcrowded content. Simplify the UI first.

Use the shared layout tokens directly where possible:

```css
input,
select,
textarea,
button {
  min-height: var(--layout-control-min-height);
  padding-block: var(--layout-control-padding-block);
  padding-inline: var(--layout-control-padding-inline);
  border-radius: var(--layout-control-radius);
}
```

If a component needs local aliases, map them back to the shared layout tokens instead of redefining comfortable and compact values:

```css
:root {
  --input-padding-block: var(--layout-control-padding-block);
  --input-padding-inline: var(--layout-control-padding-inline);
  --input-min-height: var(--layout-control-min-height);
}
```

For exact shared layout values and computed token definitions, use [layout.css](/Users/stuft2/Projects/design-tokens/src/css/layout.css) as the source of truth.

## Native Layout Defaults

The shared layout stylesheet applies the default page-shell contract for full pages that use a top-level `header`, `main`, and `footer`.

- `body` resets default margin, uses `--layout-space-inline` for page-inline padding, stops shrinking at `--layout-page-min-inline-size`, and becomes a full-height flex column
- `body > main` grows to fill remaining vertical space, fills the available inline space regardless of its content width, centers the primary content column, and caps it at `--layout-max-inline-size`
- Plain `section` elements get block-end spacing from `--layout-section-margin-block-end`
- `section[id]` uses `--layout-anchor-scroll-offset` for anchor-link scroll positioning
- Top-level `main` landmarks get vertical padding from `--layout-landmark-padding-block`
- Top-level `footer` landmarks use half of `--layout-landmark-padding-block`; the branded page header component keeps its own internal padding while staying flush and full bleed

Use `<section>` only when its contents form a logical region of prose or content that the page also separates visually from adjacent regions. Do not use `<section>` as a generic wrapper for layout, spacing, or a single heading-and-label treatment; use a neutral `<div>` with an appropriate layout utility or application class instead.

When related introductory content—such as an explanation, primary search control, and supporting description—forms one logical region, wrap the complete region in `<section class="flow">`. Prefer the section's native separation and the shared `flow` rhythm over application CSS that recreates spacious block padding and gaps.

This means the page footer naturally stays at the bottom of the viewport on short pages without requiring an extra shell class. Override these defaults locally only when a page or component has a stronger layout requirement.

## Utility Classes

The shared layout stylesheet also provides a small utility layer for composition.

### `flow`

Margin-based vertical rhythm for document content that should keep normal block layout.

- Defaults `--flow-space` to `var(--layout-space-stack)`
- Applies block-start margin only between adjacent children
- Use when prose, headings, lists, or mixed semantic content should keep normal document flow

### `cluster`

Horizontal flex layout for peer items that should wrap and stay vertically aligned.

- Uses `var(--layout-space-inline)` for gap
- Keeps wrapped rows packed from the start with `align-content: flex-start`
- Resets `height` to `auto`
- Resets direct child margins to zero
- Removes `list-style` when applied directly to `ul`

### `justify-end`

Inline-end alignment utility for flex rows.

- Sets `justify-content: flex-end`
- Use with `cluster` or other flex containers when actions should align to the right/end

### `justify-between`

Distributed alignment utility for flex rows.

- Sets `justify-content: space-between`
- Use with `cluster` or other flex containers when leading content and trailing actions should sit on opposite sides

### `stack`

Deprecated vertical flex layout retained for compatibility. Use `flow` for new vertical content and migrate existing `stack` layouts when they do not require a flex formatting context.

- Uses `var(--layout-space-stack)` for gap
- Removes `list-style` when applied directly to `ul`
- Do not use in new markup

### `sidebar`

Responsive two-part layout for a narrow supporting region paired with flexible main content.

- Defaults `--sidebar-target-width` to `20rem`
- Defaults `--sidebar-gap` to twice `var(--layout-space-inline)`
- Lets the first child act as the sidebar and the last child absorb remaining space
- Aligns child regions to the start so shorter sidebars keep their natural height
- Stacks child regions in source order below `48rem` and resets the first child's target-width basis so it keeps its natural height
- Uses wrapped sidebar/content columns at `48rem` and above

When the sidebar contains in-page navigation, put each link target ID on the content `section`, not on its heading. The shared `section[id]` rule applies `--layout-anchor-scroll-offset`, keeping the section heading visible below sticky primary navigation when the link is followed.

```html
<main class="sidebar">
  <nav class="side-nav" aria-label="On this page">
    <h2>On this page</h2>
    <ul>
      <li><a href="#color-preview">Color Preview</a></li>
    </ul>
  </nav>

  <div>
    <section id="color-preview" aria-labelledby="color-preview-title">
      <h2 id="color-preview-title">Color Preview</h2>
      <!-- Section content -->
    </section>
  </div>
</main>
```

### `center`

Centered content column using the shared layout width.

- Defaults `--center-max-inline-size` to `var(--layout-max-inline-size)`
- Defaults `--center-padding-inline` to `var(--layout-space-inline)`
- Uses `box-sizing: content-box` so padding does not reduce the configured maximum inline size
- When combined with `.sidebar` on the same element, fills the available inline size and uses `border-box` so its protected padding stays within the page boundary

### `grid`

Responsive grid for repeated sibling content.

- Defaults `--grid-column-gap` to `var(--layout-space-inline)`
- Defaults `--grid-row-gap` to `var(--layout-space-stack)`
- Defaults `--grid-min-column-size` to `16rem`
- Resets direct child `min-width` to `0` so wide content does not force overflow
- Uses `auto-fit` columns immediately, with each column at least `--grid-min-column-size` when space allows

### `switcher`

Adaptive flex layout for peers that should share a row when space allows and stack when space is constrained.

- Defaults `--switcher-target-width` to `20rem`
- Lets each direct child grow equally from the target width
- Resets direct child `min-inline-size` to `0` so wide content can shrink within the layout

### `cover`

Full-height vertical layout that centers the second child between optional leading and trailing content.

- Defaults `--cover-min-block-size` to `100dvh`
- Uses flex column layout
- Applies auto block margins to the second direct child

### `reel`

Horizontal scrolling row for media, cards, nav, or other items that should not wrap.

- Uses `var(--layout-space-inline)` for gap
- Uses the shared stack and inline spacing as internal scrollport padding so child shadows remain visible instead of being clipped at the Reel edges
- Allows inline overflow and hides vertical overflow
- Prevents direct children from shrinking so each item keeps its natural or assigned width

### `overflow-auto`

Overflow utility for elements that should show scrollbars only when content exceeds their bounds.

- Sets `overflow: auto`
- Use with an explicit size or max size so the browser has a boundary for overflow
- Allows scrolling in either axis when needed
- When the contents have no focusable controls, add `tabindex="0"`, `role="region"`, and an accessible name so keyboard users can enter and scroll the region

### `frame`

Aspect-ratio wrapper for media.

- Defaults `--frame-aspect-ratio` to `16 / 9`
- Clips overflowing media
- Sizes direct `img`, `video`, and `iframe` elements to fill the frame with `object-fit: cover`

### `masonry`

Multi-column layout for repeated content blocks when a staggered vertical flow is acceptable.

- Defaults `--masonry-min-column-size` to `16rem`
- Defaults `--masonry-column-gap` to `var(--layout-space-inline)`
- Defaults `--masonry-row-gap` to `var(--layout-space-stack)`
- Uses CSS columns for layout and applies `break-inside: avoid` to direct children
- Gives direct children `inline-size: 100%` and `margin-block-end: var(--masonry-row-gap)` so cards stack cleanly within each column

### `fluid`

Reset utility for making a container full width while keeping any existing padding.

- Removes default outer margin and any width cap on the element
- Use when an element should break out of a default content column without removing its local padding

### `flush`

Reset utility for removing default outer margin and inner padding.

- Removes default outer margin, inner padding, and any width cap on the element
- Use when an element should break out of a default content column and also clear built-in padding, such as `ul`

Use `fluid` when you want a container to span the available inline space while preserving padding. Use `flush` when you also need to remove built-in padding.

### `no-gap`

Reset utility for removing layout gaps.

- Sets `gap: 0`
- Sets `--flow-space: 0px` when combined with `.flow`

### `list-unstyled`

List reset utility for preserving semantic list structure without visible markers or marker indentation.

- Applies to `ul` and `ol`
- Removes `list-style` and inline-start padding without changing margins or item spacing
- Compose with `flow` when list items need normal vertical rhythm
- Add `role="list"` so assistive technologies continue to announce the list when markers are visually removed

## Native Primitives

The shared layout stylesheet also normalizes a small set of semantic HTML elements when they act like layout primitives.

### `main > header`

Direct child headers of the main landmark introduce the primary content area.

- Centers text so intro headings and supporting copy read as a page-level title block
- Leaves nested component and section headers to their local layout or component rules

### `<hr>`

Global subtle full-width divider that reads like a system border instead of the browser-default rule treatment.

- Uses `var(--layout-control-border-width)` and `var(--color-border-subtle)` for the line
- Resets native border chrome and outer margin
- Use when authors need an explicit divider between adjacent blocks, such as accordion items
- Keep spacing outside the `hr` itself so surrounding flow, utilities, or component structure control rhythm

## Authoring Rules

- Use shared layout tokens for density and rhythm before creating a new token.
- Keep component-specific aliases mapped back to shared tokens unless the component truly needs its own semantic token.
- Treat `1rem` as the base unit and size layout measurements against a `0.125rem` rhythm. That `0.125rem` rhythm comes from the underlying `2px` system, but token values should always be authored in `rem`, not `px`.
- The combined occupied space should land on that `0.125rem` rhythm even when the individual parts do not. Padding, border space, line height, and minimum height should work together to produce the intended step value.

## Behavior

Covered by [Playwright behavior tests](../../tests/e2e/layout.spec.js).

- LAY-001: At `48rem` and wider, `.sidebar` lays out its children in a row, gives the first child `--sidebar-target-width` as its flex basis, and lets the last child absorb the remaining space.
- LAY-002: Below `48rem`, `.sidebar` stacks its children in source order and makes each child fill the available inline size.
- LAY-003: Below `48rem`, the first child resets its desktop target-width flex basis to `auto` so its block size follows its content instead of `--sidebar-target-width`.
- LAY-004: `.flow` preserves normal block layout and adds `--flow-space` only between adjacent children.
- LAY-005: `.cluster` creates a wrapping flex row, aligns peer items, removes direct child margins, and removes list markers when used on a list.
- LAY-006: `.justify-end` aligns flex items to the inline end.
- LAY-007: `.justify-between` distributes available inline space between flex items.
- LAY-008: Deprecated `.stack` remains a vertical flex layout with the shared stack gap and removes list markers when used on a list so existing consumers do not break.
- LAY-009: `.center` centers a content-box column, defaults its maximum inline size to `--layout-max-inline-size`, and preserves inline padding outside that width.
- LAY-010: `.grid` creates responsive equal-width columns from `--grid-min-column-size`, uses independent row and column gaps, and allows direct children to shrink without overflow.
- LAY-011: `.switcher` wraps equal-growth flex children from `--switcher-target-width` and allows their contents to shrink within the layout.
- LAY-012: `.cover` creates a full-height flex column and vertically centers its second direct child between optional leading and trailing content.
- LAY-013: `.reel` creates a padded, non-wrapping horizontal scroller whose direct children retain their natural or assigned inline size and whose padding keeps child shadows visible within the scrollport.
- LAY-014: `.overflow-auto` exposes browser-managed overflow scrolling on either axis when the element has a constrained size.
- LAY-015: `.frame` clips content to `--frame-aspect-ratio` and makes direct image, video, or iframe media fill the frame with `object-fit: cover`.
- LAY-016: `.masonry` uses CSS columns sized from `--masonry-min-column-size` and keeps each direct child intact while stacking it with the configured row gap.
- LAY-017: `.fluid` removes outer margin and maximum inline-size constraints while preserving the element's padding.
- LAY-018: `.flush` removes outer margin, inner padding, and maximum inline-size constraints.
- LAY-019: `.no-gap` removes the gap from compatible grid and flex compositions and removes adjacent-child flow spacing when combined with `.flow`.
- LAY-020: An element composed with both `.sidebar` and `.center` fills its available inline size and includes the Center padding within that size so intrinsically wide sidebar content does not overflow the page.
- LAY-021: `--layout-surface-border-width` should match `--layout-control-border-width` in each layout theme while remaining a separate semantic token for bounded surfaces.
- LAY-022: A top-level `main` fills the available inline space up to the shared `--layout-max-inline-size` cap even when its contents have a narrow intrinsic width.
- LAY-023: `.list-unstyled` removes markers and inline-start indentation from semantic ordered and unordered lists without controlling their margins or item spacing.

## Variables

| Variable | Type | Description |
| --- | --- | --- |
| `--grid-column-gap` | `<length>` | Column gap for the responsive grid utility. Defaults to `--layout-space-inline`. Use for: horizontal spacing between `.grid` columns Avoid for: vertical grid row spacing |
| `--grid-min-column-size` | `<length>` | Minimum column size for the responsive grid utility. Defaults to `16rem`. Use for: controlling when `.grid` items wrap into additional columns Avoid for: masonry or sidebar column sizing |
| `--grid-row-gap` | `<length>` | Row gap for the responsive grid utility. Defaults to `--layout-space-stack`. Use for: vertical spacing between `.grid` rows Avoid for: horizontal grid column spacing |
| `--layout-action-padding-block` | `<length>` | Vertical padding for action-style controls. Buttons use block padding that resolves with the reserved border to `0.75rem` of vertical occupied space per side. Use for: buttons, file chooser buttons, and similar action affordances that share the system action sizing Avoid for: text inputs, selects, or content spacing between separate components |
| `--layout-action-padding-inline` | `<length>` | Horizontal padding for action-style controls. This token is intentionally wider than `--layout-control-padding-inline` so buttons read as actions rather than text-entry fields. Comfortable action inline padding resolves to `2rem` after subtracting the reserved control border, which is `32px` when `1rem` is `16px`. Use for: `button`, `::file-selector-button`, and similar action affordances that should feel prominent and easy to target Avoid for: standard field padding or inter-component spacing |
| `--layout-anchor-scroll-offset` | `<length>` | Scroll offset for anchored elements such as `section[id]` so in-page navigation does not land underneath sticky UI. Use for: `scroll-margin-top` on anchor targets when a sticky header or nav is present Avoid for: physical layout spacing or visual padding inside sections |
| `--layout-control-border-width` | `<length-percentage>` | Default border width for standard controls. Use for: shared control borders when the system should apply one consistent border weight Avoid for: hairline dividers or expressive one-off borders |
| `--layout-control-gap` | `<length-percentage>` | Internal gap between items inside a composed control or small control group. Use for: icon and label spacing, inline control clusters, small grouped actions Avoid for: outer component margins |
| `--layout-control-icon-size` | `<length>` | Default icon size for icons that appear inside standard interactive controls. Control icons are slightly larger than the shared icon size so they hold their weight inside action and form controls. Use for: button icons, inline control affordances, and similar control-adjacent icons that should scale from the control type size Avoid for: standalone decorative icons or component-specific display icon treatments |
| `--layout-control-line-height-size` | `<length>` | Computed control line-height length used when layout sizing calculations need to combine body line-height with padding and borders. Derived from: `--font-size` and `--font-line-height-body` Use for: shared layout height and offset calculations Avoid for: direct text styling, which should use `--font-line-height-body` |
| `--layout-control-min-height` | `<length-percentage>` | Minimum height for standard form controls and similar interactive elements. Use for: buttons, inputs, selects, and textareas where a consistent control height is needed Avoid for: containers whose height is content-driven |
| `--layout-control-padding-block` | `<length>` | Vertical padding inside field-style controls. Control padding should be evaluated with the rest of the control sizing math, not in isolation. Text inputs use block padding that resolves with the reserved border to `1rem` of vertical occupied space per side. Use for: `input`, `select`, and `textarea` vertical padding Avoid for: spacing between separate components |
| `--layout-control-padding-inline` | `<length>` | Horizontal padding for field-style controls where content entry and scan efficiency matter. Use for: `input`, `select`, `textarea`, and similar controls that should feel content-driven and space-efficient Avoid for: buttons, button-like affordances, or inter-component spacing |
| `--layout-control-radius` | `<length-percentage>` | Corner radius for standard controls in each layout theme. Use for: default control corner treatment when the component follows the shared density system Avoid for: expressive component shapes that need their own semantic radius token |
| `--layout-focus-ring-size` | `<length-percentage>` | Default focus ring spread for interactive elements. Use for: focus-visible treatment on controls and actions Avoid for: static shadows or decorative halos |
| `--layout-landmark-padding-block` | `<length>` | Responsive vertical padding for top-level page landmarks when `<header>`, `<main>`, or `<footer>` are direct children of `<body>`. Use for: consistent top-level page breathing room, with `main` on the full value and page `header`/`footer` on half Avoid for: nested landmarks inside components or local section padding |
| `--layout-max-inline-size` | `<length-percentage>` | Maximum inline size for centered page and content containers. Use for: the default width cap shared by the top-level page shell and `.center` utility Avoid for: readable prose measure, which should be constrained independently |
| `--layout-page-min-inline-size` | `<length-percentage>` | Minimum inline size for the page shell. Use for: preventing full-page layouts from collapsing below the supported narrow viewport width Avoid for: component-level min-width decisions |
| `--layout-safe-area-top` | `<length>` | Top safe-area inset for devices with display cutouts such as iPhone notches. Use for: sticky top offsets or sticky container padding when content must clear the hardware inset Avoid for: ordinary spacing between content blocks on rectangular viewports |
| `--layout-section-margin-block-end` | `<length>` | Responsive block-end spacing applied to plain `<section>` elements so stacked document sections stay visually separated. Use for: default spacing between adjacent sections in long-form page layouts Avoid for: component-internal spacing or one-off card/list gaps |
| `--layout-shadow` | `Any` | Shared elevation shadow for raised layout surfaces. Use for: cards, popovers, and other raised regions that need the system elevation treatment Avoid for: dark mode, where the token resolves to none |
| `--layout-space-inline` | `<length-percentage>` | Horizontal spacing between peer elements arranged in a row. Use for: spacing between buttons, inline fields, and horizontal control groups Avoid for: text padding inside a control |
| `--layout-space-stack` | `<length-percentage>` | Vertical spacing between stacked elements such as labels, helper text, form rows, or grouped controls. Use for: vertical rhythm between related elements Avoid for: internal control padding |
| `--layout-sticky-offset` | `<length>` | Shared top offset for sticky elements inside page content. Use for: `top` on sticky content that should stay below persistent page chrome such as a sticky primary nav Avoid for: anchor scroll positioning or visual padding |
| `--layout-surface-border-width` | `<length>` | Default border width for bounded surfaces. Use for: cards, dialogs, code blocks, tooltips, and similar containers Avoid for: interactive control borders, hairline dividers, or expressive one-off borders |
| `--masonry-column-gap` | `<length>` | Column gap for the masonry utility. Defaults to `--layout-space-inline`. Use for: horizontal spacing between masonry columns Avoid for: vertical spacing between masonry items |
| `--masonry-min-column-size` | `<length>` | Minimum column size for the masonry utility. Defaults to `16rem`. Use for: controlling masonry column width Avoid for: responsive grid columns |
| `--masonry-row-gap` | `<length>` | Row gap for the masonry utility. Defaults to `--layout-space-stack`. Use for: block-end spacing between masonry items Avoid for: horizontal spacing between masonry columns |
| `--sidebar-gap` | `<length>` | Gap between sidebar and main content regions in the responsive sidebar utility. Defaults to twice `--layout-space-inline`. Use for: spacing the two `.sidebar` regions Avoid for: unrelated flex or grid gaps |
| `--sidebar-target-width` | `<length>` | Target width for the sidebar region in the responsive sidebar utility. Defaults to `20rem`. Use for: tuning the supporting column width in `.sidebar` layouts Avoid for: general page width constraints |
| `--switcher-target-width` | `<length>` | Target width for adaptive peers in the switcher utility. Defaults to `20rem`. Use for: controlling when `.switcher` children share a row or stack Avoid for: fixed child widths |
