PRIMITIVE
Rule stack
A vertical list whose separators are the background showing through a one-pixel gap. The source design has seven of these, every one spelled out by hand.
Key and value
The commonest row in the system: a tracked key, a value hard right, both on one baseline. Run out of room and they wrap rather than collide.
<div class="rule-stack rule-stack-on-sunk" style="width:100%">
<div class="rule-row rule-row-spec">
<span class="rule-row-key">LABELS</span>
<span class="rule-row-value">Above, mono, .16em</span>
</div>
<div class="rule-row rule-row-spec">
<span class="rule-row-key">REQUIRED</span>
<span class="rule-row-value">Accent asterisk</span>
</div>
<div class="rule-row rule-row-spec">
<span class="rule-row-key">HINTS</span>
<span class="rule-row-value">Below, 11px</span>
</div>
</div>One column line for the whole stack
Add .rule-stack-aligned and every value starts at the same x β the column is as wide as the widest key, which is a width nobody had to pick.
<div class="rule-stack rule-stack-aligned" style="width:100%">
<div class="rule-row rule-row-spec">
<span class="rule-row-key">BASED IN</span>
<span class="rule-row-value">KrakΓ³w, and remote since 2019</span>
</div>
<div class="rule-row rule-row-spec">
<span class="rule-row-key">AVAILABILITY</span>
<span class="rule-row-value">Two days a week from March</span>
</div>
<div class="rule-row rule-row-spec">
<span class="rule-row-key">RATE</span>
<span class="rule-row-value">On request, and it depends on the work</span>
</div>
</div>Rows that are links
The whole row is the target. Tab to one and you will see the ring drawn inside it β an outset ring on a flush row gets clipped by its neighbours and comes out as two stubs down the sides.
<div class="rule-stack rule-stack-on-sunk" style="width:100%">
<a href="#" class="rule-row rule-row-spec">
<span class="rule-row-key">EMAIL</span>
<span class="rule-row-value">[email protected]</span>
</a>
<a href="#" class="rule-row rule-row-spec">
<span class="rule-row-key">GITHUB</span>
<span class="rule-row-value">@yourhandle</span>
</a>
</div>The same list with the box taken off
rule-stack-open draws real borders instead of letting a painted container show through a gap β take the fill away and the paint-through trick has nothing left to show. Reach for it when the list IS the section rather than a panel inside it: a column of questions, a set of notes beside a heading. It is asking for --ds-pad-row-lg here, because with no frame around them the space is the only thing keeping one row apart from the next.
Is it really free?
Yes, for personal use, with no trial timer and no locked features. There is no account to make and nothing to enter a card into.
Does it change anything on my machine?
No. It runs from the file you downloaded, and nothing is written outside it apart from your own saved work.
Do I need to be online?
Only to download it. After that there is never a connection to make, and none is opened on its own.
<!-- The box taken off. Lines between the rows and one under the last, drawn on
whatever ground the list is standing on. A question and its answer per row,
which is the shape this was written for. -->
<div class="rule-stack rule-stack-open" style="--rule-row-pad: var(--ds-pad-row-lg)">
<div class="rule-row flow flow-tight">
<h3 class="feature-cell-title">Is it really free?</h3>
<p class="feature-cell-body">Yes, for personal use, with no trial timer and no locked features. There is no account to make and nothing to enter a card into.</p>
</div>
<div class="rule-row flow flow-tight">
<h3 class="feature-cell-title">Does it change anything on my machine?</h3>
<p class="feature-cell-body">No. It runs from the file you downloaded, and nothing is written outside it apart from your own saved work.</p>
</div>
<div class="rule-row flow flow-tight">
<h3 class="feature-cell-title">Do I need to be online?</h3>
<p class="feature-cell-body">Only to download it. After that there is never a connection to make, and none is opened on its own.</p>
</div>
</div>Three columns, and a row that ends in a sentence
--rule-columns is the aligned stack's track list, and two columns is only its default. A release row is version, date, and what changed β the rows needed nothing at all, because subgrid already spans every column there is. The third cell is a rule-row-note: a SENTENCE, in the body face, where a key and a value are both label faces you scan rather than read. Same distinction section-head draws between its note and its lede.
Vector pane treatments: hatched, solid colour and paper, with per-line colour and width.
Free panel layouts. Dock, float or close anything, and keep the arrangement between sessions.
Image import, palette library, and per-layer Z offset.
<!-- Three columns rather than two. `--rule-columns` is the track list, and the
rows needed nothing: `subgrid` already spans every column there is.
The third cell is a `rule-row-note` β a SENTENCE, in the body face, where a
key and a value are both label faces you scan rather than read. -->
<div class="rule-stack rule-stack-open rule-stack-aligned" style="--rule-columns: auto auto 1fr; width:100%">
<div class="rule-row">
<span class="rule-row-value">v0.9.0</span>
<span class="rule-row-key">12 Aug 2026</span>
<p class="rule-row-note">Vector pane treatments: hatched, solid colour and paper, with per-line colour and width.</p>
</div>
<div class="rule-row">
<span class="rule-row-value">v0.8.0</span>
<span class="rule-row-key">3 Jun 2026</span>
<p class="rule-row-note">Free panel layouts. Dock, float or close anything, and keep the arrangement between sessions.</p>
</div>
<div class="rule-row">
<span class="rule-row-value">v0.7.0</span>
<span class="rule-row-key">19 Mar 2026</span>
<p class="rule-row-note">Image import, palette library, and per-layer Z offset.</p>
</div>
</div>Reference
Read from primitives/rule-stack.css when this page was built, so it cannot fall behind the stylesheet.
.rule-stack.rule-row.rule-stack-on-sunk.rule-stack-open.rule-row-spec.rule-stack-aligned.rule-row-value.rule-row-key.rule-row-note--rule-row-pad β falls back to --ds-pad-row--ds-focus-ring-inset--ds-focus-width--ds-font-body--ds-font-display--ds-gap-loose--ds-gap-tight--ds-ground--ds-hairline--ds-ink--ds-ink-faint--ds-ink-soft--ds-motion-fast--ds-radius--ds-raised--ds-rule--ds-sunk--ds-text-meta--ds-text-small--ds-text-ui--ds-track-keyNotes
Because this shape gives a closed outer frame for free, never double-draws between rows, and survives rows being reordered, filtered or conditionally rendered β where a :last-child rule ends up pointing at whichever element happens to be last today.
One thing to get right: a row must set a background, and it must be the surface the stack sits on. Use .rule-stack-on-sunk inside a sunk section. Getting this wrong is the most common misuse β the list reads as inset when it should be flush.
Subgrid, and it is the only thing that does this. A row has to stay a real box β it paints a surface, carries padding, lifts on hover and draws its focus ring inside itself β so it cannot be display: contents and hand its children up to the stack. Subgrid lets it keep all of that and still put its children on the stack’s column lines. The first column is auto: as wide as the widest key, which is a width nobody had to pick, and it gives ground towards min-content rather than pushing anything out of the box.
One trap, and you will not guess it from the CSS. A row that declares container-type cannot be a subgrid at all β containment forces an independent formatting context, so subgrid quietly computes to none. We watched the stack report four columns of 0px 0px 422px 0px with every rule correct in the build. If your row shape is a container, hand the container to the stack; the ledger does exactly that.