Skip to content

PRIMITIVE

Split

Two columns that become one, with no breakpoint and no wrapper. Equal by default; a third and two thirds when you ask. A portrait beside a bio, a quote beside a client rail, a pitch beside a form.

A portrait and what goes beside it

The floor is per instance: 220px here, because the stage is 540px and two 300px columns plus a 64px gap need 664. The default is 300px, which stacks at this width β€” narrow the window and watch this one do the same.

PLACEHOLDER Portrait β€” 4:5

A tall thing on the left, and it sets the height of nothing.

Both children take an equal share while there is room for both. The moment there is not, each takes the whole width and they stack in source order β€” no breakpoint, no wrapper, nothing measuring the window.

BASED IN Somewhere Β· CET
MORNINGS Mat, then desk
EVENINGS Usually a sampler
HTML
<!-- The floor is per instance. 220px here because the stage is 540px wide; the
     default is 300px, which stacks at this width β€” which is the whole point. -->
<div class="split" style="--split-min:220px; width:100%">
  <figure class="flow" style="margin:0">
    <div class="placeholder placeholder-warm" style="--ratio:4/5">
      <span class="placeholder-kicker">PLACEHOLDER</span>
      <span class="placeholder-note">Portrait β€” 4:5</span>
    </div>
    <p class="placeholder-caption">A tall thing on the left, and it sets the height of nothing.</p>
  </figure>
  <div class="flow">
    <p class="placeholder-caption">Both children take an equal share while there is room for both. The moment there is not, each takes the whole width and they stack in source order β€” no breakpoint, no wrapper, nothing measuring the window.</p>
    <div class="rule-stack">
      <div class="rule-row rule-row-spec">
        <span class="rule-row-key">BASED IN</span>
        <span class="rule-row-value">Somewhere Β· CET</span>
      </div>
      <div class="rule-row rule-row-spec">
        <span class="rule-row-key">MORNINGS</span>
        <span class="rule-row-value">Mat, then desk</span>
      </div>
      <div class="rule-row rule-row-spec">
        <span class="rule-row-key">EVENINGS</span>
        <span class="rule-row-value">Usually a sampler</span>
      </div>
    </div>
  </div>
</div>

The unequal pair

Add `split-aside` and the first child takes a third, the rest twice that. Narrow the window: it holds the ratio exactly until the floors stop fitting, then goes to one column in a single step.

PLACEHOLDER Portrait β€” 1:1

A third and two thirds, held exactly at every width the pair survives β€” and one column the moment it does not. The ratio lives in the flex bases; the wrap lives in the floors. Neither can do the other's job.

RATIO 1 : 2, by the bases
STACKS AT Whatever the floors say
HTML
<!-- The narrow column's share and its floor are both per instance. 26% and
     140px here because the stage is 540px wide; the defaults are 30% and 200px,
     which stack at this width. -->
<div class="split split-aside" style="--split-aside:26%; --split-min:200px; width:100%">
  <figure class="flow" style="margin:0">
    <div class="placeholder" style="--ratio:1/1">
      <span class="placeholder-kicker">PLACEHOLDER</span>
      <span class="placeholder-note">Portrait β€” 1:1</span>
    </div>
  </figure>
  <div class="flow">
    <p class="placeholder-caption">A third and two thirds, held exactly at every width the pair survives β€” and one column the moment it does not. The ratio lives in the flex bases; the wrap lives in the floors. Neither can do the other's job.</p>
    <div class="rule-stack">
      <div class="rule-row rule-row-spec">
        <span class="rule-row-key">RATIO</span>
        <span class="rule-row-value">1 : 2, by the bases</span>
      </div>
      <div class="rule-row rule-row-spec">
        <span class="rule-row-key">STACKS AT</span>
        <span class="rule-row-value">Whatever the floors say</span>
      </div>
    </div>
  </div>
</div>

split-stretch, for a row of cards

Every column as tall as the tallest. The base case is align-items: start on purpose β€” stretched, a short bio beside a tall portrait grows a column of empty space it then centres nothing in. A row of boxes with their own edges is the other case: three cards ending at three different heights read as a fault rather than as a difference. Measured on a consuming site's three download cards before this existed: 490px, 514px and 431px. Check what the extra height lands on before you reach for it β€” a card holds its content at the top and grows empty space under it, which is fine, while a rule-stack stretches its LAST ROW into that space and the rows stop lining up with the column beside them.

ONE

A line, and nothing else to say.

TWO

This one takes a paragraph, because the thing it describes has a condition attached and the condition is the part people miss.

THREE

Two lines or so, which is what most of them turn out to be.

HTML
<!-- Three boxes that say different amounts. Take `split-stretch` off and they
     end at three different heights β€” which reads as a fault rather than as a
     difference, once each one has a box of its own.

     170px and not the theme's 300px, because three columns need 3 x min + 2 x
     gap: at a 1440px viewport the gap is 72px, so 300px asks for 1044px in a
     reading column that is 700px wide and the third box wraps. 170px needs
     654px and leaves 46px of slack. -->
<div class="split split-stretch" style="--split-min:170px; width:100%">
  <div class="notice">
    <span class="notice-kind">ONE</span>
    <p class="notice-body">A line, and nothing else to say.</p>
  </div>
  <div class="notice">
    <span class="notice-kind">TWO</span>
    <p class="notice-body">This one takes a paragraph, because the thing it describes has a condition attached and the condition is the part people miss.</p>
  </div>
  <div class="notice">
    <span class="notice-kind">THREE</span>
    <p class="notice-body">Two lines or so, which is what most of them turn out to be.</p>
  </div>
</div>

Reference

Read from primitives/split.css when this page was built, so it cannot fall behind the stylesheet.

CLASSES.split.split-stretch.split-aside
SETTINGSSet the first on one element; a theme sets the second for all of them.--split-gap β€” falls back to --ds-split-gap--split-min β€” falls back to --ds-split-min--split-aside β€” falls back to --ds-split-aside
READS--ds-split-aside-min

Notes

πŸ›‘ IT IS FLEX, AND IT WAS GRID UNTIL A PHANTOM TRACK TURNED UP IN IT

There were two engines in this file once, and collapsing them into one is the most interesting thing that has happened to it. The equal split was a grid β€” repeat(auto-fit, minmax(min(100%, --split-min), 1fr)) β€” which asks for as many tracks as will fit rather than as many as there are children.

Two children in a 1120px box with a 75px gap fit three, so the computed value was 522.5px 522.5px 0px: two real columns and a collapsed third nobody asked for. No pixel ever moved because of it β€” five engines collapse the phantom's gutter and hand the two children the whole row, and the spec says they must. But a browser's grid inspector draws that collapsed gutter outside the element, so the overlay shows a trailing strip of hatching the layout does not have. Two people looked at that strip and reasonably concluded the band was losing 75px it never lost.

Flex asks for exactly as many boxes as there are children, so there is no phantom to collapse and no overlay to misread. And it was already what .split-aside had to use: auto-fit makes every track identical by definition, so it could never say “a third and two thirds”, and the obvious alternative β€” 1fr 2fr β€” will not wrap, so at a narrow width both tracks sit on their floor, the row totals 200% and the page scrolls sideways. One engine now. Measured against the old grid on every split in this book at 1440, 1024, 760 and 390: every box and every child identical.

IT IS NOT A HAIRLINE GRID

Fair question, because they reflow the same way. Hairline grid rules its cells and closes a frame around them; this one puts space between two things and draws nothing at all. A split with a gap of zero is a hairline grid missing every one of its lines β€” if you want the rules, you want the other one.

START, NOT STRETCH

align-items: start is the point rather than a default. Stretched, a short bio beside a tall portrait grows a column of empty space it then centres nothing in β€” and worse, a rule stack inside one stretches its last row to swallow the difference, so its row heights stop matching anything beside them.

THE INNER min() IS THE PART PEOPLE DROP

Every child carries min-inline-size: min(100%, var(--split-min)). Without the min(100%, …) a 300px floor on a 260px screen makes the column wider than the viewport and the whole page scrolls sideways. Same rule, same reason, as the hairline grid β€” and here the floors are also what make the line overflow, and therefore wrap. Drop one and you get two columns that shrink forever and never stack.

FOUR PARTS STILL SPELL THIS BY HAND

Testimonial, enquiry card, CTA slab and hero each write these five lines out with a token pair of their own. They were built before this existed, and converting one is a behaviour-preserving change plus a token retirement rather than a tidy-up β€” so they are named here instead of quietly left. Adding a fifth hand-written copy is not a thing anybody may do.