Layout primitives

SLASHED ships breakpoint-free, container-query-driven layout primitives in the slashed.layout layer (core/layout.css). Each is a single class with per-instance tokens you can override inline (style="--sf-stack-gap: …"). Tokens are declared in core/tokens.layout.css.

All primitives are exercised live in the demo.

The primitives

Class What it does Key tokens
.sf-section vertical page rhythm; sizes --xs/--s/--m/--l/--xl/--2xl --sf-section-pad*
.sf-section-group collapses the gap between adjacent sections
.sf-container centered max-width wrapper; declares the named sf-layout container; --narrow --sf-container-*, --sf-gutter
.sf-box isolated unit with padding and optional border outline --sf-box-padding, --sf-box-border-width, --sf-box-border-color
.sf-center intrinsic centering with max-width and gutters; --intrinsic --sf-center-max, --sf-center-gutter
.sf-place-center box-neutral centring: centres content on both axes (grid + place-items:center) in a plain element
.sf-stack vertical flow with consistent gap (the “owl”) --sf-stack-gap
.sf-cluster wrapping inline group; --no-wrap --sf-cluster-gap/-align/-justify
.sf-sidebar content + fixed-ish side panel that wraps when narrow --sf-sidebar-*
.sf-switcher N columns above a threshold, stacked below; --no-wrap, --vertical --sf-switcher-threshold/-gap
.sf-grid auto-fill responsive grid; --fit, --xs … --2xl, --dense --sf-grid-min, --sf-grid-gap
.sf-grid-cols-1 / -2 / -3 / -4 / -6 fixed-column grids, container-responsive (no -5) --sf-grid-gap
.sf-grid-cols-1-2 / -2-1 / -1-3 / -3-1 ratio two-column grids --sf-grid-gap
.sf-bento dense free-form grid; container modifiers --2/--3/--6, --row-compact/--row-tall; child span classes .sf-bento-wide/-full/-tall/-featured --sf-bento-*
.sf-alternate zigzag two-column layout, reverses every other row; CQ-responsive --sf-content-gap, --sf-gap
.sf-pancake sticky-footer grid: header / main(1fr) / footer
.sf-content-grid breakout layout; children .sf-breakout, .sf-full-bleed; establishes an inline-size CQ scope (like .sf-container) so .sf-grid-cols-*/.sf-bento still respond when it replaces a container --sf-content-width, --sf-breakout-width
.sf-grid-flex flex-based grid alternative for uneven item counts; last-row leftovers stretch to fill (default) or stay fixed and centered (--center); --xs … --2xl --sf-grid-min, --sf-grid-gap
.sf-cover full-height region with a centered .sf-cover__center; --min/--max/--padding-* --sf-cover-*
.sf-frame aspect-ratio media box --sf-frame-ratio
.sf-bg-layer cover media layer behind the parent’s content (parent auto-positions + isolates); composes under .sf-scrim --sf-bg-layer-inset/-fit/-position/-radius/-z
.sf-reel horizontal scroll strip --sf-reel-*, --sf-mask-scrim-*
.sf-imposter absolutely-centered overlay; --fixed, --contain --sf-imposter-margin
.sf-subgrid / .sf-subgrid-rows inherit parent grid tracks
.sf-divider token-driven separator; --vertical, --soft, --strong, --dashed, --dotted, --gradient --sf-divider-*
.sf-gap injects gap into any existing flex/grid container without imposing display; --xs … --2xl --sf-gap
.sf-equal flowing multi-column layout (CSS columns, not grid) — content distributes like a newspaper column instead of sitting in fixed cells; --2/--3/--4/--6 set a target column-count --sf-equal-min-col, --sf-equal-gap, --sf-equal-rule-*
.sf-section--collapse adjacent sections share padding 50/50 across the boundary --sf-section-pad
.sf-icon em-based inline icon sizing; --xs … --2xl --sf-icon-*
.sf-icon--boxed padded, bordered icon frame (content-box sizing) --sf-icon-box-pad, --sf-icon-box-radius, --sf-icon-box-bg, --sf-icon-box-border

.sf-bento has two distinct, non-interchangeable modifier families — a -- (double-dash) class always modifies the container (.sf-bento itself), a - (single-dash) class targets a child placed inside it:

Class Applies to Effect
.sf-bento--2 / --3 / --6 container sets the column count
.sf-bento--row-compact / --row-tall container sets the row-height tier for every auto row in the grid
.sf-bento-wide a child that item spans 2 columns
.sf-bento-full a child that item spans the full row
.sf-bento-tall a child that one item spans 2 rows
.sf-bento-featured a child that item spans 2 columns and 2 rows

.sf-bento--row-tall and .sf-bento-tall are not aliases: the first stretches every row in the grid, the second stretches one grid item. Put the container modifier on .sf-bento, the child modifiers on the items inside it — never on the same element.

Size-modifier scale – xs..2xl everywhere

Every size-aware primitive supports the canonical range --xs --s --m --l --xl --2xl.

primitive what the suffix scales --xs --s --m (default) --l --xl --2xl
.sf-stack block-axis gap
.sf-cluster inline-axis gap
.sf-gap layout-agnostic gap
.sf-grid min column width
.sf-grid-flex min item width
.sf-section block padding
.sf-icon font-size

Three suffixes scale different physical dimensions. .sf-grid--xs means “narrow column tier” (denser layouts), not “small gap” — gap on .sf-grid is controlled separately by --sf-grid-gap, independent of the column-min modifier. To go beyond the built-in scale, override the scoped token directly: style="--sf-stack-gap: var(--sf-space-3xl)". The underlying space tokens (--sf-space-2xs/-3xl/-4xl) are part of the public token API.

Why no breakpoints

Primitives respond to their own container width via @container, not the viewport. Drop a .sf-grid-cols-3 inside any width context and it adapts — no media queries, no breakpoint tokens. See the container query contract for the two named containers (sf-layout, sf-alternate) and when to use them.

Composing

Primitives nest freely:

<section class="sf-section">
  <div class="sf-container">
    <div class="sf-stack">
      <h1>Title</h1>
      <div class="sf-grid sf-grid--m">
        <article class="sf-stack sf-stack--s"></article>
        <article class="sf-stack sf-stack--s"></article>
      </div>
    </div>
  </div>
</section>

Override a single instance without new CSS:

<div class="sf-cluster" style="--sf-cluster-gap: var(--sf-space-l)"></div>

Responsive gaps

Gaps are already fluid: --sf-grid-gap defaults to --sf-gap, which interpolates between its mobile and desktop ends via the shared space scale. Retune the whole rhythm at once with the space-scale endpoints (--sf-space-base-min / --sf-space-base-max) rather than per-token knobs.

When one primitive needs a different gap on small vs large screens, override its scoped token inside a @container query — the same container-driven model the primitives themselves use, so the gap reacts to the same width that collapses the columns. Endpoints stay live tokens, so they still follow any scale retuning:

.product-grid { --sf-grid-gap: var(--sf-space-l); }

@container (min-width: 48rem) {
  .product-grid { --sf-grid-gap: var(--sf-space-xl); }
}
<div class="sf-container">
  <div class="sf-grid product-grid"></div>
</div>

The override needs a container ancestor (.sf-container or .sf-cq) — the same requirement as .sf-grid-cols-*. The gap steps at the breakpoint rather than interpolating across it; for a gap that single step is imperceptible in normal use. The same pattern works for any scoped gap token (--sf-gap, --sf-content-gap, --sf-gutter, --sf-cluster-gap, …).

Container-query scope

The container-responsive primitives (.sf-grid-cols-*, .sf-bento, and any @container-scoped token override like the one above) resolve against the nearest ancestor that establishes an inline-size query container. Two wrappers establish one for you: .sf-container and .sf-content-grid — so either can host those primitives directly.

.sf-center deliberately does not. It’s a minimal centring primitive with no side effects (it doesn’t become a containing block for position: fixed descendants or a new stacking context), so it stays composable. When you need a container-responsive child inside a centred wrapper, add the query scope explicitly by composing .sf-cq:

<div class="sf-center sf-cq">
  <div class="sf-grid-cols-3"></div>
</div>

Centring: pick the primitive, not a .center utility

Coming from a utility framework, .sf-center is a false friend. There, a .center class usually centres the content inside it (flex place-items, margin: auto, text-align). In SLASHED that job belongs to .sf-place-center; .sf-center is the Every-Layout Center primitive: a max-width, guttered wrapper that centres itself in its parent — it does not centre its own children (unless you add --intrinsic). The two are a pair: .sf-center centres itself, .sf-place-center centres its content.

.sf-place-center is the box-neutral centring primitive — display: grid; place-items: center — the named form of the community display:grid; place-items:center idiom. Reach for it when you have a plain element and just want its content centred on both axes, without adopting a row/column/wrap primitive:

<div class="sf-place-center" style="min-block-size: 12rem">
  <p>centred on both axes</p>
</div>

Otherwise centring is expressed as intent on the right primitive:

To centre… Use Mechanism
content in a plain box, both axes .sf-place-center grid + place-items
a block wrapper horizontally .sf-center (prose measure) or .sf-container (app wrapper, adds CQ) margin-inline: auto
children in a row .sf-cluster--center justify-content
children in a column .sf-stack--center align-items
leftover grid items .sf-grid-flex--center justify-content
an element on both axes (overlay) .sf-imposter absolute + translate
content in a full-height region .sf-cover__center margin-block: auto
a wrapper’s children by their intrinsic width .sf-center--intrinsic flex column + align-items

Two things to remember about .sf-center specifically: