A narrower column, per instance
The cap is a token, and a token is an input: override --pct-container-max-width on one element and only that column narrows — no class, no media query.
Twenty rem, for a form.
Thirty-two rem, for an article.
A reading column: content capped at a maximum width, centred, with a gutter that follows the viewport.
@pacit/components/containerpct-containerNo APG pattern applies, and none is invented — the reasoning is under Accessibility ↓
This column is 28rem at most — one custom property, scoped to this instance. Children can respond to the container, not the viewport.
import { Component } from '@angular/core';
import { PctContainer } from '@pacit/components/container';
/** A reading column: centred, padded, capped — and the cap is a token you may override. */
@Component({
selector: 'demo-container',
imports: [PctContainer],
// The host states the width the column needs — the stage is a flex row, and a column
// inside a box sized by its content has no width to give (lesson-146).
styles:
':host { display: block; inline-size: 100%; } pct-container { outline: 1px dashed var(--pct-border); }',
template: `
<pct-container style="--pct-container-max-width: 28rem">
<p>
This column is 28rem at most — one custom property, scoped to this
instance. Children can respond to the container, not the viewport.
</p>
</pct-container>
`,
})
export class ContainerDemo {}container.demo.ts — the same file runs here and is highlighted under Code
One import per component, and the shortest possible use — the package itself is installed once, on the start page.
import { PctContainer } from '@pacit/components/container';| Exported | What it is |
|---|---|
PctContainercomponentpct-container | A reading column: content capped at --pct-container-max-width, centred, with a gutter that follows the viewport. The first of the three layout primitives (0057), and like the other two it carries no ARIA at all — layout is presentational, and the platform's div needs no help being one. It has no inputs either: both of its lengths are tokens, and a token scoped on the element is the per-instance API. |
<pct-container>
<p>A column no wider than the reading measure.</p>
</pct-container>Each example is the component itself, not a screenshot. Open the code under any of them.
The cap is a token, and a token is an input: override --pct-container-max-width on one element and only that column narrows — no class, no media query.
Twenty rem, for a form.
Thirty-two rem, for an article.
Read from the source at build time — the types, the defaults and the lines of documentation are the library's own.
A reading column: content capped at --pct-container-max-width, centred, with a gutter that follows the viewport. The first of the three layout primitives (0057), and like the other two it carries no ARIA at all — layout is presentational, and the platform's div needs no help being one. It has no inputs either: both of its lengths are tokens, and a token scoped on the element is the per-instance API.
| Name | Type | What it does |
|---|---|---|
| No inputs — the element is configured by its content. | ||
(click), (focus), the form's submit — are the platform's, and two-way models above carry the rest. The hooks a stylesheet or a test can hold. Stable by contract — the parts inventory gate fails the build when one disappears.
| Attribute | Bound to | Note |
|---|---|---|
class | pct-container |
Two ways in, both stable. No class names to depend on.
In a test, PctContainerHarness from @pacit/components/testing reads a part by the name below and a state by its attribute — the harness gate holds the names to the package.
No parts of its own — the element you write is the whole of it.
Every value the component reads, with the light and the dark default. Override a token at any scope and it cascades like any custom property.
| Token | Styles |
|---|---|
--pct-container-max-width72rem | Maximum width of the column. |
--pct-container-padding-xclamp(16px, 4vw, 40px) | Horizontal padding of the column. |
A brand override in a few declarations. Scope it to a theme attribute, a page or one container — the component never notices.
[data-theme='brand'] {
--pct-container-max-width: 60rem;
}This column is 28rem at most — one custom property, scoped to this instance. Children can respond to the container, not the viewport.
Pattern: none — layout is presentational and this host carries no ARIA at all: no role, no label, not one aria-* attribute. The unit suite pins the absence itself, so a future "helpful" attribute is a red test. Named in the class JSDoc.
| key | effect | test |
|---|
Empty by design: the container is not interactive and adds nothing focusable. The projected content keeps its own keyboard, untouched — there is no handler in the component to intercept anything with.
Every line is a test in three engines or a gate on every commit. The gaps are listed the same way, each with the requirement that will close it.
libs/components/container/src/container.ts — "no ARIA at all", and the reasoncontainer.spec.ts › "carries no ARIA at all" plus layout.spec.ts › "none of the three carries a single ARIA attribute" — no role, no aria-*, nothing focusable added, in three enginesapps/sandbox-e2e/src/a11y.spec.ts — /layout in SBX_ROUTESapps/sandbox-e2e/src/layout.spec.ts, and pixels would re-prove the same numbers blurrier. The /layout view's baseline is the grid card'sforced-colors: active — no state carried by colour alonenot applicable — the component paints nothing: no colour, no border, no background stands in container.scss, so forced colours has nothing to replaceprefers-reduced-motion — duration from a tokennot applicable — nothing moves--pct-control-height-*not applicable — a column is not a control and has no height of its owndir="rtl" screenshotcontainer.scss is logical throughout (max-inline-size, margin-inline, padding-inline — req-token-logical, enforced by check-styles); nothing directional is drawn, so no RTL baseline of its ownNG05xxapps/sandbox-e2e/src/hydration.spec.ts — /layout in SBX_ROUTESFormValueControllibs/components/parts.snapshot.md — a component with no parts is itself the entrycontrast.policy.jsonlibs/tokens/tokens.snapshot.md — two --pct-container-* names; the contrast policy deliberately holds no entry, because the component paints no colourPCT_TEXTSnot applicable — no text at alllibs/components/size.snapshot.md — the ./container row, on @angular/core alone: no ./core, no config, no texts — a column is one class and one stylesheetdocs/acr/at/ — three readers, whole sandbox, and the argument is now measured rather than asserted: /layout produced no stop of its own in any of the three logs. Every stop there belongs to the sandbox scaffold — Theme · panel · light · selected radio button. and its two neighbours — so a container adds nothing a Tab walk can land on. It does not prove silence to a reader READING the page, which is a different pass, and no reader announced a box where one was drawnapps/sandbox/src/app/views/layout/ (the sandbox view). The published site: /components/container — prerendered, the demo's own source is the code tablibs/components/container/src/container.spec.ts — 2 cases; the targeted Stryker run of 2026-09-02 generates zero mutants for container.ts — a class with no logic offers nothing to break. The file stays in mutation.policy.json, so the day it grows logic the report owes a row — the day's shared full run confirmed it: zero mutants, and the snapshot owes no row to a file that generates noneRead at build time from the tracked snapshots, not typed here.
The first of the three layout primitives (0057): content capped at --pct-container-max-width, centred, with a gutter that follows the viewport through a clamp(). The host also declares container-type: inline-size, so everything projected into the column can measure against this column with a container query instead of guessing at a viewport a sidebar may have eaten half of.
lesson-146).clamp() lives in one token. A theme overriding --pct-container-padding-x owns the whole expression — floor, slope and ceiling — not a parameter of it. Recorded in 0057's costs.container-type: inline-size makes the host a query container, not a positioning ancestor. Size containment does not change what position: absolute resolves against; the two are unrelated, and the name reads scarier than what it does.72rem widens with a reader's larger type on purpose; a fixed-pixel column is one token override away, not a second component.