03 / Layout primitives

Seven primitives. Almost no breakpoints.

Every demo below is live — drag the window narrower and watch. Nothing here uses a media query. fr distributes leftover space, minmax and clamp set the boundaries, and the browser resolves the rest. Breakpoints only appear where a layout must genuinely change shape rather than reflow.

Column grid — 4 / 8 / 12

Page composition starts at four columns and doubles, then adds half again: four on phones, eight on tablets, twelve on desktop. Four is the right floor because it is the smallest count that still divides usefully — halves and quarters — and because eight columns on a 375px screen gives you 30px tracks that no content fits into.

4 columns · the base

1
2
3
4
span 4
span 2
span 2
span 3
1
--se-grid-columns: 4;
--se-grid-gutter:  var(--se-space-4);
--se-grid-margin:  clamp(1rem, 4vi, 2rem);

[data-se="grid"] {
  display: grid;
  grid-template-columns: repeat(var(--se-grid-columns), minmax(0, 1fr));
  gap: var(--se-grid-gutter);
  padding-inline: var(--se-grid-margin);
}

@media (--se-bp-md) { :root { --se-grid-columns: 8;  } }
@media (--se-bp-lg) { :root { --se-grid-columns: 12; } }

Two media queries, and they are the only ones in the system. Worth being honest about why they earn an exception: a column count is a discrete decision, not a continuous one. There is no such thing as 7.4 columns, so no amount of clamp or fr can interpolate between four and eight. Everything the column grid holds still sizes itself intrinsically — the grid decides where things sit, never how big they are. Component-level layout uses the intrinsic primitives below and needs no breakpoints at all.

Grid

Portfolio tiles, project cards, template pickers. Columns are found, not declared.

display: grid;
gap: var(--se-space-4);
grid-template-columns: repeat(auto-fit, minmax(min(100%, var(--se-col-min)), 1fr));

Editor chrome: a panel with a floor and a ceiling, main content takes the rest.

minmax(min(100%, 11rem), 1fr)
3fr
display: grid;
gap: var(--se-space-4);
grid-template-columns: minmax(min(100%, var(--se-sidebar-min)), 1fr) 3fr;

Switcher

Side by side above a threshold, stacked below it — decided by the container, not the viewport.

display: flex;
flex-wrap: wrap;
gap: var(--se-space-4);
/* children */
flex: 1 1 calc((var(--se-switch-at) - 100%) * 999);

Cluster

Skill tags, toolbars, filter chips. Wraps without ceremony.

Art direction Motion Editorial design Type Brand systems Photography
display: flex;
flex-wrap: wrap;
gap: var(--se-space-2);
align-items: center;

Stack

Vertical rhythm. One gap, no margins anywhere.

Frame

Locks a ratio for work thumbnails, whatever the source file is.

16 / 9
1 / 1

Cover

Header, flexible middle, footer. The published-portfolio shell.

1fr