Components

Subdivision Field

The manual's Teilungsraster plate makes a claim the web implementation had never answered: the recursive subdivision “kann als Konzept für interaktive UI-Elemente oder Menüstrukturen genutzt werden.” Until now the subdivision was a layout — .subdivide, and the blog grid on top of it — where the hierarchy is authored once and then fixed, because which article is largest is a fact about the archive. A field is the same series with the hierarchy handed to the reader. Point at a cell and it takes the half; everything else keeps its place in the row and continues the series behind it.

The field

Five cells, resting. This is the pure plate drawn exactly — 1/2, 1/4, 1/8, 1/8 — because the resting head is the first cell. Move across it, or tab into it.

The tick names its cell at every width, which is the only thing a 1/16 cell has room for and is how the application plate labels a column too. The copy arrives in the same series the widths do: the head carries all of it, the next rank keeps its title, and from there down the tick is the cell.

<nav class="cf-subdiv" aria-label="Prozess">
  <a class="cf-subdiv__cell" href="/prozess/discovery">
    <span class="cf-subdiv__tick">01 — Discovery</span>
    <span class="cf-subdiv__body">
      <span class="cf-subdiv__title">Wir hören zu, bevor wir messen</span>
      <span class="cf-subdiv__meta">Workshop · 2 Wochen</span>
    </span>
  </a>
  <!-- three to six cells; nothing else is authored -->
</nav>
There are no custom properties in that markup, on purpose. Depth is read off the DOM — :has(> .cf-subdiv__cell:nth-child(5):last-child) sets --subdiv-n — so the count cannot drift from the number of cells the way a hand-written --subdiv-count can. Index comes from :nth-child. The only number that moves is the head, and it is one inherited value on the container. Add or delete a cell and the series re-forms correctly with no other edit. Compare .subdivide, where --rank and --subdivide-count are both hand-set: that is right for a layout, where the ranks encode editorial fact, and wrong here.

The rule, and why it is this rule

The halving series never changes: 1/2, 1/4, 1/8 … with the last two ranks tied, which is what makes the fractions sum to exactly one rather than to one minus the tail. What changes is who stands at its head. The head takes rank 0; every other cell keeps its position in the row and takes the next rank in markup order.

HeadWidths, five cellsWhere the rules stand
cell 1 (rest)16 · 8 · 4 · 2 · 2the pure plate
cell 28 · 16 · 4 · 2 · 2rules 3, 4, 5 unmoved
cell 38 · 4 · 16 · 2 · 2rules 4, 5 unmoved
cell 48 · 4 · 2 · 16 · 2rule 5 unmoved
cell 58 · 4 · 2 · 2 · 16

Three properties fall out of that one rule, and together they are the argument for it over the obvious alternative — rotating the series so the head always sits in the widest slot.

1. The cells never reorder. Reading order is markup order at every state. A menu whose items change places while you look at it is not a menu.

2. The head only ever grows around the pointer. Cell k at the head takes s1 while cells 1..k-1 take s2..sk; the series is non-increasing, so sum(s2..sk) ≤ sum(s1..sk-1) and the head's leading edge moves left and never right. Its box strictly contains the box it had, so the pointer that summoned the cell is still inside it. The rotation fails exactly here. Measured in Chromium at 1024 px: hovering cell 4 under a rotating series moves that cell from [896, 960] to [256, 768] — it leaves the pointer entirely, the cursor lands on cell 5, and the field jumps a second time. Under this rule the same cell goes from [896, 960] to [448, 960]. A layout that moves out from under the pointer that summoned it is a trap; this one cannot be one.

3. The rules ahead of you do not move. Cells after the head keep their resting weights exactly, so every hairline from the head rightwards stands where it stood at rest. Only the rules behind your attention slide. That is what makes a field of six moving columns legible rather than busy: at any moment at most half the drawing is in motion, and it is the half you have already read.

Written in CSS the rank is four lines, because CSS has no conditional and clamp(0, x, 1) is the sign of an integer:

--subdiv-after:  clamp(0, var(--subdiv-h) - var(--subdiv-i), 1);   /* 1 if this cell precedes the head */
--subdiv-before: clamp(0, var(--subdiv-i) - var(--subdiv-h), 1);
--subdiv-eq:     calc(1 - var(--subdiv-after) - var(--subdiv-before));  /* 1 only at the head */
--subdiv-r:      calc((var(--subdiv-i) + var(--subdiv-after)) * (1 - var(--subdiv-eq)));

flex-grow: pow(2, calc(var(--subdiv-n) - 1 - min(var(--subdiv-r), var(--subdiv-n) - 2)));

The min() is the tie between the last two ranks. Swept in Chromium against the shipping CSS — every depth from three to six, every head at each depth, 18 states — inside a 1024 px field: the cells divide the field's 1023 px content box (the missing pixel is its right contour) into 511.5 / 255.75 / 127.875 / 63.938 / 31.969, exact halves at every rank, summing to the content box with no remainder in any state. The containment property in point 2 was measured separately and holds for every cell at every head.

Depth

Three to six cells, the same range .subdivide covers. Below three there is no series; above six the tail cell is 1/32 of the field and the tick stops fitting. Nothing is set to choose the depth — the field counts its own children.

This is the application plate's own subject — a blog index divided by recency — with the difference that the plate fixes the hierarchy and the field lets the reader move it. Four cells here rather than five, and neither the CSS nor the markup was told.

Anatomy

PartWhat it is
.cf-subdiv The field. Carries top, right and bottom edges; the left edge belongs to the first cell. Sets --subdiv-height (14rem) and holds the head as --subdiv-h.
.cf-subdiv__cell One cell, and a real link. Draws its own leading rule as ::before, so the rules are cell-owned and never doubled.
.cf-subdiv__cell::after The light. The head's contour drawn in --gradient-foil-ink, faded in and out by rank rather than switched by a selector.
.cf-subdiv__tick Geist Mono, vertical, standing on the leading rule. Visible at every rank — this is the cell's permanent name.
.cf-subdiv__title Visible at ranks 0 and 1.
.cf-subdiv__meta Mono, visible at rank 0 only.
--subdiv-height The one thing worth overriding. 14rem by default; 9rem reads as a bar rather than a board.

Where it sits in the six layers

Base wash, then contour, then light, then text — no opaque layer and no glass. The field is drawn, not filled, which is the default for anything structural in this system, and there is nothing complicated underneath it for glass to calm.

The light enters the contour rather than sitting on it. The head's leading rule is painted with --gradient-foil-ink — the foil's shadow half — instead of ink. This is the one place in the six-layer stack the light layer and the contour layer are allowed to be the same pixel, and it is what makes the light appear to travel along the field as attention moves. It is the ink half because the field sits on the page wash, where the lit half of the foil measures 1.1–1.5:1.

It is never lime. #E1FF00 on CF-Grau is 1.4:1 and a contour has to clear 3:1 on its own. This is the same reasoning that put the foil rather than the lime ramp in the black button's label: the foil exists precisely so colour can enter a mark without spending the screen's one lime moment.

Behaviour, support and the ways out

PathWhat the reader gets
Pointer Hover promotes the cell. The head's box always contains the box it had, so the pointer cannot fall out of the cell it opened.
Keyboard Identical. :focus-visible is in the same :is() as :hover, so tabbing through the field opens each cell exactly as pointing at it does, and the focus ring is the system's own.
Touch The resting field, and a tap follows the link. Every cell is named by its tick at rest, so nothing is reachable only by hovering.
Screen reader A <nav> of links. Copy is faded with opacity, never display, so a cell's title is part of its link name at every rank — the accessible name does not depend on the visual state.
Reduced motion Every state, no transition. The promotion still happens; it arrives instantly. Motion is opted into under prefers-reduced-motion: no-preference, the way the rest of the system does it.
No :has() or no pow() Equal cells, all copy visible — the manual's other form, .subdivide--even, rather than a degradation. The whole system is inside one @supports selector(:has(*)) and (flex-grow: pow(2, 3)).
Under 44rem of its own width The field folds to a stack: equal cells, every cell open, ticks horizontal, leading rules become top rules. A container query, not a media query — the same field is comfortable in 900 px of page and impossible in 900 px of viewport behind a 272 px sidebar.
Print, forced colours Everything, unweighted and unfaded.
Why flex-grow and not grid-template-columns. A grid would put the whole track set on the container, which means one declaration per head per depth — sixteen of them for depths three to six — where the flex form is one declaration on the cell and a number that inherits. flex-grow is also a plain number, so it interpolates: the field can transition where a track list cannot.

Why nothing animates to an intrinsic size. interpolate-size: allow-keywords and calc-size() would be the obvious modern way to open a cell to its content width, and they are still Chromium-only — not Baseline in Firefox or Safari. A field whose proportions only resolve in one engine is not a system. The series is unitless and needs neither.

What transitioning flex-grow actually costs. It reflows the field every frame — flex-grow is not composited, and no amount of it is. It is affordable here because the field is one flex line of at most six items and the copy inside them is faded rather than re-wrapped, so the reflow is a handful of boxes and no text layout. This is not a licence to transition flex-grow on a page-level layout.

Do and don't

DoDon't
Use it where the items have a natural order and unequal weight — a process, an archive by recency, a set of chapters. Use it for items of equal rank. A subdivision claims a hierarchy; four services that matter equally should be four equal cells, and that is .subdivide--even.
Give every cell a tick short enough to read vertically in a 1/16 cell — two or three words. Let the tick wrap. It is white-space: nowrap, so a long one will overflow its cell rather than fold.
Let the resting head be the cell you would have made largest anyway. It is always the first one. Reorder the markup to change what opens first. Markup order is reading order, and the reader will find the rest.
Keep it to one per page. Put two fields in one section. Two of them re-proportioning near each other is the busiest thing this system can draw.
Let the cells be links or buttons — something focus lands on. Make the cells <div>s and drive the field from hover alone. That is a menu no keyboard can open.
Nest it in nothing. It is a band, full width of its column. Put it inside the 12-column grid alongside content. The subdivision orders quantity; the 12-column grid orders content; the two are never nested.

Still open