diff --git a/src/uix/eidos/components/_layout/index.ts b/src/uix/eidos/components/_layout/index.ts
new file mode 100644
index 000000000..315bc7535
--- /dev/null
+++ b/src/uix/eidos/components/_layout/index.ts
@@ -0,0 +1,45 @@
+/**
+ * Layout namespace — re-exports the 8 layout primitives as a single
+ * group for consumers who prefer the dotted style:
+ *
+ * import { Layout } from '$uix/eidos/components/_layout';
+ * …
+ * …
+ *
+ * The individual barrels still work and are the preferred form for
+ * tree-shaken imports:
+ *
+ * import { Box } from '$uix/eidos/components/box';
+ */
+export { default as Box } from '../box';
+export { default as Flex } from '../flex';
+export { default as Grid } from '../grid';
+export { default as Stack } from '../stack';
+export { default as Group } from '../group';
+export { default as Wrap } from '../wrap';
+export { default as Container } from '../container';
+export { default as Section } from '../section';
+
+export type { BoxProps } from '../box';
+export type { FlexProps } from '../flex';
+export type { GridProps } from '../grid';
+export type { StackProps, StackDirection } from '../stack';
+export type { GroupProps } from '../group';
+export type { WrapProps } from '../wrap';
+export type { ContainerProps, ContainerSize, ContainerAlign } from '../container';
+export type { SectionProps, SectionSize } from '../section';
+
+export type {
+ LayoutSpaceValue,
+ LayoutLengthValue,
+ LayoutDisplay,
+ LayoutOverflow,
+ LayoutPosition,
+ LayoutAlignSelf,
+ LayoutJustifySelf,
+ LayoutFlexDirection,
+ LayoutFlexWrap,
+ LayoutAlignItems,
+ LayoutJustifyContent,
+ LayoutGridAutoFlow
+} from './shared';
diff --git a/src/uix/eidos/components/_layout/shared.ts b/src/uix/eidos/components/_layout/shared.ts
new file mode 100644
index 000000000..a30d1666f
--- /dev/null
+++ b/src/uix/eidos/components/_layout/shared.ts
@@ -0,0 +1,128 @@
+/**
+ * Shared layout helpers for eidos layout primitives.
+ *
+ * Ported from the legacy `air/components/layout/shared.ts` with one
+ * architectural shift: numeric space values now emit `var(--space-N)`
+ * tokens from the eidos foundation (`src/uix/eidos/generated/base.css`),
+ * not the retired `--air-space-N` aliases.
+ *
+ * formatLayoutSpace(2) -> 'var(--space-2)'
+ * formatLayoutSpace('1rem') -> '1rem'
+ * formatLayoutLength(120) -> '120px'
+ * formatLayoutLength('50%') -> '50%'
+ * formatLayoutRaw(3) -> '3'
+ *
+ * The shape is identical to air's helpers so the per-primitive Svelte
+ * components below stay tiny — they just push CSS custom properties
+ * into a declarations list and join.
+ */
+
+export type LayoutSpaceValue = number | string;
+export type LayoutLengthValue = number | string;
+export type LayoutDisplay =
+ | 'block'
+ | 'inline'
+ | 'inline-block'
+ | 'flex'
+ | 'inline-flex'
+ | 'grid'
+ | 'inline-grid'
+ | 'contents'
+ | 'none';
+export type LayoutOverflow = 'visible' | 'hidden' | 'clip' | 'scroll' | 'auto';
+export type LayoutPosition = 'static' | 'relative' | 'absolute' | 'fixed' | 'sticky';
+export type LayoutAlignSelf =
+ | 'auto'
+ | 'start'
+ | 'end'
+ | 'center'
+ | 'stretch'
+ | 'flex-start'
+ | 'flex-end'
+ | 'baseline';
+export type LayoutJustifySelf = 'auto' | 'start' | 'end' | 'center' | 'stretch';
+export type LayoutFlexDirection = 'row' | 'row-reverse' | 'column' | 'column-reverse';
+export type LayoutFlexWrap = 'nowrap' | 'wrap' | 'wrap-reverse';
+export type LayoutAlignItems =
+ | 'stretch'
+ | 'start'
+ | 'end'
+ | 'center'
+ | 'baseline'
+ | 'flex-start'
+ | 'flex-end';
+export type LayoutJustifyContent =
+ | 'start'
+ | 'end'
+ | 'center'
+ | 'stretch'
+ | 'space-between'
+ | 'space-around'
+ | 'space-evenly'
+ | 'flex-start'
+ | 'flex-end';
+export type LayoutGridAutoFlow = 'row' | 'column' | 'dense' | 'row dense' | 'column dense';
+
+function parseNumberish(value: number | string): number | undefined {
+ if (typeof value === 'number') return Number.isFinite(value) ? value : undefined;
+ if (typeof value !== 'string') return undefined;
+ const normalized = value.trim();
+ if (!normalized) return undefined;
+ if (!/^-?\d+(\.\d+)?$/.test(normalized)) return undefined;
+ const parsed = Number(normalized);
+ return Number.isFinite(parsed) ? parsed : undefined;
+}
+
+/**
+ * Format a space-scale value (gap, padding, margin). Numbers map to the
+ * `--space-N` token; strings are emitted verbatim so consumers can pass
+ * `'1rem'`, `'auto'`, or a custom `var(--…)` chain.
+ */
+export function formatLayoutSpace(value: LayoutSpaceValue | undefined): string | undefined {
+ if (value === undefined || value === null || value === '') return undefined;
+ const parsed = parseNumberish(value);
+ return parsed !== undefined ? `var(--space-${parsed})` : String(value);
+}
+
+/**
+ * Format a length value (width, height, inset). Numbers become px;
+ * strings pass through.
+ */
+export function formatLayoutLength(value: LayoutLengthValue | undefined): string | undefined {
+ if (value === undefined || value === null || value === '') return undefined;
+ const parsed = parseNumberish(value);
+ return parsed !== undefined ? `${parsed}px` : String(value);
+}
+
+/**
+ * Format a raw value (flex, grow, shrink, order). Always stringifies.
+ */
+export function formatLayoutRaw(value: string | number | undefined): string | undefined {
+ if (value === undefined || value === null || value === '') return undefined;
+ return String(value);
+}
+
+/**
+ * Append a CSS custom-property declaration to the list, but only when the
+ * value is defined. Keeps the resulting `style=""` attribute compact —
+ * primitives don't emit dozens of `--…: ;` no-ops.
+ */
+export function pushStyleVar(
+ declarations: string[],
+ name: `--${string}`,
+ value: string | undefined
+): void {
+ if (!value) return;
+ declarations.push(`${name}:${value}`);
+}
+
+/**
+ * Compose a final style string from a list of `--var:value` declarations
+ * and an optional caller-provided `style` prop.
+ */
+export function composeStyle(declarations: string[], style: string | undefined): string {
+ const own = declarations.join(';');
+ if (!style) return own;
+ if (!own) return style;
+ return `${own};${style}`;
+}
diff --git a/src/uix/eidos/components/box/box.css b/src/uix/eidos/components/box/box.css
new file mode 100644
index 000000000..83d6b1466
--- /dev/null
+++ b/src/uix/eidos/components/box/box.css
@@ -0,0 +1,83 @@
+/*
+ * Box recipe — universal box-model utility. Every CSS property falls back
+ * to `revert-layer` so unset Box props inherit the normal cascade rather
+ * than forcing an initial value.
+ *
+ * Cascade pattern (padding example):
+ * padding-top = --box-padding-top
+ * || --box-padding-block
+ * || --box-padding
+ * || revert-layer
+ *
+ * The shorthand props (`padding`, `paddingX`, `paddingY`, `margin`,
+ * `marginX`, `marginY`, `inset`, `insetX`, `insetY`) supply default
+ * values for the per-side props at the CSS layer, not in JS — so a
+ * caller passing both `padding={4}` and `paddingTop={6}` gets exactly
+ * the expected behavior without `box.svelte` needing to know about
+ * shorthand resolution.
+ */
+
+[data-box] {
+ box-sizing: border-box;
+ min-width: 0;
+
+ display: var(--box-display, revert-layer);
+ position: var(--box-position, revert-layer);
+ width: var(--box-width, revert-layer);
+ min-width: var(--box-min-width, 0);
+ max-width: var(--box-max-width, revert-layer);
+ height: var(--box-height, revert-layer);
+ min-height: var(--box-min-height, revert-layer);
+ max-height: var(--box-max-height, revert-layer);
+
+ padding-top: var(
+ --box-padding-top,
+ var(--box-padding-block, var(--box-padding, revert-layer))
+ );
+ padding-right: var(
+ --box-padding-right,
+ var(--box-padding-inline, var(--box-padding, revert-layer))
+ );
+ padding-bottom: var(
+ --box-padding-bottom,
+ var(--box-padding-block, var(--box-padding, revert-layer))
+ );
+ padding-left: var(
+ --box-padding-left,
+ var(--box-padding-inline, var(--box-padding, revert-layer))
+ );
+
+ margin-top: var(--box-margin-top, var(--box-margin-block, var(--box-margin, revert-layer)));
+ margin-right: var(
+ --box-margin-right,
+ var(--box-margin-inline, var(--box-margin, revert-layer))
+ );
+ margin-bottom: var(
+ --box-margin-bottom,
+ var(--box-margin-block, var(--box-margin, revert-layer))
+ );
+ margin-left: var(--box-margin-left, var(--box-margin-inline, var(--box-margin, revert-layer)));
+
+ gap: var(--box-gap, revert-layer);
+
+ top: var(--box-top, revert-layer);
+ right: var(--box-right, revert-layer);
+ bottom: var(--box-bottom, revert-layer);
+ left: var(--box-left, revert-layer);
+ inset-block-start: var(--box-top, var(--box-inset-block, var(--box-inset, revert-layer)));
+ inset-inline-end: var(--box-right, var(--box-inset-inline, var(--box-inset, revert-layer)));
+ inset-block-end: var(--box-bottom, var(--box-inset-block, var(--box-inset, revert-layer)));
+ inset-inline-start: var(--box-left, var(--box-inset-inline, var(--box-inset, revert-layer)));
+
+ overflow: var(--box-overflow, revert-layer);
+ overflow-x: var(--box-overflow-x, var(--box-overflow, revert-layer));
+ overflow-y: var(--box-overflow-y, var(--box-overflow, revert-layer));
+
+ flex: var(--box-flex, revert-layer);
+ flex-grow: var(--box-grow, revert-layer);
+ flex-shrink: var(--box-shrink, revert-layer);
+ flex-basis: var(--box-basis, revert-layer);
+ order: var(--box-order, revert-layer);
+ align-self: var(--box-align-self, revert-layer);
+ justify-self: var(--box-justify-self, revert-layer);
+}
diff --git a/src/uix/eidos/components/box/box.svelte b/src/uix/eidos/components/box/box.svelte
new file mode 100644
index 000000000..a79e3a621
--- /dev/null
+++ b/src/uix/eidos/components/box/box.svelte
@@ -0,0 +1,129 @@
+
+
+
+ {@render children?.()}
+
diff --git a/src/uix/eidos/components/box/index.ts b/src/uix/eidos/components/box/index.ts
new file mode 100644
index 000000000..1f96a6ffa
--- /dev/null
+++ b/src/uix/eidos/components/box/index.ts
@@ -0,0 +1,10 @@
+// Box — universal box-model utility.
+//
+// import { Box } from '$uix/eidos/components/box';
+//
+// …
+import Box from './box.svelte';
+
+export { Box };
+export default Box;
+export type { BoxProps } from './types';
diff --git a/src/uix/eidos/components/box/types.ts b/src/uix/eidos/components/box/types.ts
new file mode 100644
index 000000000..b404a0307
--- /dev/null
+++ b/src/uix/eidos/components/box/types.ts
@@ -0,0 +1,86 @@
+import type { Snippet } from 'svelte';
+import type { HTMLAttributes } from 'svelte/elements';
+import type { ResponsiveProp } from '$uix/eidos/lib/types';
+import type {
+ LayoutAlignSelf,
+ LayoutDisplay,
+ LayoutJustifySelf,
+ LayoutLengthValue,
+ LayoutOverflow,
+ LayoutPosition,
+ LayoutSpaceValue
+} from '../_layout/shared';
+
+export type BoxProps = Omit, 'style' | 'children'> & {
+ /** `display`. */
+ display?: ResponsiveProp;
+ /** `width`. Number -> px, string passes through. */
+ width?: ResponsiveProp;
+ /** `min-width`. */
+ minWidth?: ResponsiveProp;
+ /** `max-width`. */
+ maxWidth?: ResponsiveProp;
+ /** `height`. */
+ height?: ResponsiveProp;
+ /** `min-height`. */
+ minHeight?: ResponsiveProp;
+ /** `max-height`. */
+ maxHeight?: ResponsiveProp;
+ /** `padding`. Number -> `--space-N`, string passes through. */
+ padding?: ResponsiveProp;
+ /** `padding-inline` (left+right). */
+ paddingX?: ResponsiveProp;
+ /** `padding-block` (top+bottom). */
+ paddingY?: ResponsiveProp;
+ paddingTop?: ResponsiveProp;
+ paddingRight?: ResponsiveProp;
+ paddingBottom?: ResponsiveProp;
+ paddingLeft?: ResponsiveProp;
+ /** `margin`. */
+ margin?: ResponsiveProp;
+ /** `margin-inline`. */
+ marginX?: ResponsiveProp;
+ /** `margin-block`. */
+ marginY?: ResponsiveProp;
+ marginTop?: ResponsiveProp;
+ marginRight?: ResponsiveProp;
+ marginBottom?: ResponsiveProp;
+ marginLeft?: ResponsiveProp;
+ /** `gap` (flex/grid). */
+ gap?: ResponsiveProp;
+ /** `position`. */
+ position?: ResponsiveProp;
+ top?: ResponsiveProp;
+ right?: ResponsiveProp;
+ bottom?: ResponsiveProp;
+ left?: ResponsiveProp;
+ /** Shorthand for all four insets. */
+ inset?: ResponsiveProp;
+ /** `inset-inline` (left+right). */
+ insetX?: ResponsiveProp;
+ /** `inset-block` (top+bottom). */
+ insetY?: ResponsiveProp;
+ /** `overflow`. */
+ overflow?: ResponsiveProp;
+ overflowX?: ResponsiveProp;
+ overflowY?: ResponsiveProp;
+ /** Flex item: `flex` shorthand. */
+ flex?: ResponsiveProp;
+ /** Flex item: `flex-grow`. */
+ grow?: ResponsiveProp;
+ /** Flex item: `flex-shrink`. */
+ shrink?: ResponsiveProp;
+ /** Flex item: `flex-basis`. */
+ basis?: ResponsiveProp;
+ /** Flex/grid item: `order`. */
+ order?: ResponsiveProp;
+ /** Flex/grid item: `align-self`. */
+ alignSelf?: ResponsiveProp;
+ /** Grid item: `justify-self`. */
+ justifySelf?: ResponsiveProp;
+ /** Extra inline style. Merged after the box's own CSS variable declarations. */
+ style?: string;
+ /** Extra class names. */
+ class?: string;
+ children?: Snippet;
+};
diff --git a/src/uix/eidos/components/container/container.css b/src/uix/eidos/components/container/container.css
new file mode 100644
index 000000000..eb9a9a2b2
--- /dev/null
+++ b/src/uix/eidos/components/container/container.css
@@ -0,0 +1,13 @@
+/*
+ * Container recipe — all sizing/alignment is driven via the Box prop
+ * channel (maxWidth, marginLeft/Right). The dedicated selector exists
+ * so consumers can target `[data-container]` for archetype-level
+ * adjustments without needing to know the Box marker.
+ *
+ * Width and padding fall back to foundation tokens when the JS layer
+ * hasn't pushed them (e.g. SSR snapshots before hydration).
+ */
+
+[data-box][data-container] {
+ max-width: var(--box-max-width, var(--container-width-xl));
+}
diff --git a/src/uix/eidos/components/container/container.svelte b/src/uix/eidos/components/container/container.svelte
new file mode 100644
index 000000000..0cec2663b
--- /dev/null
+++ b/src/uix/eidos/components/container/container.svelte
@@ -0,0 +1,65 @@
+
+
+
+ {@render children?.()}
+
diff --git a/src/uix/eidos/components/container/index.ts b/src/uix/eidos/components/container/index.ts
new file mode 100644
index 000000000..73f229df7
--- /dev/null
+++ b/src/uix/eidos/components/container/index.ts
@@ -0,0 +1,10 @@
+// Container — max-width centered content container.
+//
+// import { Container } from '$uix/eidos/components/container';
+//
+// …
+import Container from './container.svelte';
+
+export { Container };
+export default Container;
+export type { ContainerProps, ContainerSize, ContainerAlign } from './types';
diff --git a/src/uix/eidos/components/container/types.ts b/src/uix/eidos/components/container/types.ts
new file mode 100644
index 000000000..a883194a1
--- /dev/null
+++ b/src/uix/eidos/components/container/types.ts
@@ -0,0 +1,16 @@
+import type { ResponsiveProp, Size } from '$uix/eidos/lib/types';
+import type { BoxProps } from '../box/types';
+
+export type ContainerSize = Extract;
+export type ContainerAlign = 'left' | 'center' | 'right';
+
+export type ContainerProps = BoxProps & {
+ /**
+ * Max content width. Maps to the `--container-width-{size}` tokens
+ * defined in the eidos foundation. `full` removes the cap.
+ * @default 'xl'
+ */
+ size?: ResponsiveProp;
+ /** Horizontal alignment within the parent. @default 'center' */
+ align?: ResponsiveProp;
+};
diff --git a/src/uix/eidos/components/flex/flex.css b/src/uix/eidos/components/flex/flex.css
new file mode 100644
index 000000000..570010fbc
--- /dev/null
+++ b/src/uix/eidos/components/flex/flex.css
@@ -0,0 +1,14 @@
+/*
+ * Flex recipe — additional CSS variables layered on top of the Box
+ * recipe. `row-gap` and `column-gap` fall back to the Box-level `--box-gap`
+ * so callers can use `gap={N}` to set both axes at once.
+ */
+
+[data-box][data-flex] {
+ flex-direction: var(--flex-direction, row);
+ align-items: var(--flex-align, stretch);
+ justify-content: var(--flex-justify, flex-start);
+ flex-wrap: var(--flex-wrap, nowrap);
+ row-gap: var(--flex-row-gap, var(--box-gap, revert-layer));
+ column-gap: var(--flex-column-gap, var(--box-gap, revert-layer));
+}
diff --git a/src/uix/eidos/components/flex/flex.svelte b/src/uix/eidos/components/flex/flex.svelte
new file mode 100644
index 000000000..e1b20ce71
--- /dev/null
+++ b/src/uix/eidos/components/flex/flex.svelte
@@ -0,0 +1,55 @@
+
+
+
+ {@render children?.()}
+
diff --git a/src/uix/eidos/components/flex/index.ts b/src/uix/eidos/components/flex/index.ts
new file mode 100644
index 000000000..e3659fbab
--- /dev/null
+++ b/src/uix/eidos/components/flex/index.ts
@@ -0,0 +1,10 @@
+// Flex — display:flex container.
+//
+// import { Flex } from '$uix/eidos/components/flex';
+//
+// …
+import Flex from './flex.svelte';
+
+export { Flex };
+export default Flex;
+export type { FlexProps } from './types';
diff --git a/src/uix/eidos/components/flex/types.ts b/src/uix/eidos/components/flex/types.ts
new file mode 100644
index 000000000..9a206cf07
--- /dev/null
+++ b/src/uix/eidos/components/flex/types.ts
@@ -0,0 +1,26 @@
+import type { ResponsiveProp } from '$uix/eidos/lib/types';
+import type { BoxProps } from '../box/types';
+import type {
+ LayoutAlignItems,
+ LayoutFlexDirection,
+ LayoutFlexWrap,
+ LayoutJustifyContent,
+ LayoutSpaceValue
+} from '../_layout/shared';
+
+export type FlexProps = Omit & {
+ /** Use `inline-flex` instead of `flex`. @default false */
+ inline?: boolean;
+ /** `flex-direction`. @default 'row' */
+ direction?: ResponsiveProp;
+ /** `align-items`. */
+ align?: ResponsiveProp;
+ /** `justify-content`. */
+ justify?: ResponsiveProp;
+ /** `flex-wrap`. @default 'nowrap' */
+ wrap?: ResponsiveProp;
+ /** `row-gap`. Overrides `gap` for the row axis. */
+ rowGap?: ResponsiveProp;
+ /** `column-gap`. Overrides `gap` for the column axis. */
+ columnGap?: ResponsiveProp;
+};
diff --git a/src/uix/eidos/components/grid/grid.css b/src/uix/eidos/components/grid/grid.css
new file mode 100644
index 000000000..99299e802
--- /dev/null
+++ b/src/uix/eidos/components/grid/grid.css
@@ -0,0 +1,23 @@
+/*
+ * Grid recipe — additional CSS variables layered on top of the Box
+ * recipe. Track and placement props (`place-items`, `place-content`,
+ * `grid-column`, `grid-row`, `grid-area`) fall through to `revert-layer`
+ * so they only apply when explicitly set.
+ */
+
+[data-box][data-grid] {
+ grid-template-columns: var(--grid-template-columns, none);
+ grid-template-rows: var(--grid-template-rows, none);
+ grid-auto-columns: var(--grid-auto-columns, auto);
+ grid-auto-rows: var(--grid-auto-rows, auto);
+ grid-auto-flow: var(--grid-auto-flow, row);
+ align-items: var(--grid-align, stretch);
+ justify-content: var(--grid-justify, start);
+ place-items: var(--grid-place-items, 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));
+ column-gap: var(--grid-column-gap, var(--grid-gap, revert-layer));
+}
diff --git a/src/uix/eidos/components/grid/grid.svelte b/src/uix/eidos/components/grid/grid.svelte
new file mode 100644
index 000000000..01c3399e5
--- /dev/null
+++ b/src/uix/eidos/components/grid/grid.svelte
@@ -0,0 +1,63 @@
+
+
+
+ {@render children?.()}
+
diff --git a/src/uix/eidos/components/grid/index.ts b/src/uix/eidos/components/grid/index.ts
new file mode 100644
index 000000000..6eabb3ed3
--- /dev/null
+++ b/src/uix/eidos/components/grid/index.ts
@@ -0,0 +1,10 @@
+// Grid — display:grid container.
+//
+// import { Grid } from '$uix/eidos/components/grid';
+//
+// …
+import Grid from './grid.svelte';
+
+export { Grid };
+export default Grid;
+export type { GridProps } from './types';
diff --git a/src/uix/eidos/components/grid/types.ts b/src/uix/eidos/components/grid/types.ts
new file mode 100644
index 000000000..b1e4bad0f
--- /dev/null
+++ b/src/uix/eidos/components/grid/types.ts
@@ -0,0 +1,41 @@
+import type { ResponsiveProp } from '$uix/eidos/lib/types';
+import type { BoxProps } from '../box/types';
+import type {
+ LayoutAlignItems,
+ LayoutGridAutoFlow,
+ LayoutJustifyContent,
+ LayoutSpaceValue
+} from '../_layout/shared';
+
+export type GridProps = Omit & {
+ /** `grid-template-columns`. */
+ templateColumns?: ResponsiveProp;
+ /** `grid-template-rows`. */
+ templateRows?: ResponsiveProp;
+ /** `grid-auto-columns`. */
+ autoColumns?: ResponsiveProp;
+ /** `grid-auto-rows`. */
+ autoRows?: ResponsiveProp;
+ /** `grid-auto-flow`. */
+ autoFlow?: ResponsiveProp;
+ /** `column-gap`. */
+ columnGap?: ResponsiveProp;
+ /** `row-gap`. */
+ rowGap?: ResponsiveProp;
+ /** Shorthand for both `row-gap` and `column-gap`. */
+ gap?: ResponsiveProp;
+ /** `align-items`. */
+ align?: ResponsiveProp;
+ /** `justify-content`. */
+ justify?: ResponsiveProp;
+ /** `place-items` shorthand. */
+ placeItems?: ResponsiveProp;
+ /** `place-content` shorthand. */
+ placeContent?: ResponsiveProp;
+ /** Grid item: `grid-column`. */
+ gridColumn?: ResponsiveProp;
+ /** Grid item: `grid-row`. */
+ gridRow?: ResponsiveProp;
+ /** Grid item: `grid-area`. */
+ gridArea?: ResponsiveProp;
+};
diff --git a/src/uix/eidos/components/group/group.css b/src/uix/eidos/components/group/group.css
new file mode 100644
index 000000000..8bfec804a
--- /dev/null
+++ b/src/uix/eidos/components/group/group.css
@@ -0,0 +1,49 @@
+/*
+ * Group recipe — cluster behavior driven by `data-grow` and
+ * `data-attached`. The attached overlap defaults to 1px so adjacent
+ * 1px borders collapse cleanly; consumers can override via
+ * `--group-attached-overlap`.
+ */
+
+[data-box][data-flex][data-group] {
+ --group-attached-overlap: 1px;
+}
+
+[data-box][data-flex][data-group][data-grow] > * {
+ flex: 1 1 0;
+ min-width: 0;
+}
+
+/* ── Attached: row direction ───────────────────────────────────────── */
+[data-box][data-flex][data-group][data-attached]:not([data-direction='column']):not(
+ [data-direction='column-reverse']
+ )
+ > :not(:first-child) {
+ margin-inline-start: calc(var(--group-attached-overlap) * -1);
+ border-start-start-radius: 0 !important;
+ border-end-start-radius: 0 !important;
+}
+
+[data-box][data-flex][data-group][data-attached]:not([data-direction='column']):not(
+ [data-direction='column-reverse']
+ )
+ > :not(:last-child) {
+ border-start-end-radius: 0 !important;
+ border-end-end-radius: 0 !important;
+}
+
+/* ── Attached: column direction ────────────────────────────────────── */
+[data-box][data-flex][data-group][data-attached][data-direction='column'] > :not(:first-child),
+[data-box][data-flex][data-group][data-attached][data-direction='column-reverse']
+ > :not(:first-child) {
+ margin-block-start: calc(var(--group-attached-overlap) * -1);
+ border-start-start-radius: 0 !important;
+ border-start-end-radius: 0 !important;
+}
+
+[data-box][data-flex][data-group][data-attached][data-direction='column'] > :not(:last-child),
+[data-box][data-flex][data-group][data-attached][data-direction='column-reverse']
+ > :not(:last-child) {
+ border-end-start-radius: 0 !important;
+ border-end-end-radius: 0 !important;
+}
diff --git a/src/uix/eidos/components/group/group.svelte b/src/uix/eidos/components/group/group.svelte
new file mode 100644
index 000000000..ecfe4e048
--- /dev/null
+++ b/src/uix/eidos/components/group/group.svelte
@@ -0,0 +1,43 @@
+
+
+
+ {@render children?.()}
+
diff --git a/src/uix/eidos/components/group/index.ts b/src/uix/eidos/components/group/index.ts
new file mode 100644
index 000000000..a328cd476
--- /dev/null
+++ b/src/uix/eidos/components/group/index.ts
@@ -0,0 +1,12 @@
+// Group — inline cluster (flex row by default).
+//
+// import { Group } from '$uix/eidos/components/group';
+//
+// …
+// …
+// …
+import Group from './group.svelte';
+
+export { Group };
+export default Group;
+export type { GroupProps } from './types';
diff --git a/src/uix/eidos/components/group/types.ts b/src/uix/eidos/components/group/types.ts
new file mode 100644
index 000000000..d18c96c84
--- /dev/null
+++ b/src/uix/eidos/components/group/types.ts
@@ -0,0 +1,19 @@
+import type { ResponsiveProp } from '$uix/eidos/lib/types';
+import type { FlexProps } from '../flex/types';
+
+export type GroupProps = Omit & {
+ /**
+ * Make every child flex to equal width (`flex: 1 1 0`). Useful for
+ * toolbars where buttons should fill the row evenly.
+ * @default false
+ */
+ grow?: boolean;
+ /**
+ * Attach children edge-to-edge, hiding adjacent borders/radii to
+ * produce a single visual unit (segmented control, button group).
+ * @default false
+ */
+ attached?: boolean;
+ /** @default 'row' */
+ direction?: ResponsiveProp<'row' | 'column'>;
+};
diff --git a/src/uix/eidos/components/section/index.ts b/src/uix/eidos/components/section/index.ts
new file mode 100644
index 000000000..ab4ac2643
--- /dev/null
+++ b/src/uix/eidos/components/section/index.ts
@@ -0,0 +1,10 @@
+// Section — top-level section with block-axis padding.
+//
+// import { Section } from '$uix/eidos/components/section';
+//
+//
+import Section from './section.svelte';
+
+export { Section };
+export default Section;
+export type { SectionProps, SectionSize } from './types';
diff --git a/src/uix/eidos/components/section/section.css b/src/uix/eidos/components/section/section.css
new file mode 100644
index 000000000..8ef0ada75
--- /dev/null
+++ b/src/uix/eidos/components/section/section.css
@@ -0,0 +1,20 @@
+/*
+ * Section recipe — defines the size-keyed padding tokens locally. These
+ * are intentionally not in the generated foundation: Section is the
+ * sole consumer and the values live with the component that owns the
+ * scale.
+ *
+ * sm -> 32px
+ * md -> 48px
+ * lg -> 64px
+ * xl -> 96px
+ */
+
+[data-box][data-section] {
+ --section-padding-block-sm: var(--space-8);
+ --section-padding-block-md: var(--space-12);
+ --section-padding-block-lg: var(--space-16);
+ --section-padding-block-xl: calc(var(--space-16) * 1.5);
+
+ inline-size: 100%;
+}
diff --git a/src/uix/eidos/components/section/section.svelte b/src/uix/eidos/components/section/section.svelte
new file mode 100644
index 000000000..fb754af59
--- /dev/null
+++ b/src/uix/eidos/components/section/section.svelte
@@ -0,0 +1,49 @@
+
+
+
+ {@render children?.()}
+
diff --git a/src/uix/eidos/components/section/types.ts b/src/uix/eidos/components/section/types.ts
new file mode 100644
index 000000000..cb2de4744
--- /dev/null
+++ b/src/uix/eidos/components/section/types.ts
@@ -0,0 +1,14 @@
+import type { ResponsiveProp, Size } from '$uix/eidos/lib/types';
+import type { BoxProps } from '../box/types';
+
+export type SectionSize = Extract;
+
+export type SectionProps = BoxProps & {
+ /**
+ * Vertical (block-axis) padding. Maps to size-scoped padding tokens
+ * defined in `section.css`. Override per-instance by passing
+ * `paddingY` directly.
+ * @default 'lg'
+ */
+ size?: ResponsiveProp;
+};
diff --git a/src/uix/eidos/components/stack/index.ts b/src/uix/eidos/components/stack/index.ts
new file mode 100644
index 000000000..88017bfee
--- /dev/null
+++ b/src/uix/eidos/components/stack/index.ts
@@ -0,0 +1,11 @@
+// Stack — vertical (default) or horizontal stack.
+//
+// import { Stack } from '$uix/eidos/components/stack';
+//
+// …
+// …
+import Stack from './stack.svelte';
+
+export { Stack };
+export default Stack;
+export type { StackProps, StackDirection } from './types';
diff --git a/src/uix/eidos/components/stack/stack.css b/src/uix/eidos/components/stack/stack.css
new file mode 100644
index 000000000..b01d118f4
--- /dev/null
+++ b/src/uix/eidos/components/stack/stack.css
@@ -0,0 +1,12 @@
+/*
+ * Stack recipe — no per-axis override; Stack reuses the Flex `gap`
+ * channel directly. Marker selector exists so consumers can target
+ * `[data-stack]` for archetype-level rules.
+ */
+
+[data-box][data-flex][data-stack] {
+ /* All visual behavior comes from Flex; this rule exists so the marker
+ * selector matches at least one declaration and isn't dropped by the
+ * build's empty-rule pruning. */
+ display: var(--box-display, flex);
+}
diff --git a/src/uix/eidos/components/stack/stack.svelte b/src/uix/eidos/components/stack/stack.svelte
new file mode 100644
index 000000000..0b8396885
--- /dev/null
+++ b/src/uix/eidos/components/stack/stack.svelte
@@ -0,0 +1,27 @@
+
+
+
+ {@render children?.()}
+
diff --git a/src/uix/eidos/components/stack/types.ts b/src/uix/eidos/components/stack/types.ts
new file mode 100644
index 000000000..a199c94ce
--- /dev/null
+++ b/src/uix/eidos/components/stack/types.ts
@@ -0,0 +1,24 @@
+import type { ResponsiveProp } from '$uix/eidos/lib/types';
+import type { FlexProps } from '../flex/types';
+import type {
+ LayoutAlignItems,
+ LayoutFlexDirection,
+ LayoutJustifyContent,
+ LayoutSpaceValue
+} from '../_layout/shared';
+
+export type StackDirection = Extract<
+ LayoutFlexDirection,
+ 'row' | 'row-reverse' | 'column' | 'column-reverse'
+>;
+
+export type StackProps = Omit<
+ FlexProps,
+ 'direction' | 'wrap' | 'inline' | 'rowGap' | 'columnGap'
+> & {
+ /** @default 'column' */
+ direction?: ResponsiveProp;
+ gap?: ResponsiveProp;
+ align?: ResponsiveProp;
+ justify?: ResponsiveProp;
+};
diff --git a/src/uix/eidos/components/wrap/index.ts b/src/uix/eidos/components/wrap/index.ts
new file mode 100644
index 000000000..ce5b40ee4
--- /dev/null
+++ b/src/uix/eidos/components/wrap/index.ts
@@ -0,0 +1,10 @@
+// Wrap — flex row that always wraps.
+//
+// import { Wrap } from '$uix/eidos/components/wrap';
+//
+// {#each tags as t}{t}{/each}
+import Wrap from './wrap.svelte';
+
+export { Wrap };
+export default Wrap;
+export type { WrapProps } from './types';
diff --git a/src/uix/eidos/components/wrap/types.ts b/src/uix/eidos/components/wrap/types.ts
new file mode 100644
index 000000000..43294c256
--- /dev/null
+++ b/src/uix/eidos/components/wrap/types.ts
@@ -0,0 +1,11 @@
+import type { ResponsiveProp } from '$uix/eidos/lib/types';
+import type { FlexProps } from '../flex/types';
+import type { LayoutAlignItems, LayoutJustifyContent, LayoutSpaceValue } from '../_layout/shared';
+
+export type WrapProps = Omit & {
+ gap?: ResponsiveProp;
+ rowGap?: ResponsiveProp;
+ columnGap?: ResponsiveProp;
+ align?: ResponsiveProp;
+ justify?: ResponsiveProp;
+};
diff --git a/src/uix/eidos/components/wrap/wrap.css b/src/uix/eidos/components/wrap/wrap.css
new file mode 100644
index 000000000..60827cb4f
--- /dev/null
+++ b/src/uix/eidos/components/wrap/wrap.css
@@ -0,0 +1,9 @@
+/*
+ * Wrap recipe — locks `flex-wrap: wrap` over the Flex default. The
+ * data-wrap attribute also gates any future archetype rules that want
+ * to distinguish "wrap by intent" from "wrap=wrap configured on a Flex".
+ */
+
+[data-box][data-flex][data-wrap] {
+ flex-wrap: wrap;
+}
diff --git a/src/uix/eidos/components/wrap/wrap.svelte b/src/uix/eidos/components/wrap/wrap.svelte
new file mode 100644
index 000000000..2b755b67d
--- /dev/null
+++ b/src/uix/eidos/components/wrap/wrap.svelte
@@ -0,0 +1,36 @@
+
+
+
+ {@render children?.()}
+
diff --git a/src/uix/eidos/index.css b/src/uix/eidos/index.css
index 586dddf40..21abbfce0 100644
--- a/src/uix/eidos/index.css
+++ b/src/uix/eidos/index.css
@@ -60,6 +60,15 @@
@import './events.css';
/* ── Component recipes ──────────────────────────────────────────────────── */
+/* Layout primitives load first so component recipes can layer on top of them. */
+@import './components/box/box.css';
+@import './components/flex/flex.css';
+@import './components/grid/grid.css';
+@import './components/stack/stack.css';
+@import './components/group/group.css';
+@import './components/wrap/wrap.css';
+@import './components/container/container.css';
+@import './components/section/section.css';
@import './components/icon/icon.css';
@import './components/avatar/avatar.css';
@import './components/breadcrumb/breadcrumb.css';
diff --git a/web/routes/uix/components/box/+page.svelte b/web/routes/uix/components/box/+page.svelte
new file mode 100644
index 000000000..2a2aa1aec
--- /dev/null
+++ b/web/routes/uix/components/box/+page.svelte
@@ -0,0 +1,133 @@
+
+
+
+
+
+
+ Live example
+
+
+ Cell A
+
+
+ Cell B
+
+
+ Cell C
+
+
+
+
+
+ Props
+
+
+
+ | Prop |
+ Type |
+ Notes |
+
+
+
+
+ display |
+ LayoutDisplay |
+ block · inline · flex · grid · … |
+
+
+ width / minWidth / maxWidth |
+ number | string |
+ number → px, string passes through |
+
+
+ height / minHeight / maxHeight |
+ number | string |
+ same as above |
+
+
+ padding / paddingX / paddingY |
+ number | string |
+ number → var(--space-N) |
+
+
+
+ paddingTop / paddingRight /
+ paddingBottom / paddingLeft
+ |
+ number | string |
+ per-side overrides |
+
+
+ margin / marginX / marginY / sides |
+ number | string |
+ same mapping as padding |
+
+
+ gap |
+ number | string |
+ for flex/grid containers |
+
+
+
+ position / top / right /
+ bottom / left
+ |
+ various |
+ position + insets |
+
+
+ inset / insetX / insetY |
+ number | string |
+ shorthand insets |
+
+
+ overflow / overflowX / overflowY |
+ LayoutOverflow |
+ visible · hidden · clip · scroll · auto |
+
+
+
+ flex / grow / shrink / basis /
+ order / alignSelf / justifySelf
+ |
+ various |
+ item-side flex/grid props |
+
+
+ style |
+ string |
+ extra inline style; merged after Box vars |
+
+
+
+
+
+
+ Reference
+
+ -
+ radix-themes Box — same idea: token-driven shorthand for the
+ box model; we mirror the responsive prop shape.
+
+ -
+ chakra-ui Box — origin of the per-side / shorthand prop split
+ (
paddingX, paddingY, …).
+
+
+
+
diff --git a/web/routes/uix/components/container/+page.svelte b/web/routes/uix/components/container/+page.svelte
new file mode 100644
index 000000000..b178ae533
--- /dev/null
+++ b/web/routes/uix/components/container/+page.svelte
@@ -0,0 +1,82 @@
+
+
+
+
+
+
+
+
+ Props
+
+
+
+ | Prop |
+ Default |
+ Notes |
+
+
+
+
+ size |
+ 'xl' |
+ sm · md · lg · xl · xxl · full |
+
+
+ align |
+ 'center' |
+ left · center · right |
+
+
+ paddingX |
+ var(--container-padding-inline) |
+ override per-instance |
+
+
+ … plus every BoxProps prop |
+
+
+
+
+
+
+ Reference
+
+ -
+ radix-themes Container — same surface; sizes map to fixed
+ pixel widths via tokens.
+
+ -
+ chakra-ui Container — origin of the centered max-width pattern.
+
+
+
+
diff --git a/web/routes/uix/components/flex/+page.svelte b/web/routes/uix/components/flex/+page.svelte
new file mode 100644
index 000000000..4fa80021d
--- /dev/null
+++ b/web/routes/uix/components/flex/+page.svelte
@@ -0,0 +1,95 @@
+
+
+
+
+
+
+ Live example
+
+ Start
+ Middle
+ End
+
+
+
+
+ Props
+
+
+
+ | Prop |
+ Default |
+ Notes |
+
+
+
+
+ inline |
+ false |
+ switch to inline-flex |
+
+
+ direction |
+ 'row' |
+ row · row-reverse · column · column-reverse |
+
+
+ wrap |
+ 'nowrap' |
+ nowrap · wrap · wrap-reverse |
+
+
+ align |
+ — |
+ stretch · start · center · end · baseline |
+
+
+ justify |
+ — |
+ start · center · end · space-between · space-around · space-evenly |
+
+
+ gap / rowGap / columnGap |
+ — |
+ row/columnGap override gap per axis |
+
+
+ … plus every BoxProps prop except display |
+
+
+
+
+
+
+ Reference
+
+ -
+ radix-themes Flex — same surface (
direction,
+ align, justify, wrap, gap).
+
+ -
+ chakra-ui Flex — origin of the responsive Flex container API;
+ our prop names match.
+
+
+
+
diff --git a/web/routes/uix/components/grid/+page.svelte b/web/routes/uix/components/grid/+page.svelte
new file mode 100644
index 000000000..49873bff2
--- /dev/null
+++ b/web/routes/uix/components/grid/+page.svelte
@@ -0,0 +1,92 @@
+
+
+
+
+
+
+ Live example
+
+ 1
+ 2
+ 3
+ 4
+ 5
+ 6
+
+
+
+
+ Props
+
+
+
+ | Prop |
+ Notes |
+
+
+
+
+ templateColumns / templateRows |
+ raw grid-template-* strings |
+
+
+ autoColumns / autoRows |
+ implicit track sizes |
+
+
+ autoFlow |
+ row · column · dense · row dense · column dense |
+
+
+ gap / rowGap / columnGap |
+ row/columnGap override gap per axis |
+
+
+ align / justify |
+ align-items / justify-content |
+
+
+ placeItems / placeContent |
+ raw shorthand strings |
+
+
+ gridColumn / gridRow / gridArea |
+ per-item placement |
+
+
+ … plus every BoxProps prop except display |
+
+
+
+
+
+
+ Reference
+
+ -
+ CSS Grid spec — props are 1:1 with the CSS properties they wrap.
+
+ -
+ radix-themes Grid — same prop names (
columns is
+ called templateColumns here, for symmetry with templateRows).
+
+
+
+
diff --git a/web/routes/uix/components/group/+page.svelte b/web/routes/uix/components/group/+page.svelte
new file mode 100644
index 000000000..11fde0e07
--- /dev/null
+++ b/web/routes/uix/components/group/+page.svelte
@@ -0,0 +1,94 @@
+
+
+
+
+
+
+ Default
+
+
+
+
+
+
+
+
+ Attached (segmented control)
+
+
+
+
+
+
+
+
+ Grow (equal widths)
+
+
+
+
+
+
+
+ Props
+
+
+
+ | Prop |
+ Default |
+ Notes |
+
+
+
+
+ direction |
+ 'row' |
+ row · column |
+
+
+ grow |
+ false |
+ children get flex: 1 1 0 |
+
+
+ attached |
+ false |
+ collapses inner radii + overlaps 1px to merge borders |
+
+
+ gap |
+ 0 if attached |
+ defaults to 0 when attached is on |
+
+
+ … plus FlexProps minus wrap |
+
+
+
+
+
+
+ Reference
+
+ -
+ mantine Group — same name and intent.
+
+ -
+ chakra-ui ButtonGroup — origin of the
+
attached behavior, generalized here.
+
+
+
+
diff --git a/web/routes/uix/components/section/+page.svelte b/web/routes/uix/components/section/+page.svelte
new file mode 100644
index 000000000..6cfa68009
--- /dev/null
+++ b/web/routes/uix/components/section/+page.svelte
@@ -0,0 +1,97 @@
+
+
+
+
+
+
+
+
+ Padding scale
+
+
+
+ | Size |
+ Block padding |
+
+
+
+ sm | var(--space-8) = 32px |
+ md | var(--space-12) = 48px |
+ lg | var(--space-16) = 64px |
+ xl | calc(var(--space-16) * 1.5) = 96px |
+
+
+
+
+
+ Props
+
+
+
+ | Prop |
+ Default |
+ Notes |
+
+
+
+
+ size |
+ 'lg' |
+ sm · md · lg · xl |
+
+
+ paddingY |
+ — |
+ override the size-keyed default |
+
+
+ … plus every BoxProps prop |
+
+
+
+
+
+
+ Reference
+
+ -
+ radix-themes Section — same intent (size-keyed block padding
+ for vertical rhythm).
+
+ -
+ HTML
<section> — Section is a `<div>`
+ in batch 1; wrap with a real <section> when you need landmark semantics.
+
+
+
+
diff --git a/web/routes/uix/components/stack/+page.svelte b/web/routes/uix/components/stack/+page.svelte
new file mode 100644
index 000000000..11c4aac6b
--- /dev/null
+++ b/web/routes/uix/components/stack/+page.svelte
@@ -0,0 +1,92 @@
+
+
+
+
+
+
+ Vertical (default)
+
+ Row 1
+ Row 2
+ Row 3
+
+
+
+
+ Horizontal
+
+ A
+ B
+ C
+
+
+
+
+ Props
+
+
+
+ | Prop |
+ Default |
+ Notes |
+
+
+
+
+ direction |
+ 'column' |
+ row · row-reverse · column · column-reverse |
+
+
+ gap |
+ — |
+ maps to var(--space-N) when numeric |
+
+
+ align / justify |
+ — |
+ inherited from Flex |
+
+
+ … plus FlexProps minus wrap, inline, rowGap, columnGap |
+
+
+
+
+
+
+ Reference
+
+ -
+ chakra-ui Stack / VStack / HStack — same intent; we collapse
+ the three into one component with a
direction prop.
+
+ -
+ radix-themes Flex direction='column' — equivalent shape; Stack
+ is the named shortcut.
+
+
+
+
diff --git a/web/routes/uix/components/wrap/+page.svelte b/web/routes/uix/components/wrap/+page.svelte
new file mode 100644
index 000000000..98b878e5e
--- /dev/null
+++ b/web/routes/uix/components/wrap/+page.svelte
@@ -0,0 +1,81 @@
+
+
+
+
+
+
+ Live example
+
+ {#each tags as t (t)}
+ {t}
+ {/each}
+
+
+
+
+ Props
+
+
+
+ | Prop |
+ Notes |
+
+
+
+
+ gap / rowGap / columnGap |
+ independent row/column gaps |
+
+
+ align / justify |
+ main- and cross-axis alignment |
+
+
+ … plus FlexProps minus direction and wrap |
+
+
+
+
+
+
+ Reference
+
+ -
+ mantine Group wrap — same intent; we name the wrap-locked
+ variant explicitly to make intent obvious at the call site.
+
+ -
+ chakra-ui Wrap — origin of the dedicated wrap component.
+
+
+
+