PLW LayoutFoundation 03
Palette
Type
Shape
Accent

Layout · width

Container

Centers content and caps line length with consistent gutters. Four widths: narrow (reading), default, wide (dashboards), and full. Gutters tighten on small screens.

AWidths
narrow · 46rem · long-form reading
default · 72rem · most pages
wide · 88rem · dashboards, galleries
full · edge to edge (still gutter-padded)

The hatched band is the viewport; the solid block is the container. Notice the gutters hold as the cap changes.

Semantics & a11y

  • Purely presentational <div>; landmarks (main, nav) live on Section, not here.
  • narrow keeps measure near 60–75 characters for readability.
  • Gutters use padding-inline so they never collapse on nested containers.

Token contract

--space-4--space-6--container-narrow--container-default--container-wide
// Container.tsx
type Props = { size?: "narrow" | "default" | "wide" | "full"; className?: string; children: React.ReactNode };
const max = { narrow: "max-w-[var(--container-narrow)]", default: "max-w-[var(--container-default)]", wide: "max-w-[var(--container-wide)]", full: "max-w-none" };
export const Container = ({ size = "default", className = "", children }: Props) =>
  <div className={`w-full mx-auto px-[var(--space-6)] ${max[size]} ${className}`}>{children}</div>;

Layout · rhythm + tone

Section

A vertical band with consistent block padding and an optional tone: default, surface, accent, or inverted (dark ground, light text). Density adjusts the rhythm: compact, regular, generous.

ATones

Default tone

Page background

Inherits the canvas. The everyday section.

Surface tone

Raised band

Lifts off the page to group related content.

Accent tone

Brand moment

For CTAs and high-emphasis bands.

Inverted tone

Dark ground

Light text on the foreground color, tokens remapped so children invert too.

Semantics & a11y

  • Renders <section>; pair with an aria-labelledby heading for landmark navigation.
  • Inverted remaps --color-bg/surface/border/muted locally, so nested cards and dividers stay legible without overrides.
  • Accent tone relies on the AA-tuned --color-accent-fg per palette.

Token contract

--space-10--space-16--space-24--color-surface--color-accent--color-accent-fg--color-fg--color-bg
// Section.tsx
const density = { compact: "py-[var(--space-10)]", regular: "py-[var(--space-16)]", generous: "py-[var(--space-24)]" };
const tone = {
  default:  "",
  surface:  "bg-[var(--color-surface)]",
  accent:   "bg-[var(--color-accent)] text-[var(--color-accent-fg)]",
  inverted: "bg-[var(--color-fg)] text-white [--color-surface:color-mix(in_srgb,var(--color-fg)_80%,#fff)] [--color-border:color-mix(in_srgb,#fff_22%,transparent)]",
};
export const Section = ({ as:Tag="section", t="default", d="regular", className="", ...p }) =>
  <Tag className={`${density[d]} ${tone[t]} ${className}`} {...p} />;

Layout · vertical rhythm

Stack

The vertical rhythm primitive: a flex column with a single gap from the space scale. Five steps (xs → xl). Replaces ad-hoc margins so spacing is consistent and editable in one place.

AGap scale
xs · space-2
item
item
item
sm · space-3
item
item
item
regular · space-6
item
item
item
lg · space-10
item
item
item

Semantics & a11y

  • No semantics of its own; wrap meaningful children (headings, paragraphs, controls).
  • Uses gap, not margins, so reordering or removing children never breaks spacing.
  • data-align="center" centers and sets text-align for hero stacks.

Token contract

--space-2--space-3--space-6--space-10--space-16
// Stack.tsx — vertical rhythm via a single gap token.
const gap = { xs:"gap-[var(--space-2)]", sm:"gap-[var(--space-3)]", md:"gap-[var(--space-6)]", lg:"gap-[var(--space-10)]", xl:"gap-[var(--space-16)]" };
export const Stack = ({ gap:g="md", align, className="", ...p }) =>
  <div data-align={align} className={`flex flex-col ${gap[g]} data-[align=center]:items-center data-[align=center]:text-center ${className}`} {...p} />;

Layout · horizontal rhythm

Inline

The horizontal counterpart to Stack: a wrapping flex row with a gap token and justify options. For button rows, tag lists, metadata, and toolbars, never bare inline-block siblings.

AGaps & justify
sm · space-2
FilterOpen nowTop ratedInsured

lg · space-8
Mon–Fri9–5By appointment

justify between · row spans full width
Whitman

Semantics & a11y

  • Wraps by default so rows never overflow on small screens.
  • Preferred over inline-block runs: gap spacing survives drag-reorder and deletion.
  • For toolbars, add role="toolbar" on the consuming element.

Token contract

--space-2--space-4--space-8
// Inline.tsx — horizontal rhythm, wraps by default.
const gap = { sm:"gap-[var(--space-2)]", md:"gap-[var(--space-4)]", lg:"gap-[var(--space-8)]" };
export const Inline = ({ gap:g="md", justify, className="", ...p }) =>
  <div data-justify={justify} className={`flex flex-wrap items-center ${gap[g]}
    data-[justify=between]:justify-between data-[justify=center]:justify-center ${className}`} {...p} />;

Layout · 12 columns

Grid

A responsive 12-column grid with a gap token. Children span columns (col-6, col-4…) and collapse toward one column as space tightens. Toggle Mobile above to watch it fold.

ACommon splits
col-6
col-6
col-4
col-4
col-4
col-3
col-3
col-3
col-3
col-8 · main
col-4 · aside

Semantics & a11y

  • Visual order only; keep DOM order meaningful so tab and reading order match.
  • Collapses to fewer columns under 720px and to one under 460px (and in Mobile preview).
  • Gap is a token, so grid density matches Stack/Inline across the system.

Token contract

--space-3--space-6--space-10
// Grid.tsx — 12-col, responsive, gap from tokens.
export const Grid = ({ gap="md", className="", ...p }) => {
  const g = { tight:"gap-[var(--space-3)]", md:"gap-[var(--space-6)]", loose:"gap-[var(--space-10)]" }[gap];
  return <div className={`grid grid-cols-12 ${g} ${className}`} {...p} />;
};
// Child: <div className="col-span-12 md:col-span-4"> … </div>