jixoai-ui
the terminal design language
loading the style sheet…
Skip to content
docs · print medium: screen

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).

install — stack

$npx jixoai-ui add stack

https://ui.jixoai.com/r/stack.json

16 gap rungs · --jx-space-N closed CSS axis vocabulary structural props · never-ambient omission transparency

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.

one two three
stack-usage.svelte svelte
<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

gap 4 — var(--jx-space-4)
cell cell cell
gap 12 — var(--jx-space-12)
cell cell cell
gap 24 — var(--jx-space-24)
cell cell cell
gap 48 — var(--jx-space-48)
cell cell cell
stack-gap-demo.svelte svelte
<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.

PropertyTypeDefaultDescription
structural, not style direction · gap · align · justify · wrap · inlinethe 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 nameNOTHING — 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-* · handlersthe 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 followannounces: 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 elseno 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 maintainannounces: 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

justify between — edge packing
title actions
align baseline — text rows
Aa baseline peer chip
stack-axes-demo.svelte svelte
<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

wrap — the chip cloud
tag 1tag 2tag 3tag 4tag 5tag 6tag 7tag 8tag 9tag 10tag 11tag 12tag 13tag 14
stack-wrap-demo.svelte svelte
<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.

Stack usage
<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

PropertyTypeDefaultDescription
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 booleanfalseAllow wrapping onto multiple lines (flex-wrap: wrap).
inline booleanfalseInline flow (display: inline-flex) instead of block flow.
children Snippet—The flow’s contents.
…rest HTMLAttributes<HTMLDivElement>spreadEvery other attribute lands on the div — consumer attributes replace, never merge.

Universal props

PropertyTypeDefaultDescription
size'small' | 'medium' | 'large' | 'auto' | number'auto' · ambient scopethe base scale — root font-size; parts size in em
shape'round' | 'scoop' | 'bevel' | 'notch' | 'square' | 'squircle' | 'auto''auto' · ambient scopecorner geometry (CSS corner-shape; §14 degrade table)
radius'small' | 'medium' | 'large' | 'auto' | number'auto' · ambient scopecorner size; auto = the concentric broadcast (§3)
density'small' | 'medium' | 'large' | 'auto' | number'auto' · ambient scopespacing/leading scale over the kernel channels (§4)
color'primary' | 'secondary' | 'error' | 'warn' | 'success' | 'info' | 'auto' | number | string'auto' · ambient scopethe hue axis of the fixed oklch primary system (§5)
theme'light' | 'dark' | 'system' | 'auto''auto' · ambient scopelight/dark profile; system = the JS-mutable global (§6)
elevation'level-1' | 'level0' | 'level1' | 'level2' | 'level3' | 'level4' | 'level5' | 'auto' | number'auto' · ambient scopeofficial M3 levels over the surface ladder (§7)
motion'reduced' | 'subtle' | 'normal' | 'expressive' | 'auto' | number'auto' · ambient scopeintensity 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.

PropertyTypeDefaultDescription
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.
TokenDefaultSource
--jx-space-2 … 8016 rungscolor
data-jx-stackthe only hookcomponent
the merge lawcarriers + your stylestructural
rest-replacespread before stampstructural
omissionno atom stampedstructural

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-universal.svelte svelte
<Stack gap="8" size={18} density="small">…</Stack>
jixoai-ui ui.jixoai.com the jixoai design language

Theme

Brand hue

0°

Auto cycle · 24h