# Page container

> One element that centres content at a named width (prose, narrow, default, wide or full) and keeps a 16, 24 or 32 px gutter from the screen edge. No vertical space, no background.

- ID: `cmp_page_container_01`
- Slug: `page-container-01`
- Version: `1.0.0` (current)
- Status: published
- Published: 2026-09-30
- Updated: 2026-09-30
- Available versions: `1.0.0`
- Kind: control
- Primary category: `layout`
- Detail page: https://pagesugar.com/components/page-container-01?variant=default
- Preview: https://pagesugar.com/preview/page-container-01

## Variants

| Variant | Label | Default | Artifact digest |
| --- | --- | --- | --- |
| `default` | Default | yes | `sha256-125f140e2d30dffe1765e42925e64c98bff9e0e1f002ab26b0b26208ff815fb2` |

## Runtime and compatibility

- Runtime: svelte
- Svelte: 5
- SvelteKit required: no (portable Svelte component)
- Tailwind CSS: 4
- SSR: supported
- Requires client-side JavaScript: no
- Integration level: presentational
- Appearance modes: light
- Suggested directory: `src/lib/components/page-container-01`

## Dependencies

No third-party runtime packages.

## Services

No external services required.

## Usage

Wrap content and pick a size; the container centres it and holds the gutter. It adds no vertical padding or margin, paints no background and sets no type, so vertical rhythm, backgrounds and full-bleed bands belong to the section around it. Other HTML attributes (id, aria-labelledby, style) pass through to the element.

Required props: `children`

```svelte
<script lang="ts">
	import Container from '$lib/components/page-container-01/Container.svelte';
</script>

<Container as="section" size="prose" aria-labelledby="returns-title" class="py-16 sm:py-24">
	<h2 id="returns-title" class="text-3xl font-semibold tracking-tight">Returns</h2>
	<p class="mt-4 text-zinc-600">Send anything back within 30 days of delivery for a full refund.</p>
</Container>
```

Limitations:

- The maximum width is the content width: once the maximum is reached, a default container measures 72rem plus two gutters. To cap the outer box instead, change box-content to box-border and the width class to w-full together; changing only one of them insets the content twice.
- The gutter follows viewport breakpoints (sm, lg), not the width of the parent. Inside a narrow column, set --page-container-gutter or nest the container so its gutter drops to zero.
- A container anywhere inside another .page-container has no gutter of its own, and --page-container-gutter on it cannot restore one. A band that breaks out of a container to the viewport edge should close the outer container instead of nesting in it. A section of your own with side padding does not clear the gutter; set --page-container-gutter: 0px on the inner container there.
- Give --page-container-gutter a length or percentage with a unit: 0px, not 0. A unitless zero makes the width calculation invalid, and inside a flex or grid parent the container can then shrink to its content.
- Borders on the root are not subtracted from its width and overflow the parent by their thickness; put a border on an element inside, or use a ring or outline.
- No safe-area insets. If your page sets viewport-fit=cover, wrap the gutter in max() with env(safe-area-inset-left) and env(safe-area-inset-right).
- The class prop is appended after the container's classes; a conflicting width or padding utility is not guaranteed to win.

## Usage guide

### Page container

A width primitive. Use one per band of content: the section around it owns vertical space and
background, the container owns the measure and the gutter.

#### Sizes

| Size      | Content width | Use                            |
| --------- | ------------- | ------------------------------ |
| `prose`   | 65ch          | Articles, help pages, policies |
| `narrow`  | 48rem         | Forms, sign-in, checkout       |
| `default` | 72rem         | Marketing sections             |
| `wide`    | 80rem         | Headers, footers, dense grids  |
| `full`    | none          | Boards, tables, dashboards     |

The width is the content width. The box is `box-content`, so the gutter (16 px, 24 px from
`sm`, 32 px from `lg`) sits outside it. A `default` container therefore starts on the same x as
the inner column of every PageSugar section.

#### A full-bleed band

```svelte
<div class="bg-zinc-950 py-24 text-zinc-50">
	<Container>
		<h2 class="text-3xl font-semibold tracking-tight">Plan the next quarter in one afternoon</h2>
	</Container>
</div>
```

#### An article inside a page layout

```svelte
<Container as="main" size="wide">
	<Container as="article" size="prose">
		<!-- 65ch, centred, no second gutter -->
	</Container>
</Container>
```

#### Changing the gutter

```css
.docs-page {
	--page-container-gutter: clamp(1.5rem, 6vw, 5rem);
}
```

## Props

| Name | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| `size` | 'prose' \| 'narrow' \| 'default' \| 'wide' \| 'full' | no | `'default'` | Content width: prose 65ch (max-w-prose), narrow 48rem, default 72rem (the edge PageSugar sections use), wide 80rem, full no maximum. |
| `as` | 'div' \| 'section' \| 'header' \| 'footer' \| 'main' \| 'article' | no | `'div'` | The element to render. A section becomes a region landmark only when it is named, for example with aria-labelledby. |
| `class` | `string` | no |  | Extra classes on the root, such as vertical padding. Appended after the container's own; not a guaranteed override for width or gutter. |
| `children` | `Snippet` | yes |  | The content to constrain. |

## Customization

Pick a size per use, set one CSS variable to change the gutter, and edit the widths lookup in the source to change what each size means.

- Gutter: set --page-container-gutter on the container or any ancestor to one length with a unit, such as 0px, 1.25rem or clamp(1.5rem, 6vw, 5rem). It replaces all three steps (16, 24 and 32 px).
- Widths: the widths object at the top of the script maps each size to a complete Tailwind class. Change max-w-6xl to max-w-5xl to make default 64rem; keep whole class names so Tailwind finds them.
- Vertical space and backgrounds: add them on a section around the container, or pass padding through class (class="py-16 sm:py-24"). The container never adds its own.
- Full-bleed bands: put the background on an outer element that spans the page and the container inside it; the content still lines up with every other container of the same size.
- Nesting: a prose container inside a default one keeps its 65ch measure and takes no second gutter, so an article can sit inside a page layout.
- Container queries: add @container through class when children should respond to the container's width rather than the viewport's.

| Token | Public CSS variable |
| --- | --- |
| `gutter` | `--page-container-gutter` |

## Accessibility

- Renders the element named by as and adds no role or ARIA; the element's own semantics apply.
- A section is a region landmark only when it has an accessible name; pass aria-labelledby pointing at its heading.
- Render main once per page. header and footer are banner and contentinfo landmarks only when they are not inside an article, aside, main, nav or section, or an element with the matching role (article, complementary, main, navigation, region).
- The gutter uses padding-inline and centring uses margin-inline, so the layout mirrors under dir="rtl" without changes.

Known limitations:

- The container does not break long words or wrap wide children; content wider than the measure (a long URL, a wide table) needs its own overflow-wrap or scrolling.

## License

- Declared source: MIT
- Default license approval is pending. See https://pagesugar.com/docs/license.

## Source

- Palette: Default (`default`)
- Entry: `Container.svelte`
- Suggested directory: `src/lib/components/page-container-01`
- Files: 1
- Artifact digest: `sha256-125f140e2d30dffe1765e42925e64c98bff9e0e1f002ab26b0b26208ff815fb2`

Paths below are relative to the suggested directory. Copy the files as they are;
they import nothing from this site.

#### `Container.svelte`

Role: entry · 2769 bytes · SHA-256 `3778ea6e37aa375648029f4d54199abfbf8e335f73174fc9dda95e4526b32c4e`

```svelte
<!--
	Page container: one element that centres content at a named width and keeps a gutter between
	it and the screen edge. It adds no vertical space and paints nothing.

	The named width is the maximum width of the content, not of the box. The box is content-box, so
	the gutter sits outside the measure: `default` holds up to 72rem of content, the same edge a
	PageSugar section's inner column uses, and `prose` up to 65ch.
-->
<script lang="ts" module>
	export type ContainerSize = 'prose' | 'narrow' | 'default' | 'wide' | 'full';
	export type ContainerElement = 'div' | 'section' | 'header' | 'footer' | 'main' | 'article';
</script>

<script lang="ts">
	import type { Snippet } from 'svelte';
	import type { HTMLAttributes } from 'svelte/elements';

	interface Props extends Omit<HTMLAttributes<HTMLElement>, 'class' | 'children'> {
		/** Content width: prose 65ch, narrow 48rem, default 72rem, wide 80rem, full none. */
		size?: ContainerSize;
		/** The element to render. Name a section with aria-labelledby if it should be a landmark. */
		as?: ContainerElement;
		/** Extra classes on the root. Added after the container's own, not a guaranteed override. */
		class?: string;
		/** The content. */
		children: Snippet;
	}

	let { size = 'default', as = 'div', class: className, children, ...rest }: Props = $props();

	/* Complete class strings, so Tailwind finds every one of them in this file. */
	const widths: Record<ContainerSize, string> = {
		prose: 'max-w-prose',
		narrow: 'max-w-3xl',
		default: 'max-w-6xl',
		wide: 'max-w-7xl',
		full: 'max-w-none'
	};
</script>

<!--
	--_inline is the gutter in force at this breakpoint. The width takes both gutters off the
	parent, so the box fills it inside a flex or grid parent too, where auto margins would
	otherwise shrink it to its content. Keep the parentheses round var(--_inline)*2: without them
	Tailwind reads the underscore as a space.
-->
<svelte:element
	this={as}
	{...rest}
	class={[
		'page-container mx-auto box-content w-[calc(100%-(var(--_inline)*2))] px-[var(--_inline)]',
		'[--_inline:var(--_gutter)] sm:[--_inline:var(--_gutter-sm)] lg:[--_inline:var(--_gutter-lg)]',
		widths[size],
		className
	]}
>
	{@render children?.()}
</svelte:element>

<style>
	/* One public variable sets the gutter at every width. Unset, it steps 16, 24 and 32 px. */
	.page-container {
		--_gutter: var(--page-container-gutter, 1rem);
		--_gutter-sm: var(--page-container-gutter, 1.5rem);
		--_gutter-lg: var(--page-container-gutter, 2rem);
	}

	/* A container anywhere inside another is already clear of the screen edge; its gutter would
	   double. Close the outer container before a band that breaks out to the viewport edge. */
	.page-container :global(.page-container) {
		--_inline: 0px;
	}
</style>
```

## Artifacts

### Default (`default`) (default)

- Artifact digest: `sha256-125f140e2d30dffe1765e42925e64c98bff9e0e1f002ab26b0b26208ff815fb2`
- Entry: `Container.svelte`
- Receipt: https://pagesugar.com/artifacts/cmp_page_container_01/1.0.0/default/sha256-125f140e2d30dffe1765e42925e64c98bff9e0e1f002ab26b0b26208ff815fb2/manifest.json
- Bundle: https://pagesugar.com/artifacts/cmp_page_container_01/1.0.0/default/sha256-125f140e2d30dffe1765e42925e64c98bff9e0e1f002ab26b0b26208ff815fb2/bundle.zip (4177 bytes, sha256 `d1803d5910f48c88aad0a7e0d457f2f7a3e1448c5a53a8e9888055e346d408d9`)

Files:

- `Container.svelte` (entry, 2769 bytes): https://pagesugar.com/artifacts/cmp_page_container_01/1.0.0/default/sha256-125f140e2d30dffe1765e42925e64c98bff9e0e1f002ab26b0b26208ff815fb2/source/Container.svelte
