registry:ui · Layout · alpha
prototype-waterfall — the standardized masonry
The layout family, alpha track, on the CSS multicol engine: the columns shorthand passes through verbatim — a number is the count form, a length string is the auto-width floor — with column-fill: balance (browser-equalized heights) and column-gap. The tradeoffs are declared, not hidden: children flow in column (newspaper) order, and break-inside stays the consumer's call.
$npx jixoai-ui add prototype-waterfall
https://ui.jixoai.com/r/prototype-waterfall.json
overview
Overview
The trio's third member: masonry on the multicol engine — two columns forms, one strategy seam, and the tradeoffs said out loud.
The three alpha laws carry over unchanged: SINGLE ROOT + REST SPREAD (one div;
consumer attributes first, family stamps after), ZERO TRANSLATION (the columns shorthand IS the CSS — a number is the
count form, a length string ('14rem') is the auto-width FLOOR where the count follows
the viewport; one property covers both shapes with no branching), and INLINE STYLE
ONLY (no utilities, no theme tokens, no css file). There is NO JavaScript in the
family — no resize observation, no absolute positioning, no layout-settling timer:
the browser's column balancer is the engine, so reflow on any control change is
immediate and native (measured in the task-37 probe: each rig change settles by
the first animation frame — the same-task read is stale at Svelte's flush boundary).
The declared tradeoffs (honest, not hidden): children flow in COLUMN order — down
column one, then column two, newspaper order, not shortest-column-first masonry —
and break-inside stays the consumer's call (the
component never overrides it; pass style="break-inside: avoid" on cards you refuse to
split). The strategy prop ships with a single
member — 'balanced' → column-fill: balance, the browser equalizing column heights —
as the seam for a future 'ordered' (JS-measured shortest-column placement) to join
the union non-breakingly.
The eight axes are forwarders as on the trio's first two members: all no-own, carriers in the consumer style attr, density stamping its rung for descendants, theme riding the .dark bridge. A waterfall-specific nuance measured below: BOTH column forms are §11-stamp-scale-blind — the rem floor reads the DOCUMENT root, not the element stamp (stamp 18px → the 224px floor unchanged; document root 16→20px → 280px; two-direction probe). Reading order is coherent — the browser fills column one first, so announced order matches the visual column sequence — but content is segmented: a card straddling a column break is split unless you forbid it.
prototype-waterfall · the rig
The property panel, live: the columns shorthand in both forms plus gap — measured end-to-end in task 37 against the container's resolved column geometry (computed column-count / column-width, and the laid-out count from where the cards land).
- columns form
- gap (px)
the count form fixes the columns; the "14rem" form fixes the WIDTH and lets the count follow the viewport (the masonry floor — measured 3 columns at 1400px, fewer as it narrows). Cards carry break-inside: avoid — a card never splits across a column break.
<PrototypeWaterfall
columns={3}
gap={16}
strategy="balanced"
>
<!-- nine variable-height cards; break-inside is yours -->
</PrototypeWaterfall>law
The alpha contract + the multicol tradeoffs
The three trio laws hold verbatim; the waterfall adds the columns-shorthand duality and says its tradeoffs out loud — column order and the consumer's break-inside.
1 · SINGLE ROOT + REST SPREAD — one div, consumer attributes first, family stamps after. 2 · ZERO TRANSLATION — columns is the CSS columns shorthand (count form and auto-width form in one property, no branching); gap is column-gap; strategy is column-fill. 3 · INLINE STYLE ONLY — zero tokens, any host. The declared tradeoffs: children flow in column order (newspaper order — announced order matches the visual column sequence), and break-inside is never overridden by the component. The 'ordered' strategy (JS-measured shortest-column placement) is the recorded future seam — the prop ships now so the union can grow without breaking v0 consumers.
The layout trio, cross-referenced: prototype-flex — the row prototype-grid — the tracks prototype-waterfall — the columns — one contract, three engines.
types
Types — the columns forms, rendered
The shorthand's two shapes on identical card decks: the count form fixes the columns; the auto-width form fixes the width and lets the count follow — the masonry floor.
prototype-waterfall · columns forms
<PrototypeWaterfall columns={3} gap={16}>
{#each cards as card (card.id)}
<figure style="break-inside: avoid;">…</figure>
{/each}
</PrototypeWaterfall>
<!-- the auto-width form: a column LENGTH floors the width, count follows -->
<PrototypeWaterfall columns="14rem" gap={16}>…</PrototypeWaterfall>usage
Usage
Pass a count or a length; add break-inside: avoid to the cards you refuse to split.
<PrototypeWaterfall columns={3} gap={16}>
{#each cards as card (card.id)}
<figure style="break-inside: avoid;">…</figure>
{/each}
</PrototypeWaterfall>
<!-- the auto-width form: a column LENGTH floors the width, count follows -->
<PrototypeWaterfall columns="14rem" gap={16}>…</PrototypeWaterfall>api
API
One shorthand, one gap, one strategy seam — everything else rides through as native div attributes.
Properties
| Property | Type | Default | Description |
|---|---|---|---|
| columns | number | string | — | The CSS columns shorthand, verbatim: number = the count form; a length string ("14rem") = the auto-width floor — the count follows the viewport. |
| gap | number | string | — | column-gap: number → px, string verbatim. |
| strategy | 'balanced' | — | v0's single member → column-fill: balance (browser-equalized heights). 'ordered' (JS-measured shortest-column placement) is the recorded future seam — the prop ships now for it. |
| class | string | — | Passed through verbatim — the component owns no class of its own. |
| ...rest | HTMLAttributes<HTMLDivElement> | spread | Every other attribute lands on the single root (stamped data-jx-prototype-waterfall after the spread — replace, never merge). |
Universal props
| Property | Type | Default | Description |
|---|---|---|---|
| size | 'small' | 'medium' | 'large' | 'auto' | number | 'auto' · ambient scope | the base scale — root font-size; parts size in em |
| shape | 'round' | 'scoop' | 'bevel' | 'notch' | 'square' | 'squircle' | 'auto' | 'auto' · ambient scope | corner geometry (CSS corner-shape; §14 degrade table) |
| radius | 'small' | 'medium' | 'large' | 'auto' | number | 'auto' · ambient scope | corner size; auto = the concentric broadcast (§3) |
| density | 'small' | 'medium' | 'large' | 'auto' | number | 'auto' · ambient scope | spacing/leading scale over the kernel channels (§4) |
| color | 'primary' | 'secondary' | 'error' | 'warn' | 'success' | 'info' | 'auto' | number | string | 'auto' · ambient scope | the hue axis of the fixed oklch primary system (§5) |
| theme | 'light' | 'dark' | 'system' | 'auto' | 'auto' · ambient scope | light/dark profile; system = the JS-mutable global (§6) |
| elevation | 'level-1' | 'level0' | 'level1' | 'level2' | 'level3' | 'level4' | 'level5' | 'auto' | number | 'auto' · ambient scope | official M3 levels over the surface ladder (§7) |
| motion | 'reduced' | 'subtle' | 'normal' | 'expressive' | 'auto' | number | 'auto' · ambient scope | intensity across the motion kernels (§8) |
axes
The eight axes on prototype-waterfall
The zero-translation posture extends to paint: the first-time contract is ALL NO-OWN, and the family paints nothing for any axis — carriers join the consumer style attr, density stamps its rung for descendants, theme rides the .dark bridge. One measured nuance: both column forms are stamp-scale-blind — the rem floor reads the document root (stamp 18px keeps the 224px floor; root 16→20px moves it to 280px).
| Property | Type | Default | Description |
|---|---|---|---|
| density | '2xs' | 'xs' | 'sm' | 'default' | 'lg' | 'auto' | number (+ the five legacy spellings) | 'auto' | FORWARDER — the resolved rung stamps data-density on the waterfall root (measured data-density=lg explicit; null ambient), opening the kernel channels for DESCENDANTS; the column geometry consumes none. Number unit: coefficient. |
| size | 'small' | 'medium' | 'large' | 'auto' | number | 'auto' | FORWARDER, ECHO MEASURED — the §11 stamp lands inline as font-size: var(--jx-size-effective, 1rem) (measured in the style attr; computed 18px at size={18}); the stamp is ELEMENT-LEVEL — column floors do NOT follow it ('14rem' reads the document root: stamp 18px keeps the 224px floor; document root 16→20px moves it to 280px — both forms stamp-scale-blind, two-direction probe). Number unit: px. |
| shape | 'round' | 'scoop' | 'bevel' | 'notch' | 'square' | 'squircle' | 'auto' | 'auto' | FORWARDER — carrier stamp only; zero family readers. Number unit: none. |
| radius | 'small' | 'medium' | 'large' | 'auto' | number | 'auto' | FORWARDER — carrier stamp only; zero family readers. Number unit: px. |
| color | 'primary' | 'secondary' | 'error' | 'warn' | 'success' | 'info' | 'auto' | number | string | 'auto' | FORWARDER — carrier stamp only; zero family readers. Number unit: hue degrees. |
| theme | 'light' | 'dark' | 'system' | 'auto' | 'auto' | FORWARDER — dark rides the .dark class bridge on the waterfall root (measured with theme="dark"), re-scoping token chains for the subtree; the column geometry is theme-blind. No number lane. |
| elevation | 'level-1' | 'level0' | 'level1' | 'level2' | 'level3' | 'level4' | 'level5' | 'auto' | number | 'auto' | FORWARDER — carrier stamp only; zero family readers. Number unit: dp. |
| motion | 'reduced' | 'subtle' | 'normal' | 'expressive' | 'auto' | number | 'auto' | FORWARDER — carrier stamp only; multicol reflow is the engine's own (grep receipt: zero transition declarations in the family — no authored reflow to time). Number unit: coefficient. |
Receipts: the rig's two controls measured end-to-end (columns 3 → 4 → the "14rem" floor; gap 24 → 24px column-gap) against the container's resolved geometry — computed column-count/column-width AND the laid-out count from where the cards land (distinct offsetLeft columns): the count form held 3 columns at every width; the "14rem" floor form laid 3 columns at 1400px and 2 at 760px as the viewport narrowed past the floors; the reflow settled by the first animation frame (the same-task read is stale at Svelte's flush boundary; no JS reflow to wait for) — task 37; the density rung stamp (data-density=lg explicit, null ambient), the size carrier (font-size: var(--jx-size-effective, 1rem), computed 18px at size={18}) and the dark class bridge were measured on this page's served DOM; the zero-reader rows carry grep receipts over ui/prototype-waterfall/ (an inline-style-only family — no css file, no tokens). The query() seat below rides the md viewport key (48rem) on the size LANE — the lanes-vs-passthroughs boundary applies: columns/gap/strategy reject query().
<script lang="ts">
import PrototypeWaterfall from '@ui/prototype-waterfall.svelte';
import { query } from '@lib/universal-props-query.svelte';
</script>
<!-- below 48rem the base (13px root) applies; at 48rem+ the md case
(18px) wins — element-level: the rem floor does NOT follow the stamp -->
<PrototypeWaterfall columns="14rem" size={query({ md: 18 }, 13)}>…</PrototypeWaterfall>prototype-waterfall · query()
Media keys are min-width: below 48rem the base (13px root) applies; at 48rem and wider the md case wins (18px) — and the column floor does NOT follow that stamp: the §11 stamp is element-level, the '14rem' floor reads the DOCUMENT root (two-direction probe: stamp 18px → the 224px floor unchanged; document root 16→20px → 280px). The number lane goes bare. Resize across 48rem and watch the TYPE move while the tracks hold.
<script lang="ts">
import PrototypeWaterfall from '@ui/prototype-waterfall.svelte';
import { query } from '@lib/universal-props-query.svelte';
</script>
<!-- below 48rem the base (13px root) applies; at 48rem+ the md case
(18px) wins — element-level: the rem floor does NOT follow the stamp -->
<PrototypeWaterfall columns="14rem" size={query({ md: 18 }, 13)}>…</PrototypeWaterfall>PrototypeWaterfall · universal props
<PrototypeWaterfall columns={3} size={18} density="small">…</PrototypeWaterfall>a11y
Accessibility
A layout primitive: no role, no keys, no announcements of its own — the multicol story is reading order (column flow preserves DOM sequence) and the split-card hazard.
Keyboard
| Key | Action |
|---|---|
| — | Not a focus stop and no keys of its own — a plain div wrapper |
ARIA
| Attribute | Value | Description |
|---|---|---|
| column reading order | DOM order preserved | The browser fills column one first, then column two — announced order matches the visual column sequence (newspaper order); narrower viewports reflow to fewer columns linearly. |
| break-inside | the consumer’s call | A card without break-inside: avoid can SPLIT across a column break mid-sentence — pass it on every card (the usage sample shows the idiom). |
| rest-spread ARIA | verbatim | aria-label, role, data-* — anything in the rest lane lands on the single root after the spread, so consumer semantics compose with the layout stamps. |
| role | none authored | The container is a plain div; give it a role only when its children need grouping semantics. |
| theme / density stamps | subtree-scoped | The .dark bridge and data-density rung re-scope token chains for descendants — visible only to children that read them. |