feat(layout): canon Box demo + relocate grid item placement props to Box

POC for redoing Layout Batch 1 demos to follow DEMO_AUTHORING_GUIDE.
Box becomes the reference template for the remaining 7 primitives.

Demo follows the canon shape:
- Header with eyebrow + 4 meta pills (parts, events, props, scope).
- Stage permanently rendered with 3 nested boxes + trace strip
  showing live display/padding/gap state.
- 6 tabs in canonical order (Live · API · Morfo · Sema · Recipe · A11y).
- Live tab: 7 controls + reactive soma/eidos snippets.
- API tab: 6 subsections grouped by concern (sizing / spacing /
  display+position / flex/grid item / escape hatch / reference
  comparison vs radix-themes / chakra / mantine).
- Morfo / Sema / Recipe / A11y tabs each populated with the contract
  appropriate to a passive eidos-native primitive.

Architectural fixes (heredados de air, ahora correctos):
- `gridColumn`, `gridRow`, `gridArea` move from `<Grid>` to `<Box>` —
  these are item placement props, they belong on the CHILD that
  declares its slot, not the container.
- Add `placeSelf` to Box (grid item shorthand for align-self +
  justify-self).
- Grid recipe + props updated accordingly; comment clarifies that
  item placement lives on Box.

Add `src/uix/morfo/components/box.ts` (scope `eidos`, 1 part, 0 events
with justification comment). Box demo updated lede to point at Flex /
Grid for container-side props (Radix Themes split, not Chakra
everything-on-Box).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
active-uix
dev 5 months ago
parent 9ab6746c63
commit 66c6897c61

@ -80,4 +80,10 @@
order: var(--box-order, revert-layer); order: var(--box-order, revert-layer);
align-self: var(--box-align-self, revert-layer); align-self: var(--box-align-self, revert-layer);
justify-self: var(--box-justify-self, revert-layer); justify-self: var(--box-justify-self, revert-layer);
place-self: var(--box-place-self, revert-layer);
/* Grid item placement — Box used as a grid child declares its slot. */
grid-column: var(--box-grid-column, revert-layer);
grid-row: var(--box-grid-row, revert-layer);
grid-area: var(--box-grid-area, revert-layer);
} }

@ -62,6 +62,10 @@
order, order,
alignSelf, alignSelf,
justifySelf, justifySelf,
placeSelf,
gridColumn,
gridRow,
gridArea,
style, style,
class: className, class: className,
children, children,
@ -119,6 +123,12 @@
pushStyleVar(decls, '--box-order', formatLayoutRaw(eidos.resolve(order))); pushStyleVar(decls, '--box-order', formatLayoutRaw(eidos.resolve(order)));
pushStyleVar(decls, '--box-align-self', eidos.resolve(alignSelf)); pushStyleVar(decls, '--box-align-self', eidos.resolve(alignSelf));
pushStyleVar(decls, '--box-justify-self', eidos.resolve(justifySelf)); pushStyleVar(decls, '--box-justify-self', eidos.resolve(justifySelf));
pushStyleVar(decls, '--box-place-self', eidos.resolve(placeSelf));
// Grid item placement (on the child, not the container).
pushStyleVar(decls, '--box-grid-column', formatLayoutRaw(eidos.resolve(gridColumn)));
pushStyleVar(decls, '--box-grid-row', formatLayoutRaw(eidos.resolve(gridRow)));
pushStyleVar(decls, '--box-grid-area', formatLayoutRaw(eidos.resolve(gridArea)));
return composeStyle(decls, style); return composeStyle(decls, style);
}); });

@ -78,6 +78,14 @@ export type BoxProps = Omit<HTMLAttributes<HTMLElement>, 'style' | 'children'> &
alignSelf?: ResponsiveProp<LayoutAlignSelf>; alignSelf?: ResponsiveProp<LayoutAlignSelf>;
/** Grid item: `justify-self`. */ /** Grid item: `justify-self`. */
justifySelf?: ResponsiveProp<LayoutJustifySelf>; justifySelf?: ResponsiveProp<LayoutJustifySelf>;
/** Grid item: `place-self` shorthand (align-self + justify-self). */
placeSelf?: ResponsiveProp<string>;
/** Grid item: `grid-column`. */
gridColumn?: ResponsiveProp<string>;
/** Grid item: `grid-row`. */
gridRow?: ResponsiveProp<string>;
/** Grid item: `grid-area`. */
gridArea?: ResponsiveProp<string>;
/** Extra inline style. Merged after the box's own CSS variable declarations. */ /** Extra inline style. Merged after the box's own CSS variable declarations. */
style?: string; style?: string;
/** Extra class names. */ /** Extra class names. */

@ -1,8 +1,11 @@
/* /*
* Grid recipe — additional CSS variables layered on top of the Box * Grid recipe — additional CSS variables layered on top of the Box
* recipe. Track and placement props (`place-items`, `place-content`, * recipe. Container-only props: track templates, auto-flow, gap, and
* `grid-column`, `grid-row`, `grid-area`) fall through to `revert-layer` * the container-side `place-items` / `place-content` shorthands.
* so they only apply when explicitly set. *
* Item placement props (`grid-column`, `grid-row`, `grid-area`,
* `place-self`) live on the child `<Box>` — see `box.css`. A Box used
* as a Grid item declares its own slot.
*/ */
[data-box][data-grid] { [data-box][data-grid] {
@ -15,9 +18,6 @@
justify-content: var(--grid-justify, start); justify-content: var(--grid-justify, start);
place-items: var(--grid-place-items, revert-layer); place-items: var(--grid-place-items, revert-layer);
place-content: var(--grid-place-content, revert-layer); place-content: var(--grid-place-content, revert-layer);
grid-column: var(--grid-column, revert-layer);
grid-row: var(--grid-row, revert-layer);
grid-area: var(--grid-area, revert-layer);
row-gap: var(--grid-row-gap, var(--grid-gap, revert-layer)); row-gap: var(--grid-row-gap, var(--grid-gap, revert-layer));
column-gap: var(--grid-column-gap, var(--grid-gap, revert-layer)); column-gap: var(--grid-column-gap, var(--grid-gap, revert-layer));
} }

@ -26,9 +26,6 @@
justify, justify,
placeItems, placeItems,
placeContent, placeContent,
gridColumn,
gridRow,
gridArea,
style, style,
class: className, class: className,
children, children,
@ -48,9 +45,6 @@
pushStyleVar(decls, '--grid-justify', eidos.resolve(justify)); pushStyleVar(decls, '--grid-justify', eidos.resolve(justify));
pushStyleVar(decls, '--grid-place-items', eidos.resolve(placeItems)); pushStyleVar(decls, '--grid-place-items', eidos.resolve(placeItems));
pushStyleVar(decls, '--grid-place-content', eidos.resolve(placeContent)); pushStyleVar(decls, '--grid-place-content', eidos.resolve(placeContent));
pushStyleVar(decls, '--grid-column', eidos.resolve(gridColumn));
pushStyleVar(decls, '--grid-row', eidos.resolve(gridRow));
pushStyleVar(decls, '--grid-area', eidos.resolve(gridArea));
pushStyleVar(decls, '--grid-gap', formatLayoutSpace(eidos.resolve(gap))); pushStyleVar(decls, '--grid-gap', formatLayoutSpace(eidos.resolve(gap)));
pushStyleVar(decls, '--grid-column-gap', formatLayoutSpace(eidos.resolve(columnGap))); pushStyleVar(decls, '--grid-column-gap', formatLayoutSpace(eidos.resolve(columnGap)));
pushStyleVar(decls, '--grid-row-gap', formatLayoutSpace(eidos.resolve(rowGap))); pushStyleVar(decls, '--grid-row-gap', formatLayoutSpace(eidos.resolve(rowGap)));

@ -32,10 +32,8 @@ export type GridProps = Omit<BoxProps, 'display'> & {
placeItems?: ResponsiveProp<string>; placeItems?: ResponsiveProp<string>;
/** `place-content` shorthand. */ /** `place-content` shorthand. */
placeContent?: ResponsiveProp<string>; placeContent?: ResponsiveProp<string>;
/** Grid item: `grid-column`. */
gridColumn?: ResponsiveProp<string>;
/** Grid item: `grid-row`. */
gridRow?: ResponsiveProp<string>;
/** Grid item: `grid-area`. */
gridArea?: ResponsiveProp<string>;
}; };
// Note: grid item placement props (`gridColumn`, `gridRow`, `gridArea`,
// `placeSelf`) belong on the child, not the container. They live on
// `<Box>` — a Box used as a grid item declares its own placement.

@ -0,0 +1,32 @@
import type { Morfo } from '../types';
/**
* Box — universal box-model utility (layout primitive).
*
* Eidos-native: the recipe is entirely CSS-variable driven. Every public
* prop maps to a `--box-{prop}` custom property and the consumer just
* sees a single `<div data-box>` shell. No semantic events — Box is a
* pure visual / structural primitive, like the avatar / icon shells.
*
* Justification for 0-event surface: Box does not commit, emerge or
* react to anything. It is a passive container that styles its children
* via the cascade. Adding events would manufacture semantics the
* primitive doesn't carry.
*/
export const boxMorfo = {
name: 'Box',
kebab: 'box',
scope: ['eidos'],
parts: [
{
name: 'Provider',
kebab: 'provider',
archetype: 'provider',
kind: 'public',
defaultElement: 'div',
optional: false,
data: [],
aria: []
}
]
} as const satisfies Morfo;

@ -1,5 +1,104 @@
<script lang="ts"> <script lang="ts">
import { Box } from '$uix/eidos/components/box'; import { Box, type BoxProps } from '$uix/eidos/components/box';
import { compileMorfo } from '$uix/morfo';
import { boxMorfo } from '@/uix/morfo/components/box';
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 ────────────────────────────────────────────────────────
// Box has 40+ props; we expose the most-used ones as live controls
// and document the rest in the API table.
let display = $state<'block' | 'flex' | 'inline-flex' | 'grid' | 'inline-grid' | 'inline' | 'inline-block' | 'contents' | 'none'>('flex');
let padding = $state<number>(4);
let gap = $state<number>(3);
let width = $state<string>('');
let height = $state<string>('');
let overflow = $state<'visible' | 'hidden' | 'auto' | 'scroll' | 'clip' | ''>('');
let position = $state<'static' | 'relative' | 'absolute' | 'fixed' | 'sticky' | ''>('');
const displayOptions = ['block', 'flex', 'inline-flex', 'grid', 'inline-grid', 'inline', 'inline-block', 'contents', 'none'] as const;
const overflowOptions = ['', 'visible', 'hidden', 'auto', 'scroll', 'clip'] as const;
const positionOptions = ['', 'static', 'relative', 'absolute', 'fixed', 'sticky'] as const;
const boxProps = $derived<Partial<BoxProps>>({
display,
padding,
gap: gap || undefined,
width: width || undefined,
height: height || undefined,
overflow: overflow || undefined,
position: position || undefined
});
// ── Compiled morfo ────────────────────────────────────────────────────
const compiled = compileMorfo(boxMorfo);
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(
[
'<!-- Box is eidos-native — no soma layer. -->',
'<!-- Equivalent semantic markup (not real soma): -->',
'',
'<div',
' data-box',
' style="display: var(--box-display); padding: var(--box-padding); gap: var(--box-gap);"',
'>',
' …',
'</div>'
].join('\n')
);
const eidosSnippet = $derived(
[
"<script lang='ts'>",
" import { Box } from '$uix/eidos/components/box';",
'</' + 'script>',
'',
'<Box',
display !== 'flex' && ` display="${display}"`,
padding !== 0 && ` padding={${padding}}`,
gap > 0 && ` gap={${gap}}`,
width && ` width="${width}"`,
height && ` height="${height}"`,
overflow && ` overflow="${overflow}"`,
position && ` position="${position}"`,
'>',
' …',
'</Box>'
]
.filter(Boolean)
.join('\n')
);
</script> </script>
<div data-uix-canvas-inner> <div data-uix-canvas-inner>
@ -7,127 +106,336 @@
<div data-uix-eyebrow>Layout · Box</div> <div data-uix-eyebrow>Layout · Box</div>
<h1 data-uix-page-title>Box</h1> <h1 data-uix-page-title>Box</h1>
<p data-uix-page-lede> <p data-uix-page-lede>
Universal box-model utility. Every prop maps to a CSS custom property; unset Universal box-model utility — size, padding/margin, position, overflow, plus flex/grid
props fall through to the normal cascade. <em>item</em> props (alignment, placement, order). Container-side flex/grid props (direction,
align, justify, wrap, templateColumns, …) live in
<a href="/uix/components/flex">&lt;Flex&gt;</a> and
<a href="/uix/components/grid">&lt;Grid&gt;</a> — UIX follows the Radix Themes split, not the
Chakra/MUI everything-on-Box model. Every prop maps to a <code>--box-*</code> CSS custom
property emitted inline; unset props fall through via <code>revert-layer</code>.
Eidos-native: no soma backing, no semantic events.
</p> </p>
<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>props</span>40+
</span>
<span data-uix-meta-pill>
<span data-uix-meta-key>scope</span>eidos
</span>
</div>
</header> </header>
<section> <!-- Live preview always rendered -->
<h2>Live example</h2> <div data-uix-stage>
<Box <div data-uix-stage-area bind:this={stageRef}>
padding={4} <Box {...boxProps} style="border: 1px dashed var(--color-border-default); border-radius: var(--radius-md); min-width: 16rem;">
maxWidth={480} <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);">Cell A</Box>
display="flex" <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);">Cell B</Box>
gap={3} <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);">Cell C</Box>
style="background: var(--color-surface-1); border: 1px solid var(--color-border); border-radius: 8px"
>
<Box padding={2} style="background: var(--color-surface-2); border-radius: 4px">
Cell A
</Box>
<Box padding={2} style="background: var(--color-surface-2); border-radius: 4px">
Cell B
</Box>
<Box padding={2} style="background: var(--color-surface-2); border-radius: 4px">
Cell C
</Box>
</Box> </Box>
</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>display</span>
<span>{display}</span>
<span style="margin-inline-start: auto;">
<span data-uix-stage-trace-key>padding</span>
{padding} · <span data-uix-stage-trace-key>gap</span> {gap}
</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>40+</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>
Box is eidos-native — no <span data-uix-layer-badge="soma">soma</span> split. Each prop
maps to a <code>--box-*</code> custom property; numeric space values resolve to
<code>var(--space-N)</code>, numeric length values to <code>{`{N}px`}</code>, strings pass
through. Unset props fall back to the cascade via <code>revert-layer</code>.
</p>
<div data-uix-subsection-head>
<span data-uix-layer-badge="eidos">eidos</span> props · visual treatment
</div>
<div data-uix-controls>
<label data-uix-control>
<span data-uix-control-label>display</span>
<span data-uix-chips role="radiogroup">
{#each displayOptions as opt}
<button data-uix-chip data-active={display === opt} onclick={() => (display = opt)}>{opt}</button>
{/each}
</span>
</label>
<label data-uix-control>
<span data-uix-control-label>padding <span data-uix-control-hint>0–8 → var(--space-N)</span></span>
<input type="number" min="0" max="8" step="1" bind:value={padding} style="inline-size: 6rem;" />
</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>width <span data-uix-control-hint>e.g. 320px, 50%, auto</span></span>
<input type="text" placeholder="(auto)" bind:value={width} style="inline-size: 8rem;" />
</label>
<label data-uix-control>
<span data-uix-control-label>height <span data-uix-control-hint>e.g. 200px, auto</span></span>
<input type="text" placeholder="(auto)" bind:value={height} style="inline-size: 8rem;" />
</label>
<label data-uix-control>
<span data-uix-control-label>overflow</span>
<span data-uix-chips role="radiogroup">
{#each overflowOptions as opt}
<button data-uix-chip data-active={overflow === opt} onclick={() => (overflow = opt)}>{opt || '(unset)'}</button>
{/each}
</span>
</label>
<label data-uix-control>
<span data-uix-control-label>position</span>
<span data-uix-chips role="radiogroup">
{#each positionOptions as opt}
<button data-uix-chip data-active={position === opt} onclick={() => (position = opt)}>{opt || '(unset)'}</button>
{/each}
</span>
</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 · box 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 · every prop maps to a --box-* CSS variable</span>
<span data-uix-code-lang>svelte</span>
</div>
<pre><code>{eidosSnippet}</code></pre>
</div>
</section> </section>
{/if}
<section> {#if tab === 'api'}
<h2>Props</h2> <section data-uix-section>
<table> <h2 data-uix-section-title>API reference</h2>
<thead> <p data-uix-section-desc>
<tr> Numeric values for space props (<code>padding*</code>, <code>margin*</code>,
<th>Prop</th> <code>gap</code>) resolve to <code>var(--space-N)</code>. Numeric values for length props
<th>Type</th> (<code>width</code>, <code>height</code>, insets) resolve to <code>{`{N}px`}</code>. String
<th>Notes</th> values pass through unchanged. <code>revert-layer</code> is the fallback for any unset prop.
</tr> </p>
</thead>
<div data-uix-subsection-head>Sizing</div>
<div data-uix-table-wrap>
<table data-uix-table>
<thead><tr><th>Prop</th><th>Type</th><th>Notes</th></tr></thead>
<tbody> <tbody>
<tr><td class="name">width / minWidth / maxWidth</td><td class="type">number | string</td><td>Number → px, string passes through.</td></tr>
<tr><td class="name">height / minHeight / maxHeight</td><td class="type">number | string</td><td>Same as width.</td></tr>
</tbody>
</table>
</div>
<div data-uix-subsection-head>Spacing</div>
<div data-uix-table-wrap>
<table data-uix-table>
<thead><tr><th>Prop</th><th>Type</th><th>Notes</th></tr></thead>
<tbody>
<tr><td class="name">padding / paddingX / paddingY</td><td class="type">number | string</td><td>Number → <code>var(--space-N)</code>.</td></tr>
<tr><td class="name">paddingTop / paddingRight / paddingBottom / paddingLeft</td><td class="type">number | string</td><td>Per-side overrides; cascade <code>paddingX/Y</code> → <code>padding</code>.</td></tr>
<tr><td class="name">margin / marginX / marginY</td><td class="type">number | string</td><td>Same as padding mapping.</td></tr>
<tr><td class="name">marginTop / marginRight / marginBottom / marginLeft</td><td class="type">number | string</td><td>Per-side overrides.</td></tr>
<tr><td class="name">gap</td><td class="type">number | string</td><td>Useful with display flex/grid.</td></tr>
</tbody>
</table>
</div>
<div data-uix-subsection-head>Display + position</div>
<div data-uix-table-wrap>
<table data-uix-table>
<thead><tr><th>Prop</th><th>Type</th><th>Notes</th></tr></thead>
<tbody>
<tr><td class="name">display</td><td class="type">block | inline | inline-block | flex | inline-flex | grid | inline-grid | contents | none</td><td>—</td></tr>
<tr><td class="name">position</td><td class="type">static | relative | absolute | fixed | sticky</td><td>—</td></tr>
<tr><td class="name">top / right / bottom / left</td><td class="type">number | string</td><td>Number → px.</td></tr>
<tr><td class="name">inset / insetX / insetY</td><td class="type">number | string</td><td>Shorthand insets.</td></tr>
<tr><td class="name">overflow / overflowX / overflowY</td><td class="type">visible | hidden | clip | scroll | auto</td><td>—</td></tr>
</tbody>
</table>
</div>
<div data-uix-subsection-head>Flex / grid item</div>
<div data-uix-table-wrap>
<table data-uix-table>
<thead><tr><th>Prop</th><th>Type</th><th>Notes</th></tr></thead>
<tbody>
<tr><td class="name">flex / grow / shrink</td><td class="type">number | string</td><td>Item-side flex props.</td></tr>
<tr><td class="name">basis</td><td class="type">number | string</td><td>Number → px.</td></tr>
<tr><td class="name">order</td><td class="type">number</td><td>—</td></tr>
<tr><td class="name">alignSelf</td><td class="type">auto | start | end | center | stretch | baseline</td><td>—</td></tr>
<tr><td class="name">justifySelf</td><td class="type">auto | start | end | center | stretch</td><td>—</td></tr>
<tr><td class="name">placeSelf</td><td class="type">string</td><td>Shorthand for align-self + justify-self.</td></tr>
<tr><td class="name">gridColumn / gridRow</td><td class="type">string</td><td>Grid item placement. e.g. <code>"1 / 3"</code>, <code>"span 2"</code>.</td></tr>
<tr><td class="name">gridArea</td><td class="type">string</td><td>Named grid area, e.g. <code>"header"</code>.</td></tr>
</tbody>
</table>
</div>
<div data-uix-subsection-head>Escape hatch</div>
<div data-uix-table-wrap>
<table data-uix-table>
<thead><tr><th>Prop</th><th>Type</th><th>Notes</th></tr></thead>
<tbody>
<tr><td class="name">style</td><td class="type">string</td><td>Extra inline CSS appended after Box's variable declarations. Use for one-offs the prop surface doesn't cover.</td></tr>
</tbody>
</table>
</div>
<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>&lt;Box&gt;</code></td><td>Radix exposes spacing props as <code>p</code>/<code>px</code> shorthands; UIX uses full names. Both compile to CSS vars.</td></tr>
<tr><td class="name">chakra-ui</td><td><code>&lt;Box&gt;</code></td><td>Chakra mixes the style-props system with theme aliases (<code>bg</code>, <code>color</code>); UIX Box is layout-only — color / typography live elsewhere.</td></tr>
<tr><td class="name">mantine</td><td><code>&lt;Box&gt;</code></td><td>Mantine compiles styles to classnames; UIX writes inline CSS variables for transparent debugging.</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>{boxMorfo.name}</td></tr>
<tr><td class="name">kebab</td><td><code>{boxMorfo.kebab}</code></td></tr>
<tr><td class="name">scope</td><td>{boxMorfo.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> <tr>
<td><code>display</code></td> <td class="name">{part.kebab}</td>
<td>LayoutDisplay</td> <td><code data-uix-part-marker>[{part.marker}]</code></td>
<td>block · inline · flex · grid · …</td> <td class="type">&lt;{part.defaultElement}&gt;</td>
</tr> <td class="type">{part.archetype}</td>
<tr> <td class="default">{part.optional ? 'yes' : 'no'}</td>
<td><code>width</code> / <code>minWidth</code> / <code>maxWidth</code></td>
<td>number | string</td>
<td>number → px, string passes through</td>
</tr>
<tr>
<td><code>height</code> / <code>minHeight</code> / <code>maxHeight</code></td>
<td>number | string</td>
<td>same as above</td>
</tr>
<tr>
<td><code>padding</code> / <code>paddingX</code> / <code>paddingY</code></td>
<td>number | string</td>
<td>number → <code>var(--space-N)</code></td>
</tr>
<tr>
<td>
<code>paddingTop</code> / <code>paddingRight</code> /
<code>paddingBottom</code> / <code>paddingLeft</code>
</td>
<td>number | string</td>
<td>per-side overrides</td>
</tr>
<tr>
<td><code>margin</code> / <code>marginX</code> / <code>marginY</code> / sides</td>
<td>number | string</td>
<td>same mapping as padding</td>
</tr>
<tr>
<td><code>gap</code></td>
<td>number | string</td>
<td>for flex/grid containers</td>
</tr>
<tr>
<td>
<code>position</code> / <code>top</code> / <code>right</code> /
<code>bottom</code> / <code>left</code>
</td>
<td>various</td>
<td>position + insets</td>
</tr>
<tr>
<td><code>inset</code> / <code>insetX</code> / <code>insetY</code></td>
<td>number | string</td>
<td>shorthand insets</td>
</tr>
<tr>
<td><code>overflow</code> / <code>overflowX</code> / <code>overflowY</code></td>
<td>LayoutOverflow</td>
<td>visible · hidden · clip · scroll · auto</td>
</tr> </tr>
{/each}
</tbody>
</table>
</div>
<p data-uix-section-desc style="margin-top: var(--uix-space-4);">
The Provider part emits only the <code>data-box</code> marker — no states, no data
properties, no aria attributes, no keyboard. The recipe consumes the
<code>--box-*</code> CSS variables the component writes inline.
</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>
Box declares no semantic events. As a passive layout primitive, it does not commit, emerge,
or react to anything — it just styles its children. Components that animate or change state
on appearance should compose Box 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/box/box.css</code>. Every property reads
its own <code>--box-*</code> custom property and falls back via
<code>revert-layer</code> so consumers can mix Box with any other styling.
</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> <tr>
<td> <td class="name"><code>[data-box]</code></td>
<code>flex</code> / <code>grow</code> / <code>shrink</code> / <code>basis</code> / <td><span data-uix-tag data-kind="morfo">morfo</span></td>
<code>order</code> / <code>alignSelf</code> / <code>justifySelf</code> <td>Provider marker. Single root element emitted by the component.</td>
</td>
<td>various</td>
<td>item-side flex/grid props</td>
</tr> </tr>
<tr> <tr>
<td><code>style</code></td> <td class="name"><code>[data-box] {`{ display: var(--box-display, revert-layer); … }`}</code></td>
<td>string</td> <td><span data-uix-tag data-kind="eidos">eidos</span></td>
<td>extra inline style; merged after Box vars</td> <td>Read every <code>--box-*</code> custom property, fall back to the cascade when unset.</td>
</tr> </tr>
</tbody> </tbody>
</table> </table>
</div>
</section> </section>
{/if}
<section> {#if tab === 'a11y'}
<h2>Reference</h2> <section data-uix-section>
<ul> <h2 data-uix-section-title>Accessibility</h2>
<li> <div data-uix-table-wrap>
<strong>radix-themes Box</strong> — same idea: token-driven shorthand for the <table data-uix-table>
box model; we mirror the responsive prop shape. <thead><tr><th>Concern</th><th>Contract</th></tr></thead>
</li> <tbody>
<li> <tr><td class="name">Role</td><td>None implicit. Default element is <code>&lt;div&gt;</code> — semantically neutral. Use a semantic wrapper (<code>&lt;main&gt;</code>, <code>&lt;section&gt;</code>, <code>&lt;article&gt;</code>) around Box when content needs a landmark.</td></tr>
<strong>chakra-ui Box</strong> — origin of the per-side / shorthand prop split <tr><td class="name">Label</td><td>Not applicable — Box has no content semantics. Labels belong to the interactive child the Box wraps.</td></tr>
(<code>paddingX</code>, <code>paddingY</code>, …). <tr><td class="name">Keyboard</td><td>Box is not focusable. Tab order follows children.</td></tr>
</li> <tr><td class="name">Focus visible</td><td>Box does not paint a focus ring. Focus styling belongs to the interactive children.</td></tr>
</ul> </tbody>
</table>
</div>
</section> </section>
{/if}
</div> </div>

Loading…
Cancel
Save

Powered by TurnKey Linux.