registry:ui · Layout
stack — flow over the typed space ladder
The Layout family's FLOW primitive: one-dimensional arrangement (row/column) over the site's typed space ladder — every gap is a --jx-space-N rung (token-bound stylex atoms, 16 rungs 2..80), the axis vocabulary is the closed CSS set (align start/center/end/baseline/stretch; justify start/center/end/between/stretch). A Stack paints NOTHING else — layout only. Props are structural (never-ambient; no Defaults contract — outside the context gate's paint vocabulary by classification).
$npx jixoai-ui add stack
https://ui.jixoai.com/r/stack.json
overview
Overview
The Layout family's flow primitive: one-dimensional arrangement (row/column) over the site's typed space ladder. A Stack is not a new layout engine — it is the sheet's own ladder with a component face, and it paints nothing else.
Name layout intent, get flex: direction and gap are the working pair, align / justify / wrap / inline refine. Every prop is STRUCTURAL — layout intent, not paint
vocabulary — and every one is optional: an omitted prop stamps no
atom at all (row rides flex's own default), so the DOM carries
only what you named. The gap is never a free pixel: each rung is
a token-bound atom over the sheet's --jx-space-N ladder, sixteen steps from 2 to 80, so spacing composes with the
rest of the system instead of drifting per-surface.
Two contracts hold the component's shape. The structural face is
never-ambient — an axis would have nothing meaningful to inherit
(a row is a row because its consumer says so) — so
direction/gap/align/justify/wrap/inline stay outside the defaults
contract exactly as founded. The paint face is the eight-axis
surface, all no-own: an explicit lane stamps the §10 carriers
onto the style attribute JOINED with your own declarations (the
merge law), and the scope flows to your children — one size=18 moves the whole stack's voice. The
component itself paints nothing: no color, no padding, no
typography; children keep their semantics wholesale and the
reading order is the DOM order (no order/reverse props by
design).
stack
One row/column, three cells. The playground swaps the rung and the axes live — the drawer's usage file tracks every pick.
<script lang="ts">
import Stack from '@ui/stack';
</script>
<Stack direction="row" gap="12">
<div>one</div>
<div>two</div>
<div>three</div>
</Stack>token-bound
The gap ladder — 16 rungs
Gap is not a free number: every rung is a token-bound atom (gap4 → var(--jx-space-4)), so spacing composes with the rest of the system instead of drifting per-surface. The ladder runs 2 · 4 · 6 · 8 · 10 · 12 · 14 · 16 · 18 · 20 · 24 · 28 · 32 · 40 · 48 · 80.
stack · gap ladder
<script lang="ts">
import Stack from '@ui/stack';
</script>
<div class="flex flex-col gap-3">
<Stack gap="4" class="border border-border p-2"><span class="bg-muted px-2 py-1 text-[12px]">gap 4</span><span class="bg-muted px-2 py-1 text-[12px]">·</span></Stack>
<Stack gap="12" class="border border-border p-2"><span class="bg-muted px-2 py-1 text-[12px]">gap 12</span><span class="bg-muted px-2 py-1 text-[12px]">·</span></Stack>
<Stack gap="24" class="border border-border p-2"><span class="bg-muted px-2 py-1 text-[12px]">gap 24</span><span class="bg-muted px-2 py-1 text-[12px]">·</span></Stack>
</div>law
The flow primitive
Structural props name intent and stay never-ambient; omission is transparency; the gap is always a ladder rung; your attributes replace, never merge — and the stack paints nothing.
| Property | Type | Default | Description |
|---|---|---|---|
| structural, not style | direction · gap · align · justify · wrap · inline | the props name LAYOUT INTENT, never paint — classified never-ambient and outside the context gate's vocabulary: an axis would have nothing meaningful to inherit (a row is a row because its consumer says so) | announces: the structural face stays outside the eight-axis contract |
| omission transparency | a prop you did not name | NOTHING — every atom is opt-in: row is flex's default (no direction atom exists), an omitted align stamps no align-items, an omitted gap stamps no gap (measured: the bare stack computes align-items: normal, gap: normal) | announces: the DOM carries only what you named |
| the token-bound gap | gap="4" … "80" | sixteen rungs, each a var(--jx-space-N) atom — spacing is never a free pixel, it composes with the sheet ladder the rest of the system reads (measured: gap="48" computes the --jx-space-48 rung) | announces: spacing is system spacing |
| rest before stamp | your id · data-* · handlers | the spread lands BEFORE the component's own attributes — consumer attributes REPLACE, never merge (the layout-family law); data-jx-stack, the density rung and the carriers follow | announces: your attributes win; the stamps cannot be contested |
| the paint half | the eight axes (W3-D3) | all no-own, first-time: an explicit lane stamps the §10 carriers onto the style attribute — JOINED with your own declarations (the merge law, the prototype-flex posture); a Stack paints nothing itself (source receipt: the atom table holds display/flex/gap declarations only) | announces: supply for the children, paint for nobody |
| layout only | everything else | no color, no padding, no typography — children keep their semantics wholesale and the reading order IS the DOM order (no order/reverse props by design). A stack is a plain div carrying layout, nothing to maintain | announces: transparent layout |
postures
Postures — the closed CSS vocabulary
align is the cross axis (start · center · end · baseline · stretch — flex's stretch default rides omission), justify is the main axis (start · center · end · between · stretch). No invented words, no numeric weights — and wrap/inline flip the flow itself.
stack · axes
<script lang="ts">
import Stack from '@ui/stack';
</script>
<div class="flex flex-wrap items-start gap-6">
<div class="border border-border p-3 flex-1">
<span class="font-nav text-primary mb-2 block text-[11px] uppercase tracking-[0.24em]">justify between — edge packing</span>
<Stack justify="between" class="w-full">
<span class="pill">title</span>
<span class="pill">actions</span>
</Stack>
</div>
<div class="border border-border p-3 flex-1">
<span class="font-nav text-primary mb-2 block text-[11px] uppercase tracking-[0.24em]">align baseline — text rows</span>
<Stack gap="8" align="baseline">
<span class="text-xl">Aa</span>
<span class="text-[12px]">baseline peer</span>
<span class="pill">chip</span>
</Stack>
</div>
</div>stack · wrap
<script lang="ts">
import Stack from '@ui/stack';
</script>
<Stack gap="8" wrap>
{#each Array(14) as _, i}
<span class="pill">tag {i + 1}</span>
{/each}
</Stack>usage
Usage
Name layout intent, get flex — direction and gap are the working pair; align/justify/wrap/inline refine. Omitted props add nothing at all.
<script lang="ts">
import Stack from '@ui/stack';
</script>
<!-- a column with a typed rung -->
<Stack direction="column" gap="12">
<header>…</header>
<main>…</main>
<footer>…</footer>
</Stack>
<!-- a centered row of actions -->
<Stack gap="8" align="center">
<button>save</button>
<button>cancel</button>
</Stack>
<!-- toolbar distribution: pack to the edges -->
<Stack justify="between">
<span>title</span>
<span>actions…</span>
</Stack>a11y
Accessibility
A stack is a plain div carrying layout only — no role, no announcements, nothing to maintain. Reading order is DOM order; the component never reorders visually (no order/reverse props by design).
No ARIA surface: the stack is transparent layout. Children keep their own semantics wholesale — a button inside a stack is still a button, a list is still a list — and because there is no order/reverse prop, the visual order can never diverge from the reading order the accessibility tree reports.
api
API
Seven structural props plus the HTML rest — all optional, all never-ambient (no Defaults contract: an axis has nothing meaningful to inherit; a row is a row because its consumer says so).
Properties
| Property | Type | Default | Description |
|---|---|---|---|
| direction | 'row' | 'column' | 'row' | The flow axis. Row is flex’s default — no atom is stamped. |
| gap | '2' | '4' | '6' | '8' | '10' | '12' | '14' | '16' | '18' | '20' | '24' | '28' | '32' | '40' | '48' | '80' | — (none) | The typed space ladder rung (token-bound; the sheet’s --jx-space-N). Omitted = no gap at all. |
| align | 'start' | 'center' | 'end' | 'baseline' | 'stretch' | — (stretch) | Cross-axis alignment (align-items). Omission leaves flex’s own stretch default — omission transparency. |
| justify | 'start' | 'center' | 'end' | 'between' | 'stretch' | — | Main-axis distribution (justify-content). |
| wrap | boolean | false | Allow wrapping onto multiple lines (flex-wrap: wrap). |
| inline | boolean | false | Inline flow (display: inline-flex) instead of block flow. |
| children | Snippet | — | The flow’s contents. |
| …rest | HTMLAttributes<HTMLDivElement> | spread | Every other attribute lands on the div — consumer attributes 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 stack
The paint half, W3-D3 first-time, all no-own: an explicit lane stamps the §10 carriers onto the style attribute — JOINED with your own declarations — and the scope flows to your children. The structural props stay outside this contract exactly as founded.
| Property | Type | Default | Description |
|---|---|---|---|
| density | 'small' | 'medium' | 'large' | 'auto' | number (+ the five legacy spellings) | 'auto' | STAMP-ONLY, RUNG — the resolved lane stamps the §4 legacy rung on the stack root (measured: density="small" → data-density="sm"), the SCOPE your children flow through; the stack itself reads no channel. Number unit: coefficient. |
| size | 'small' | 'medium' | 'large' | 'auto' | number | 'auto' | STAMP-ONLY, VOICE — the §11 pair (--jx-size-effective + font-size) lands on the root (measured 18px computed on the size demo) and the children inherit it through plain cascade — one number moves the whole stack's TYPE; the gap rungs stay rem-of-document-root and do NOT follow it (mutation-proven: stamp 18px keeps the 8px gap; document root 16→32px moves it 8→16px). Number unit: px. |
| shape | 'round' | 'scoop' | 'bevel' | 'notch' | 'square' | 'squircle' | 'auto' | 'auto' | SUPPLY-ONLY — the alias + §14 radius factor land on the root; the stack paints no corner (source receipt: the atom table holds display/flex/gap declarations only). Number unit: none. |
| radius | 'small' | 'medium' | 'large' | 'auto' | number | 'auto' | SUPPLY-ONLY — --jx-radius-effective lands on the root for what you compose inside (a bordered child can consume it); the stack draws no border of its own. Number unit: px. |
| color | 'primary' | 'secondary' | 'error' | 'warn' | 'success' | 'info' | 'auto' | number | string | 'auto' | SUPPLY-ONLY — --jx-color-effective lands on the root; the stack's ink is its children's ink (paint belongs to the children, the hero's own law). Number unit: hue degrees. |
| theme | 'light' | 'dark' | 'system' | 'auto' | 'auto' | BRIDGE — class:dark lands on the root (measured) and re-voices the token scope your children read; the layout itself is invisible to it. No number lane. |
| elevation | 'level-1' | 'level0' | 'level1' | 'level2' | 'level3' | 'level4' | 'level5' | 'auto' | number | 'auto' | SUPPLY-ONLY — the §7 pair stamps on the root; the stack owns no shadow (layout sits IN the plane). Compose it with a surface child that reads the supply. Number unit: dp. |
| motion | 'reduced' | 'subtle' | 'normal' | 'expressive' | 'auto' | number | 'auto' | SUPPLY-ONLY — the carrier lands on the root; the flow has no choreography of its own (no transitions in the atom table — grep receipt), so the lane is pure scope for motion-reading children. Number unit: coefficient. |
| Token | Default | Source |
|---|---|---|
| --jx-space-2 … 80 | 16 rungs | color |
| data-jx-stack | the only hook | component |
| the merge law | carriers + your style | structural |
| rest-replace | spread before stamp | structural |
| omission | no atom stamped | structural |
Stack · universal props
one number moves the stack
children inherit the root font-size
named steps resolve through the alias ladder
the merge law: carriers and your style in one attribute
the bare posture: nothing named, nothing stamped
<Stack gap="8" size={18} density="small">…</Stack>