# PageLayout

> Page-body wrapper with a max-width container, default page margins, an optional grid mode, and a spacing scale.

- Status: stable
- Group: layout
- Page: https://www.digitaltableteur.com/design-system/components/page-layout
- Contract: https://www.digitaltableteur.com/contracts/v1/PageLayout.contract.json

## When to use

PageLayout is the page-body wrapper: a max-width container with default page margins, an optional grid mode with columns, and a spacing scale. Several structure blocks (GridBlock, ProofBlock, ProjectMetaSection) build on it. Use it to open a page body or section shell; inside components prefer the Grid/FlexBox primitives.

## When not to use

The contract records no forbidden uses.

## Look-alikes

The contract names no look-alike with a deciding condition.

## Props

| Prop | Type | Default | Description | Required |
| --- | --- | --- | --- | --- |
| `children` | `React.ReactNode` | none | Content to be displayed within the layout. | Yes |
| `maxWidth` | `"sm" \| "md" \| "lg" \| "full" \| "xl"` | `lg` | Maximum width constraint for the content. | No |
| `grid` | `boolean` | `false` | Whether to use the 12-column grid system. | No |
| `columns` | `number` | none | Fixed number of equal columns when grid=true, written as an inline grid-template-columns that replaces the responsive 4/8/12 column ladder grid=true gives on its own. Ignored when grid is false. | No |
| `spacing` | `"compact" \| "default" \| "comfortable" \| "spacious"` | `default` | Vertical spacing between sections. | No |
| `withMargins` | `boolean` | `true` | Apply consistent page margins. | No |
| `className` | `string` | `""` | Additional CSS class name. | No |
| `as` | `"article" \| "main" \| "div" \| "section"` | `div` | Semantic HTML element to use for the container. | No |
| `role` | `string` | none | ARIA role for accessibility. | No |
| `ariaLabel` | `string` | none | ARIA label for accessibility. | No |
| `style` | `React.CSSProperties` | none | Inline style merged over the grid columns style; caller values win. | No |

## Variants

- Allowed `maxWidth` values: `sm`, `md`, `lg`, `full`, `xl` (default `lg`).
- Available spacings: `compact`, `default`, `comfortable`, `spacious` (default `default`).
- Allowed `as` values: `article`, `main`, `div`, `section` (default `div`).

## Invalid combinations

- MUST NOT: When `columns` is set and `grid` is not set, then also set `grid`. `columns` only applies when `grid` is true; `grid` defaults to false, so the column count is dropped.
- MUST NOT: When `grid` is `false`, then do not set `columns`. `columns` only applies when `grid` is true.

## Keyboard

The contract records no keyboard interaction.

## Rules

Every item under When not to use is a MUST NOT. MUST and SHOULD follow RFC 2119.

| Level | Rule | How it is checked |
| --- | --- | --- |
| MUST NOT | When `columns` is set and `grid` is not set, then also set `grid`. `columns` only applies when `grid` is true; `grid` defaults to false, so the column count is dropped. | Checked by the MCP tool validate_component_usage (rule `pagelayout-columns-need-grid`). Also enforced in code: PageLayout.tsx gridStyle: returns {} when !grid. |
| MUST NOT | When `grid` is `false`, then do not set `columns`. `columns` only applies when `grid` is true. | Checked by the MCP tool validate_component_usage (rule `pagelayout-columns-with-grid-off`). Also enforced in code: PageLayout.tsx gridStyle: returns {} when !grid. |
| MUST | Required stories produce no axe violations at the configured severity. | Automated by `npm run test:stories:matrix:ci`. WCAG 2.5.8. |
| MUST | The accessibility tree (role, name, state) matches the committed snapshot for every required story. | Automated by `npm run test:stories:matrix:ci`. WCAG 1.3.1, 4.1.2. |
| MUST | The component remains legible and operable under a real browser forced-colors: active pass. | Automated by `npm run test:stories:hc`. |
| MUST | A human completed an end-to-end accessibility review of this component. | Manual review, last on 2026-11-04. |
| MUST NOT | Maintainers: Do not bypass design tokens or skip forced-colors verification at beta. | Release review; not machine-checked. |

## Install and import

- Install: `npm install @digitaltableteur/react` (version 0.9.1, restricted access on npm).
- Import: `import { PageLayout } from "@digitaltableteur/react";`
- Entry point: `@digitaltableteur/react/patterns` exports it too.
- Styles: import `@digitaltableteur/react/style.css` once at the app boundary.

## Links

- Machine-readable contract (Design System Contract 1.0): https://www.digitaltableteur.com/contracts/v1/PageLayout.contract.json
- Markdown version of this page: https://www.digitaltableteur.com/design-system/components/page-layout.md
- Component index as JSON: https://www.digitaltableteur.com/design-system/components.json
- All components: https://www.digitaltableteur.com/design-system/components
- Source: The implementation lives in a private repository. This page, its Markdown twin and the contract are generated from it at build time and are the public source of truth.
