Responsive layout

Page Sections distinguishes viewport width from content-container width. A narrow article in a wide window should still use a narrow content grid. Dimensions are CSS pixels; physical screen size and device pixel ratio do not select a breakpoint.

Viewport classification and page width

WidthresolveViewportLayoutDefault ordinary content width
<600xsAvailable width
600–839sAvailable width
840–1199mAvailable width
1200–1599lAvailable width, capped at 1536px
1600–1808xlParent available width minus 272px, capped at 1536px
1809–22992xlAvailable width, capped at 1536px
>=23003xlSame ordinary content cap

The 1536px cap is canonical. resolvePageLayout(ResponsiveLayoutInput) resolves content/menu positions, including optional scrollbar and safe-area reservations. The native navigation uses the available containing-block width; mount it at the viewport level.

Navigation shows its item disclosure below 1200px and satellites at 1600px. ContentPage uses its sidebar from 1200px. Image/video pairs split at a 600px container; artwork heroes at 840px.

Content grids

Unpadded container widthGridColumnsGutter/gapHeading size / lineBody size / lineHeading gap
<600xs412px18 / 28px14 / 20px8px
600–839s616px18 / 28px14 / 20px8px
840–1199m816px20 / 32px14 / 20px12px
>=1200l1224px24 / 40px16 / 24px16px

Section creates a named ps-content container, and its inner wrapper supplies one outer gutter on each side. ContentGrid adds only gaps between columns. A container exactly 840px wide selects m before its 32px total horizontal padding is subtracted.

For container width c, column count n and gap g, column width is max(0, (c - (n + 1) * g) / n). A span of s columns occupies s * columnWidth + (s - 1) * g, capped by container width. Numeric core resolution caps c at 1536px and normalizes negative/nonfinite input to zero. The section content wrapper also caps at 1536px; full layout can extend its outer surface.

Explicit responsive blocks

svelte
<script>
  import { Section, ContentGrid, ContentBlock } from '@page-sections/svelte';
</script>

<Section
  section={{
    id: 'layout',
    title: 'Layout',
    headline: 'Main story and related notes',
    style: { width: '1100px' }
  }}
>
  <ContentGrid>
    <ContentBlock span={{ xs: 4, s: 3, m: 5, l: 8 }}><p>Main story</p></ContentBlock>
    <ContentBlock span={{ xs: 4, s: 3, m: 3, l: 4 }}><p>Related notes</p></ContentBlock>
  </ContentGrid>
</Section>

Below 600px the blocks stack as full rows. On s they split evenly; on m and l the first is wider. Omitted spans fill a row; responsive values inherit mobile-first. Oversized values clamp; nonpositive, noninteger and nonfinite values fill a row. ContentBlock also emits client warnings for invalid/clamped values. Visual order follows source order; no dense placement is applied.

Catalog and editorial cards

Catalog cards use the same media-plus-text layout in CardGridSection and CarouselSection: S/M span 2 columns, L spans 4 (3 on the S grid). Media ratios are 3/2 (default), 4/3 or 1/1; titles have two lines in S/M and three in L, with an additional collection-wide eyebrow line for M/L when used. Text has 12px padding; contained media has an 8px inset. S titles are 12px. The complete card is not constrained to a square. Geometry follows layoutTokens.cards and resolveCardLayout.

Editorial cards use cardVariant="editorial", responsive itemSpan and natural text height and equal grid rows. They support descriptions, footers and independent action links.

Nested text and panels omit redundant outer gutters. A nested grid or catalog establishes a new grid and owns one gutter pair; nested declarative grids should use <Section inset>. Breakpoint selection always uses that section container before padding.

See Core API and CSS Variables and Layout Tokens.