feat(eidos): port 8 layout primitives from air (box, flex, grid, stack, group, wrap, container, section)
Visual-only primitives translated from the legacy `air/components/layout`
on the morfo-runtime branch. Architectural adjustments for the eidos
target: drop the `air-` class prefix, target `[data-{name}]` directly,
rename CSS vars to `--{name}-*`, replace `var(--air-space-N)` with the
eidos foundation `var(--space-N)` token, resolve responsive props via
`ActiveEidos.resolve(prop)`.
- `box` — universal box-model utility (size, padding, margin, gap,
position, overflow, flex/grid item props). Every prop maps to a
CSS custom property; unset props fall through via `revert-layer`.
- `flex` / `grid` / `stack` / `group` / `wrap` — layout containers
that render through Box (single `<div>` per primitive, no
semantic-tag prop in this batch). Markers stack as
`[data-box][data-{name}]`.
- `container` — max-width centered (sm/md/lg/xl/full).
- `section` — semantic-padding block with background tone variants.
Shared helpers at `_layout/shared.ts` (formatLayoutSpace /
formatLayoutLength / formatLayoutRaw / pushStyleVar / composeStyle)
plus an optional `Layout.*` namespace barrel for consumers who prefer
that style; the individual `import { Box }` from each component dir
also works.
Each primitive ships a demo at `/uix/components/{name}/` with a live
example, a props table, and a reference comparison row (radix-themes /
chakra-ui / mantine).
svelte-check: 0 errors / 0 new warnings.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
5 months ago
< script lang = "ts" >
feat(layout): canon demos + morfos for the remaining 6 primitives
Replicates the box / flex canonical pattern across grid, stack, group,
wrap, container and section. Each gets:
- `src/uix/morfo/components/{name}.ts` — scope `eidos`, single Provider
part, no events. Header comment justifies the 0-event surface as a
passive container composing through Box / Flex.
- Rewritten demo following DEMO_AUTHORING_GUIDE: 6 tabs (Live · API ·
Morfo · Sema · Recipe · A11y), `Layout · {Name}` eyebrow, 4 meta
pills, permanent stage between header and tablist, trace strip,
MutationObserver wired to data-event, reactive soma + eidos snippets,
API tables grouped by concern, reference comparison vs radix-themes
/ chakra-ui / mantine.
Cross-references in every lede + API tab:
- Grid → Box (item placement) + Flex (flex containers)
- Stack → Flex (full API) + Group (row cluster)
- Group → Stack / Wrap / Flex
- Wrap → Flex (`wrap="wrap"`) + Group (nowrap)
- Container → Box (arbitrary maxWidth) + Section (block padding)
- Section → Box (non-semantic padding) + Container (max-width)
Demo sizes: grid 479L, stack 426L, group 458L, wrap 460L, container
415L, section 386L (vs 80-95L before — matches drawer/avatar canon
mass).
svelte-check: 0 errors, 0 new warnings.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
5 months ago
import { Stack , type StackProps , type StackDirection } from '$uix/eidos/components/stack';
feat(eidos): port 8 layout primitives from air (box, flex, grid, stack, group, wrap, container, section)
Visual-only primitives translated from the legacy `air/components/layout`
on the morfo-runtime branch. Architectural adjustments for the eidos
target: drop the `air-` class prefix, target `[data-{name}]` directly,
rename CSS vars to `--{name}-*`, replace `var(--air-space-N)` with the
eidos foundation `var(--space-N)` token, resolve responsive props via
`ActiveEidos.resolve(prop)`.
- `box` — universal box-model utility (size, padding, margin, gap,
position, overflow, flex/grid item props). Every prop maps to a
CSS custom property; unset props fall through via `revert-layer`.
- `flex` / `grid` / `stack` / `group` / `wrap` — layout containers
that render through Box (single `<div>` per primitive, no
semantic-tag prop in this batch). Markers stack as
`[data-box][data-{name}]`.
- `container` — max-width centered (sm/md/lg/xl/full).
- `section` — semantic-padding block with background tone variants.
Shared helpers at `_layout/shared.ts` (formatLayoutSpace /
formatLayoutLength / formatLayoutRaw / pushStyleVar / composeStyle)
plus an optional `Layout.*` namespace barrel for consumers who prefer
that style; the individual `import { Box }` from each component dir
also works.
Each primitive ships a demo at `/uix/components/{name}/` with a live
example, a props table, and a reference comparison row (radix-themes /
chakra-ui / mantine).
svelte-check: 0 errors / 0 new warnings.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
5 months ago
import { Box } from '$uix/eidos/components/box';
feat(layout): canon demos + morfos for the remaining 6 primitives
Replicates the box / flex canonical pattern across grid, stack, group,
wrap, container and section. Each gets:
- `src/uix/morfo/components/{name}.ts` — scope `eidos`, single Provider
part, no events. Header comment justifies the 0-event surface as a
passive container composing through Box / Flex.
- Rewritten demo following DEMO_AUTHORING_GUIDE: 6 tabs (Live · API ·
Morfo · Sema · Recipe · A11y), `Layout · {Name}` eyebrow, 4 meta
pills, permanent stage between header and tablist, trace strip,
MutationObserver wired to data-event, reactive soma + eidos snippets,
API tables grouped by concern, reference comparison vs radix-themes
/ chakra-ui / mantine.
Cross-references in every lede + API tab:
- Grid → Box (item placement) + Flex (flex containers)
- Stack → Flex (full API) + Group (row cluster)
- Group → Stack / Wrap / Flex
- Wrap → Flex (`wrap="wrap"`) + Group (nowrap)
- Container → Box (arbitrary maxWidth) + Section (block padding)
- Section → Box (non-semantic padding) + Container (max-width)
Demo sizes: grid 479L, stack 426L, group 458L, wrap 460L, container
415L, section 386L (vs 80-95L before — matches drawer/avatar canon
mass).
svelte-check: 0 errors, 0 new warnings.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
5 months ago
import { compileMorfo } from '$uix/morfo';
import { stackMorfo } from '@/uix/morfo/components/stack';
feat(eidos): port 8 layout primitives from air (box, flex, grid, stack, group, wrap, container, section)
Visual-only primitives translated from the legacy `air/components/layout`
on the morfo-runtime branch. Architectural adjustments for the eidos
target: drop the `air-` class prefix, target `[data-{name}]` directly,
rename CSS vars to `--{name}-*`, replace `var(--air-space-N)` with the
eidos foundation `var(--space-N)` token, resolve responsive props via
`ActiveEidos.resolve(prop)`.
- `box` — universal box-model utility (size, padding, margin, gap,
position, overflow, flex/grid item props). Every prop maps to a
CSS custom property; unset props fall through via `revert-layer`.
- `flex` / `grid` / `stack` / `group` / `wrap` — layout containers
that render through Box (single `<div>` per primitive, no
semantic-tag prop in this batch). Markers stack as
`[data-box][data-{name}]`.
- `container` — max-width centered (sm/md/lg/xl/full).
- `section` — semantic-padding block with background tone variants.
Shared helpers at `_layout/shared.ts` (formatLayoutSpace /
formatLayoutLength / formatLayoutRaw / pushStyleVar / composeStyle)
plus an optional `Layout.*` namespace barrel for consumers who prefer
that style; the individual `import { Box }` from each component dir
also works.
Each primitive ships a demo at `/uix/components/{name}/` with a live
example, a props table, and a reference comparison row (radix-themes /
chakra-ui / mantine).
svelte-check: 0 errors / 0 new warnings.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
5 months ago
feat(layout): canon demos + morfos for the remaining 6 primitives
Replicates the box / flex canonical pattern across grid, stack, group,
wrap, container and section. Each gets:
- `src/uix/morfo/components/{name}.ts` — scope `eidos`, single Provider
part, no events. Header comment justifies the 0-event surface as a
passive container composing through Box / Flex.
- Rewritten demo following DEMO_AUTHORING_GUIDE: 6 tabs (Live · API ·
Morfo · Sema · Recipe · A11y), `Layout · {Name}` eyebrow, 4 meta
pills, permanent stage between header and tablist, trace strip,
MutationObserver wired to data-event, reactive soma + eidos snippets,
API tables grouped by concern, reference comparison vs radix-themes
/ chakra-ui / mantine.
Cross-references in every lede + API tab:
- Grid → Box (item placement) + Flex (flex containers)
- Stack → Flex (full API) + Group (row cluster)
- Group → Stack / Wrap / Flex
- Wrap → Flex (`wrap="wrap"`) + Group (nowrap)
- Container → Box (arbitrary maxWidth) + Section (block padding)
- Section → Box (non-semantic padding) + Container (max-width)
Demo sizes: grid 479L, stack 426L, group 458L, wrap 460L, container
415L, section 386L (vs 80-95L before — matches drawer/avatar canon
mass).
svelte-check: 0 errors, 0 new warnings.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
5 months ago
type Tab = 'live' | 'api' | 'morfo' | 'sema' | 'recipe' | 'a11y';
type TraceEntry = { event : string ; family : string ; intent? : string ; at : number } ;
let tab = $state< Tab > ('live');
let trace = $state< TraceEntry [ ] > ([]);
let stageRef = $state< HTMLElement | null > (null);
// ── Live state — Stack container props ────────────────────────────────
let direction = $state< StackDirection > ('column');
let align = $state< 'stretch' | 'start' | 'center' | 'end' | 'baseline'>('stretch');
let justify = $state<
'start' | 'center' | 'end' | 'space-between' | 'space-around' | 'space-evenly' | 'stretch'
>('start');
let gap = $state< number > (3);
let padding = $state< number > (4);
const directionOptions = ['row', 'row-reverse', 'column', 'column-reverse'] as const;
const alignOptions = ['stretch', 'start', 'center', 'end', 'baseline'] as const;
const justifyOptions = [
'start',
'center',
'end',
'space-between',
'space-around',
'space-evenly',
'stretch'
] as const;
const stackProps = $derived< Partial < StackProps > >({
direction,
align,
justify,
gap,
padding
});
// ── Compiled morfo ────────────────────────────────────────────────────
const compiled = compileMorfo(stackMorfo);
const partsList = $derived([...compiled.parts.byKebab.values()]);
const events = $derived([...compiled.actions.byName.values()]);
$effect(() => {
const el = stageRef;
if (!el) return;
const obs = new MutationObserver((mutations) => {
for (const m of mutations) {
if (m.attributeName !== 'data-event') continue;
const target = m.target as Element;
const ev = target.getAttribute('data-event');
if (!ev) continue;
trace = [
{
event: ev,
family: target.getAttribute('data-event-family') ?? '—',
intent: target.getAttribute('data-event-intent') ?? undefined,
at: Date.now()
},
...trace
].slice(0, 6);
}
});
obs.observe(el, { attributes : true , subtree : true , attributeFilter : [ 'data-event' ] } );
return () => obs.disconnect();
});
// ── Code snippets ─────────────────────────────────────────────────────
const somaSnippet = $derived(
[
'<!-- Stack is eidos - native — no soma layer. --> ',
'<!-- Equivalent semantic markup (not real soma): --> ',
'',
'< div ' ,
' data-box',
' data-flex',
' data-stack',
' style="display: flex; flex-direction: column; gap: var(--space-3);"',
'>',
' …',
'< / div > '
].join('\n')
);
const eidosSnippet = $derived(
[
"< script lang = 'ts' > " ,
" import { Stack } from '$uix/eidos/components/stack';",
'< /' + 'script>',
'',
'< Stack ' ,
direction !== 'column' && ` direction="${ direction } "`,
align !== 'stretch' && ` align="${ align } "`,
justify !== 'start' && ` justify="${ justify } "`,
gap > 0 && ` gap={ $ { gap }} `,
padding > 0 && ` padding={ $ { padding }} `,
'>',
' …',
'< / Stack > '
]
.filter(Boolean)
.join('\n')
);
feat(eidos): port 8 layout primitives from air (box, flex, grid, stack, group, wrap, container, section)
Visual-only primitives translated from the legacy `air/components/layout`
on the morfo-runtime branch. Architectural adjustments for the eidos
target: drop the `air-` class prefix, target `[data-{name}]` directly,
rename CSS vars to `--{name}-*`, replace `var(--air-space-N)` with the
eidos foundation `var(--space-N)` token, resolve responsive props via
`ActiveEidos.resolve(prop)`.
- `box` — universal box-model utility (size, padding, margin, gap,
position, overflow, flex/grid item props). Every prop maps to a
CSS custom property; unset props fall through via `revert-layer`.
- `flex` / `grid` / `stack` / `group` / `wrap` — layout containers
that render through Box (single `<div>` per primitive, no
semantic-tag prop in this batch). Markers stack as
`[data-box][data-{name}]`.
- `container` — max-width centered (sm/md/lg/xl/full).
- `section` — semantic-padding block with background tone variants.
Shared helpers at `_layout/shared.ts` (formatLayoutSpace /
formatLayoutLength / formatLayoutRaw / pushStyleVar / composeStyle)
plus an optional `Layout.*` namespace barrel for consumers who prefer
that style; the individual `import { Box }` from each component dir
also works.
Each primitive ships a demo at `/uix/components/{name}/` with a live
example, a props table, and a reference comparison row (radix-themes /
chakra-ui / mantine).
svelte-check: 0 errors / 0 new warnings.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
5 months ago
< / script >
< div data-uix-canvas-inner >
< header >
< div data-uix-eyebrow > Layout · Stack< / div >
< h1 data-uix-page-title > Stack< / h1 >
< p data-uix-page-lede >
feat(layout): canon demos + morfos for the remaining 6 primitives
Replicates the box / flex canonical pattern across grid, stack, group,
wrap, container and section. Each gets:
- `src/uix/morfo/components/{name}.ts` — scope `eidos`, single Provider
part, no events. Header comment justifies the 0-event surface as a
passive container composing through Box / Flex.
- Rewritten demo following DEMO_AUTHORING_GUIDE: 6 tabs (Live · API ·
Morfo · Sema · Recipe · A11y), `Layout · {Name}` eyebrow, 4 meta
pills, permanent stage between header and tablist, trace strip,
MutationObserver wired to data-event, reactive soma + eidos snippets,
API tables grouped by concern, reference comparison vs radix-themes
/ chakra-ui / mantine.
Cross-references in every lede + API tab:
- Grid → Box (item placement) + Flex (flex containers)
- Stack → Flex (full API) + Group (row cluster)
- Group → Stack / Wrap / Flex
- Wrap → Flex (`wrap="wrap"`) + Group (nowrap)
- Container → Box (arbitrary maxWidth) + Section (block padding)
- Section → Box (non-semantic padding) + Container (max-width)
Demo sizes: grid 479L, stack 426L, group 458L, wrap 460L, container
415L, section 386L (vs 80-95L before — matches drawer/avatar canon
mass).
svelte-check: 0 errors, 0 new warnings.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
5 months ago
Direction-controlled flex stack — vertical by default, horizontal when
< code > direction="row"< / code > . Thin specialization of
< a href = "/uix/components/flex" > < Flex> < / a > with locked nowrap and 4 ergonomic props
(< code > direction< / code > , < code > gap< / code > , < code > align< / code > , < code > justify< / code > ). For
row-only clusters with edge-to-edge behaviour use
< a href = "/uix/components/group" > < Group> < / a > ; for the full flex API (including
< code > wrap< / code > and per-axis gaps) drop down to < code > < Flex> < / code > .
feat(eidos): port 8 layout primitives from air (box, flex, grid, stack, group, wrap, container, section)
Visual-only primitives translated from the legacy `air/components/layout`
on the morfo-runtime branch. Architectural adjustments for the eidos
target: drop the `air-` class prefix, target `[data-{name}]` directly,
rename CSS vars to `--{name}-*`, replace `var(--air-space-N)` with the
eidos foundation `var(--space-N)` token, resolve responsive props via
`ActiveEidos.resolve(prop)`.
- `box` — universal box-model utility (size, padding, margin, gap,
position, overflow, flex/grid item props). Every prop maps to a
CSS custom property; unset props fall through via `revert-layer`.
- `flex` / `grid` / `stack` / `group` / `wrap` — layout containers
that render through Box (single `<div>` per primitive, no
semantic-tag prop in this batch). Markers stack as
`[data-box][data-{name}]`.
- `container` — max-width centered (sm/md/lg/xl/full).
- `section` — semantic-padding block with background tone variants.
Shared helpers at `_layout/shared.ts` (formatLayoutSpace /
formatLayoutLength / formatLayoutRaw / pushStyleVar / composeStyle)
plus an optional `Layout.*` namespace barrel for consumers who prefer
that style; the individual `import { Box }` from each component dir
also works.
Each primitive ships a demo at `/uix/components/{name}/` with a live
example, a props table, and a reference comparison row (radix-themes /
chakra-ui / mantine).
svelte-check: 0 errors / 0 new warnings.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
5 months ago
< / p >
feat(layout): canon demos + morfos for the remaining 6 primitives
Replicates the box / flex canonical pattern across grid, stack, group,
wrap, container and section. Each gets:
- `src/uix/morfo/components/{name}.ts` — scope `eidos`, single Provider
part, no events. Header comment justifies the 0-event surface as a
passive container composing through Box / Flex.
- Rewritten demo following DEMO_AUTHORING_GUIDE: 6 tabs (Live · API ·
Morfo · Sema · Recipe · A11y), `Layout · {Name}` eyebrow, 4 meta
pills, permanent stage between header and tablist, trace strip,
MutationObserver wired to data-event, reactive soma + eidos snippets,
API tables grouped by concern, reference comparison vs radix-themes
/ chakra-ui / mantine.
Cross-references in every lede + API tab:
- Grid → Box (item placement) + Flex (flex containers)
- Stack → Flex (full API) + Group (row cluster)
- Group → Stack / Wrap / Flex
- Wrap → Flex (`wrap="wrap"`) + Group (nowrap)
- Container → Box (arbitrary maxWidth) + Section (block padding)
- Section → Box (non-semantic padding) + Container (max-width)
Demo sizes: grid 479L, stack 426L, group 458L, wrap 460L, container
415L, section 386L (vs 80-95L before — matches drawer/avatar canon
mass).
svelte-check: 0 errors, 0 new warnings.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
5 months ago
< div data-uix-page-meta >
< span data-uix-meta-pill >
< span data-uix-meta-key > parts</ span > { compiled . parts . order . length }
< / span >
< span data-uix-meta-pill >
< span data-uix-meta-key > events< / span > 0
< / span >
< span data-uix-meta-pill >
< span data-uix-meta-key > extends< / span > Flex
< / span >
< span data-uix-meta-pill >
< span data-uix-meta-key > scope< / span > eidos
< / span >
< / div >
feat(eidos): port 8 layout primitives from air (box, flex, grid, stack, group, wrap, container, section)
Visual-only primitives translated from the legacy `air/components/layout`
on the morfo-runtime branch. Architectural adjustments for the eidos
target: drop the `air-` class prefix, target `[data-{name}]` directly,
rename CSS vars to `--{name}-*`, replace `var(--air-space-N)` with the
eidos foundation `var(--space-N)` token, resolve responsive props via
`ActiveEidos.resolve(prop)`.
- `box` — universal box-model utility (size, padding, margin, gap,
position, overflow, flex/grid item props). Every prop maps to a
CSS custom property; unset props fall through via `revert-layer`.
- `flex` / `grid` / `stack` / `group` / `wrap` — layout containers
that render through Box (single `<div>` per primitive, no
semantic-tag prop in this batch). Markers stack as
`[data-box][data-{name}]`.
- `container` — max-width centered (sm/md/lg/xl/full).
- `section` — semantic-padding block with background tone variants.
Shared helpers at `_layout/shared.ts` (formatLayoutSpace /
formatLayoutLength / formatLayoutRaw / pushStyleVar / composeStyle)
plus an optional `Layout.*` namespace barrel for consumers who prefer
that style; the individual `import { Box }` from each component dir
also works.
Each primitive ships a demo at `/uix/components/{name}/` with a live
example, a props table, and a reference comparison row (radix-themes /
chakra-ui / mantine).
svelte-check: 0 errors / 0 new warnings.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
5 months ago
< / header >
feat(layout): canon demos + morfos for the remaining 6 primitives
Replicates the box / flex canonical pattern across grid, stack, group,
wrap, container and section. Each gets:
- `src/uix/morfo/components/{name}.ts` — scope `eidos`, single Provider
part, no events. Header comment justifies the 0-event surface as a
passive container composing through Box / Flex.
- Rewritten demo following DEMO_AUTHORING_GUIDE: 6 tabs (Live · API ·
Morfo · Sema · Recipe · A11y), `Layout · {Name}` eyebrow, 4 meta
pills, permanent stage between header and tablist, trace strip,
MutationObserver wired to data-event, reactive soma + eidos snippets,
API tables grouped by concern, reference comparison vs radix-themes
/ chakra-ui / mantine.
Cross-references in every lede + API tab:
- Grid → Box (item placement) + Flex (flex containers)
- Stack → Flex (full API) + Group (row cluster)
- Group → Stack / Wrap / Flex
- Wrap → Flex (`wrap="wrap"`) + Group (nowrap)
- Container → Box (arbitrary maxWidth) + Section (block padding)
- Section → Box (non-semantic padding) + Container (max-width)
Demo sizes: grid 479L, stack 426L, group 458L, wrap 460L, container
415L, section 386L (vs 80-95L before — matches drawer/avatar canon
mass).
svelte-check: 0 errors, 0 new warnings.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
5 months ago
<!-- Live preview always rendered -->
< div data-uix-stage >
< div data-uix-stage-area bind:this = { stageRef } >
< Stack
{... stackProps }
style="border: 1px dashed var(--color-border-default); border-radius: var(--radius-md); min-block-size: 10rem; min-inline-size: 24rem;"
>
< Box
padding={ 2 }
style="border: 1px solid var(--color-primary-border); border-radius: var(--radius-sm); background: var(--color-primary-track); color: var(--color-primary-text);"
>Row A< / Box >
< Box
padding={ 2 }
style="border: 1px solid var(--color-affirm-border); border-radius: var(--radius-sm); background: var(--color-affirm-track); color: var(--color-affirm-text);"
>Row B< / Box >
< Box
padding={ 2 }
style="border: 1px solid var(--color-risk-border); border-radius: var(--radius-sm); background: var(--color-risk-track); color: var(--color-risk-text);"
>Row C< / Box >
< / Stack >
< / div >
< div data-uix-stage-trace >
< span data-uix-stage-trace-key > trace< / span >
< span > { trace . length === 0 ? 'no semantic events' : `$ { trace . length } event ( s ) ` } </ span >
< span style = "color: var(--uix-text-faint)" > ·< / span >
< span data-uix-stage-trace-key > direction< / span >
< span > { direction } </ span >
< span style = "margin-inline-start: auto;" >
< span data-uix-stage-trace-key > gap</ span > { gap } ·
< span data-uix-stage-trace-key > align</ span > { align }
< / span >
< / div >
< / div >
< div data-uix-tabs role = "tablist" >
< button data-uix-tab data-active = { tab === 'live' } onclick= {() => ( tab = 'live' )} > Live</ button >
< button data-uix-tab data-active = { tab === 'api' } onclick= {() => ( tab = 'api' )} >
API < span data-uix-tab-count > 4< / span >
< / button >
< button data-uix-tab data-active = { tab === 'morfo' } onclick= {() => ( tab = 'morfo' )} >
< span data-uix-layer-badge = "morfo" > morfo< / span >
< span data-uix-tab-count > { partsList . length } p · 0e</ span >
< / button >
< button data-uix-tab data-active = { tab === 'sema' } onclick= {() => ( tab = 'sema' )} >
< span data-uix-layer-badge = "sema" > sema< / span >
< span data-uix-tab-count > { events . length } </ span >
< / button >
< button data-uix-tab data-active = { tab === 'recipe' } onclick= {() => ( tab = 'recipe' )} > Recipe</ button >
< button data-uix-tab data-active = { tab === 'a11y' } onclick= {() => ( tab = 'a11y' )} > A11y</ button >
< / div >
{ #if tab === 'live' }
< section data-uix-section >
< h2 data-uix-section-title > Controls< / h2 >
< p data-uix-section-desc >
Stack is eidos-native — no < span data-uix-layer-badge = "soma" > soma< / span > split. The
4 container props below pass through to the underlying < code > < Flex> < / code > .
Stack also inherits every < a href = "/uix/components/box" > Box< / a > prop (padding,
margin, size, position) via Flex composition.
< / p >
< div data-uix-subsection-head >
< span data-uix-layer-badge = "eidos" > eidos< / span > props · stack behaviour
< / div >
< div data-uix-controls >
< label data-uix-control >
< span data-uix-control-label > direction< / span >
< span data-uix-chips role = "radiogroup" >
{ #each directionOptions as opt }
< button
data-uix-chip
data-active={ direction === opt }
onclick={() => ( direction = opt )} >{ opt } < /button
>
{ /each }
< / span >
< / label >
< label data-uix-control >
< span data-uix-control-label > align < span data-uix-control-hint > cross-axis< / span > < / span >
< span data-uix-chips role = "radiogroup" >
{ #each alignOptions as opt }
< button data-uix-chip data-active = { align === opt } onclick= {() => ( align = opt )}
>{ opt } < /button
>
{ /each }
< / span >
< / label >
< label data-uix-control >
< span data-uix-control-label > justify < span data-uix-control-hint > main-axis< / span > < / span >
< span data-uix-chips role = "radiogroup" >
{ #each justifyOptions as opt }
< button data-uix-chip data-active = { justify === opt } onclick= {() => ( justify = opt )}
>{ opt } < /button
>
{ /each }
< / span >
< / label >
< label data-uix-control >
< span data-uix-control-label > gap < span data-uix-control-hint > 0– 8 → var(--space-N)< / span > < / span >
< input type = "number" min = "0" max = "8" step = "1" bind:value = { gap } style="inline-size: 6rem ;" />
< / label >
< label data-uix-control >
< span data-uix-control-label > padding < span data-uix-control-hint > inherited from Box< / span > < / span >
< input
type="number"
min="0"
max="8"
step="1"
bind:value={ padding }
style="inline-size: 6rem;"
/>
< / label >
< / div >
<!-- ── Code snippets per layer ─────────────────────────────────── -->
< div data-uix-code >
< div data-uix-code-head >
< span data-uix-layer-badge = "soma" > soma< / span >
< span > n/a · stack is eidos-native — equivalent markup shown< / span >
< span data-uix-code-lang > svelte< / span >
< / div >
< pre >< code > { somaSnippet } </ code ></ pre >
< / div >
< div data-uix-code style = "margin-top: var(--uix-space-3);" >
< div data-uix-code-head >
< span data-uix-layer-badge = "eidos" > eidos< / span >
< span > visual · 4 ergonomic props on top of Flex< / span >
< span data-uix-code-lang > svelte< / span >
< / div >
< pre >< code > { eidosSnippet } </ code ></ pre >
< / div >
< / section >
{ /if }
{ #if tab === 'api' }
< section data-uix-section >
< h2 data-uix-section-title > API reference< / h2 >
< p data-uix-section-desc >
Stack is a thin, opinionated specialization of < a href = "/uix/components/flex"
>< code > < Flex> < / code > < /a
>: it locks < code > wrap< / code > to nowrap, defaults < code > direction< / code > to
< code > column< / code > , and exposes only the four most-used container props. Drop down to
< code > < Flex> < / code > when you need < code > wrap< / code > , < code > rowGap< / code > ,
< code > columnGap< / code > or < code > inline< / code > .
< / p >
< div data-uix-subsection-head > Container props< / div >
< div data-uix-table-wrap >
< table data-uix-table >
< thead > < tr > < th > Prop< / th > < th > Type< / th > < th > Default< / th > < th > Notes< / th > < / tr > < / thead >
< tbody >
< tr > < td class = "name" > direction< / td > < td class = "type" > row | row-reverse | column | column-reverse< / td > < td class = "default" > 'column'< / td > < td > < code > flex-direction< / code > .< / td > < / tr >
< tr > < td class = "name" > gap< / td > < td class = "type" > number | string< / td > < td class = "default empty" > —< / td > < td > Number → < code > var(--space-N)< / code > .< / td > < / tr >
< tr > < td class = "name" > align< / td > < td class = "type" > stretch | start | center | end | baseline | flex-start | flex-end< / td > < td class = "default empty" > —< / td > < td > < code > align-items< / code > — cross-axis.< / td > < / tr >
< tr > < td class = "name" > justify< / td > < td class = "type" > start | center | end | space-between | space-around | space-evenly | stretch | flex-start | flex-end< / td > < td class = "default empty" > —< / td > < td > < code > justify-content< / code > — main-axis.< / td > < / tr >
< / tbody >
< / table >
< / div >
< div data-uix-subsection-head > Inherited from Box< / div >
< p data-uix-section-desc >
Every < a href = "/uix/components/box" > BoxProps< / a > field is forwarded through Flex:
< code > padding*< / code > , < code > margin*< / code > , < code > width< / code > , < code > height< / code > ,
< code > position< / code > , < code > top/right/bottom/left< / code > , < code > overflow*< / code > , plus
item-side props (< code > alignSelf< / code > , < code > gridColumn< / code > , …).
< / p >
< div data-uix-subsection-head > Not on Stack (use Flex instead)< / div >
< p data-uix-section-desc >
< code > wrap< / code > , < code > inline< / code > , < code > rowGap< / code > , < code > columnGap< / code > —
Stack intentionally omits these. Use < a href = "/uix/components/flex"
>< code > < Flex> < / code > < /a
> when you need them.
< / p >
< div data-uix-subsection-head > Reference comparison< / div >
< div data-uix-table-wrap >
< table data-uix-table >
< thead > < tr > < th > Library< / th > < th > Closest equivalent< / th > < th > Difference< / th > < / tr > < / thead >
< tbody >
< tr > < td class = "name" > radix-themes< / td > < td > < code > < Flex direction="column"> < / code > < / td > < td > Radix doesn't ship a separate Stack — they use Flex with direction. UIX names the direction-locked variant for the most common case.< / td > < / tr >
< tr > < td class = "name" > chakra-ui< / td > < td > < code > < Stack> < / code > / < code > < VStack> < / code > / < code > < HStack> < / code > < / td > < td > Chakra exposes three components (Stack/VStack/HStack). UIX collapses to a single Stack with a < code > direction< / code > prop — < a href = "/uix/components/group" > < Group> < / a > covers the row-cluster case.< / td > < / tr >
< tr > < td class = "name" > mantine< / td > < td > < code > < Stack> < / code > / < code > < Group> < / code > < / td > < td > Mantine has the same split: vertical Stack + horizontal Group. UIX matches Mantine here.< / td > < / tr >
< / tbody >
< / table >
< / div >
< / section >
{ /if }
{ #if tab === 'morfo' }
< section data-uix-section >
< h2 data-uix-section-title > Morfo contract< / h2 >
< div data-uix-table-wrap >
< table data-uix-table >
< thead > < tr > < th > Field< / th > < th > Value< / th > < / tr > < / thead >
< tbody >
< tr >< td class = "name" > name</ td >< td > { stackMorfo . name } </ td ></ tr >
< tr >< td class = "name" > kebab</ td >< td >< code > { stackMorfo . kebab } </ code ></ td ></ tr >
< tr >< td class = "name" > scope</ td >< td > { stackMorfo . scope . join ( ', ' )} </ td ></ tr >
< tr >< td class = "name" > parts</ td >< td > { partsList . length } </ td ></ tr >
< tr > < td class = "name" > events< / td > < td > 0< / td > < / tr >
< / tbody >
< / table >
< / div >
< div data-uix-subsection-head > Parts< / div >
< div data-uix-table-wrap >
< table data-uix-table >
< thead > < tr > < th > kebab< / th > < th > marker< / th > < th > element< / th > < th > archetype< / th > < th > optional< / th > < / tr > < / thead >
< tbody >
{ #each partsList as part }
< tr >
< td class = "name" > { part . kebab } </ td >
< td >< code data-uix-part-marker > [{ part . marker } ]</ code ></ td >
< td class = "type" > < { part . defaultElement } > </ td >
< td class = "type" > { part . archetype } </ td >
< td class = "default" > { part . optional ? 'yes' : 'no' } </ td >
< / tr >
{ /each }
< / tbody >
< / table >
< / div >
< p data-uix-section-desc style = "margin-top: var(--uix-space-4);" >
Stack composes through < code > < Flex> < / code > which composes through
< code > < Box> < / code > , so the rendered DOM is a single
< code > div[data-box][data-flex][data-stack]< / code > shell. Recipe lives at
< code > src/uix/eidos/components/stack/stack.css< / code > — currently just the marker; all
behaviour comes from the Flex layer.
< / p >
< / section >
{ /if }
{ #if tab === 'sema' }
< section data-uix-section >
< h2 data-uix-section-title >
< span data-uix-layer-badge = "sema" > sema< / span > · events
< / h2 >
< p data-uix-section-desc >
Stack declares no semantic events. As a passive layout primitive, it does not commit,
emerge, or react to anything — it just arranges its children. Components that animate
on appearance should compose Stack with an interactive primitive (popover, drawer,
collapsible) that owns the relevant sema verbs.
< / p >
< / section >
{ /if }
{ #if tab === 'recipe' }
< section data-uix-section >
< h2 data-uix-section-title > Eidos recipe< / h2 >
< p data-uix-section-desc >
Recipe lives in < code > src/uix/eidos/components/stack/stack.css< / code > — currently
declaration-only (the < code > [data-stack]< / code > marker exists so consumers can target
stacks specifically). All visual behaviour layers on top of the Flex recipe.
< / p >
< div data-uix-table-wrap >
< table data-uix-table >
< thead > < tr > < th > Selector< / th > < th > Owner< / th > < th > Purpose< / th > < / tr > < / thead >
< tbody >
< tr >
< td class = "name" > < code > [data-stack]< / code > < / td >
< td > < span data-uix-tag data-kind = "morfo" > morfo< / span > < / td >
< td > Provider marker. Used by consumers for targeting; behaviour inherited from Flex.< / td >
< / tr >
< / tbody >
< / table >
< / div >
< / section >
{ /if }
{ #if tab === 'a11y' }
< section data-uix-section >
< h2 data-uix-section-title > Accessibility< / h2 >
< div data-uix-table-wrap >
< table data-uix-table >
< thead > < tr > < th > Concern< / th > < th > Contract< / th > < / tr > < / thead >
< tbody >
< tr > < td class = "name" > Role< / td > < td > None implicit. Default element is < code > < div> < / code > — semantically neutral. Wrap Stack in a landmark (< code > < nav> < / code > , < code > < section> < / code > , < code > < ul role="list"> < / code > ) when content needs one.< / td > < / tr >
< tr > < td class = "name" > Label< / td > < td > Not applicable — Stack has no content semantics. Labels belong to the interactive children.< / td > < / tr >
< tr > < td class = "name" > Keyboard< / td > < td > Stack is not focusable. Tab order follows DOM order even when < code > direction< / code > visually reverses (< code > row-reverse< / code > / < code > column-reverse< / code > ). Verify visual order matches focus order for keyboard users.< / td > < / tr >
< tr > < td class = "name" > Reduced motion< / td > < td > Stack does not animate.< / td > < / tr >
< / tbody >
< / table >
< / div >
< / section >
{ /if }
feat(eidos): port 8 layout primitives from air (box, flex, grid, stack, group, wrap, container, section)
Visual-only primitives translated from the legacy `air/components/layout`
on the morfo-runtime branch. Architectural adjustments for the eidos
target: drop the `air-` class prefix, target `[data-{name}]` directly,
rename CSS vars to `--{name}-*`, replace `var(--air-space-N)` with the
eidos foundation `var(--space-N)` token, resolve responsive props via
`ActiveEidos.resolve(prop)`.
- `box` — universal box-model utility (size, padding, margin, gap,
position, overflow, flex/grid item props). Every prop maps to a
CSS custom property; unset props fall through via `revert-layer`.
- `flex` / `grid` / `stack` / `group` / `wrap` — layout containers
that render through Box (single `<div>` per primitive, no
semantic-tag prop in this batch). Markers stack as
`[data-box][data-{name}]`.
- `container` — max-width centered (sm/md/lg/xl/full).
- `section` — semantic-padding block with background tone variants.
Shared helpers at `_layout/shared.ts` (formatLayoutSpace /
formatLayoutLength / formatLayoutRaw / pushStyleVar / composeStyle)
plus an optional `Layout.*` namespace barrel for consumers who prefer
that style; the individual `import { Box }` from each component dir
also works.
Each primitive ships a demo at `/uix/components/{name}/` with a live
example, a props table, and a reference comparison row (radix-themes /
chakra-ui / mantine).
svelte-check: 0 errors / 0 new warnings.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
5 months ago
< / div >