diff --git a/src/uix/morfo/components/container.ts b/src/uix/morfo/components/container.ts new file mode 100644 index 000000000..d80b24587 --- /dev/null +++ b/src/uix/morfo/components/container.ts @@ -0,0 +1,30 @@ +import type { Morfo } from '../types'; + +/** + * Container — max-width centred content shell (layout primitive). + * + * Eidos-native: caps content width at the `--container-width-{size}` + * token and centers via auto inline margins. The provider is a single + * `
- Max-width content container. Caps content at the
- --container-width-{'{size}'} token and centers it.
+ Max-width centred content shell. Caps content at one of six tokens
+ (sm · md · lg · xl · xxl · full), applies a default horizontal padding via
+ --container-padding-inline, and centres via auto inline margins (or
+ align="left" / align="right"). For arbitrary
+ max-width values use <Box> directly;
+ for semantic block padding use <Section>.
Container content (size: {size}).
| 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 |
- ||
+ Container is eidos-native — no soma split.
+ The hatched background of the stage shows the parent's width; the bordered region is
+ what Container caps. full removes the cap entirely. Container also
+ inherits every Box prop (padding, margin) via
+ composition.
+
{somaSnippet}
+ {eidosSnippet}
+
+ Container layers max-inline-size, padding-inline and
+ margin-inline rules on top of <Box>. Sizes are tokens defined in the eidos foundation; full removes the cap
+ (max-width: none). For arbitrary max-width values pass
+ maxWidth on Box directly.
+
| Prop | Type | Default | Notes |
|---|---|---|---|
| size | sm | md | lg | xl | xxl | full | 'xl' | Maps to --container-width-{`{size}`} token. full uncaps. |
| align | left | center | right | 'center' | Drives margin-inline: auto/0 combinations. |
| Prop | Type | Default | Notes |
|---|---|---|---|
| paddingX | number | string | 'var(--container-padding-inline)' | Default comes from the foundation token; override per-instance. |
| paddingY | number | string | — | Inherited from Box; default is 0. Use <Section> if you want token-keyed block padding. |
+ Every BoxProps field is forwarded:
+ padding*, margin*, width (locked to 100% by
+ Container), height, position, overflow*. Note
+ that Container sets marginLeft / marginRight from
+ align, so manual marginX overrides override the alignment.
+
| Library | Closest equivalent | Difference |
|---|---|---|
| radix-themes | <Container> | Radix exposes 4 sizes (1..4); UIX uses 6 named tokens (sm..xxl + full). Same centred-by-default shape. |
| chakra-ui | <Container> | Chakra defaults to maxW="60ch" regardless of theme; UIX wires through to foundation tokens for project-wide consistency. |
| mantine | <Container> | Mantine takes size="xs|sm|md|lg|xl" or a raw number; UIX matches the named scale and adds full to opt out. |
| Field | Value |
|---|---|
| name | {containerMorfo.name} |
| kebab | {containerMorfo.kebab} |
| scope | {containerMorfo.scope.join(', ')} |
| parts | {partsList.length} |
| events | 0 |
| kebab | marker | element | archetype | optional |
|---|---|---|---|---|
| {part.kebab} | +[{part.marker}] |
+ <{part.defaultElement}> | +{part.archetype} | +{part.optional ? 'yes' : 'no'} | +
+ Container composes through <Box>, so the rendered DOM is a single
+ div[data-box][data-container] shell with data-size and
+ data-align attributes for selector targeting.
+
+ Container declares no semantic events. As a passive layout primitive, it does not + commit, emerge, or react to anything — it just constrains its children's width. + Content inside the container owns its own sema. +
+
+ Container reads --container-width-{`{size}`} and
+ --container-padding-inline from the eidos foundation. The Svelte
+ component computes the resolved width and margins inline; the recipe at
+ src/uix/eidos/components/container/container.css mostly carries the
+ marker.
+
| Selector | Owner | Purpose |
|---|---|---|
[data-container] |
+ morfo | +Provider marker. | +
[data-container][data-size='{`{size}`}'] |
+ eidos | +Per-size hooks (currently used by the component's inline style, available for consumer overrides). | +
| Concern | Contract |
|---|---|
| Role | None implicit. Default element is <div> — semantically neutral. Container is a layout wrapper; landmarks (<main>, <article>) belong on a separate element. |
| Label | Not applicable. |
| Keyboard | Container is not focusable. Tab order follows DOM order. |
| Reduced motion | Container does not animate. |
- display:grid container with track and placement props.
+ display:grid container. Owns track templates
+ (templateColumns, templateRows,
+ autoColumns, autoRows, autoFlow), container alignment
+ (align, justify, placeItems,
+ placeContent) and the row/column gap. Item placement props
+ (gridColumn, gridRow, gridArea,
+ placeSelf) live on the child <Box>.
+ For flex layouts use <Flex>.
| 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 |
- |
columns is
- called templateColumns here, for symmetry with templateRows).
-
+ Grid is eidos-native — no soma split. The
+ container props below map to --grid-* CSS variables. Grid also inherits
+ every Box prop (padding, margin, size, position) by
+ composition. Item-side placement (gridColumn, gridRow,
+ gridArea, placeSelf) is set on the child Box.
+
{somaSnippet}
+ {eidosSnippet}
+
+ Grid adds container-side grid props on top of every BoxProps field. Track
+ templates accept any CSS grid-template-* string. Item placement
+ props (gridColumn, gridRow, gridArea,
+ placeSelf) live on the child <Box>.
+
| Prop | Type | Default | Notes |
|---|---|---|---|
| templateColumns | string | — | grid-template-columns. e.g. "repeat(3, 1fr)". |
| templateRows | string | — | grid-template-rows. |
| autoColumns | string | 'auto' | grid-auto-columns. |
| autoRows | string | 'auto' | grid-auto-rows. |
| autoFlow | row | column | dense | row dense | column dense | 'row' | grid-auto-flow. |
| Prop | Type | Default | Notes |
|---|---|---|---|
| align | stretch | start | center | end | baseline | flex-start | flex-end | 'stretch' | align-items. |
| justify | start | center | end | stretch | space-between | space-around | space-evenly | flex-start | flex-end | 'start' | justify-content. |
| placeItems | string | — | Shorthand for align-items + justify-items. |
| placeContent | string | — | Shorthand for align-content + justify-content. |
| Prop | Type | Default | Notes |
|---|---|---|---|
| gap | number | string | — | Shorthand for both axes. Number → var(--space-N). |
| rowGap | number | string | — | Per-axis override; takes precedence over gap. |
| columnGap | number | string | — | Per-axis override; takes precedence over gap. |
+ Every BoxProps field is forwarded (except
+ display, which Grid locks to grid):
+ padding*, margin*, width, height,
+ position, top/right/bottom/left, overflow*.
+
+ Item-side props are not on Grid. Set them on the child Box:
+ gridColumn, gridRow, gridArea,
+ placeSelf, alignSelf, justifySelf, order.
+ See <Box> → "Flex / grid item".
+
| Library | Closest equivalent | Difference |
|---|---|---|
| radix-themes | <Grid> | Same shape: container props on Grid, item props on Box. Radix accepts columns="3" shorthand; UIX uses raw templateColumns="repeat(3, 1fr)". |
| chakra-ui | <Grid> / <GridItem> | Chakra ships a separate <GridItem> for placement; UIX uses Box on the child instead — one primitive, less indirection. |
| mantine | <SimpleGrid> / <Grid> | Mantine splits into SimpleGrid (auto-fit responsive) and Grid (12-col span); UIX keeps a single Grid that accepts arbitrary CSS Grid templates. |
| Field | Value |
|---|---|
| name | {gridMorfo.name} |
| kebab | {gridMorfo.kebab} |
| scope | {gridMorfo.scope.join(', ')} |
| parts | {partsList.length} |
| events | 0 |
| kebab | marker | element | archetype | optional |
|---|---|---|---|---|
| {part.kebab} | +[{part.marker}] |
+ <{part.defaultElement}> | +{part.archetype} | +{part.optional ? 'yes' : 'no'} | +
+ Grid composes through <Box>, so the rendered DOM is a single
+ div[data-box][data-grid] shell. Recipe lives at
+ src/uix/eidos/components/grid/grid.css and layers on top of the Box recipe.
+
+ Grid 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 Grid with an interactive primitive (popover, drawer, + collapsible) that owns the relevant sema verbs. +
+
+ Recipe lives in src/uix/eidos/components/grid/grid.css and layers on top of
+ the Box recipe. The provider element carries both data-box and
+ data-grid markers because Grid renders through Box.
+
| Selector | Owner | Purpose |
|---|---|---|
[data-grid] |
+ morfo | +Provider marker. | +
[data-box][data-grid] {`{ grid-template-columns: var(--grid-template-columns, none); … }`} |
+ eidos | +Layer container-side grid properties on the Box recipe. | +
| Concern | Contract |
|---|---|
| Role | None implicit. Default element is <div> — semantically neutral. Wrap Grid in a landmark (<main>, <nav>, <section>) when content needs one. |
| Label | Not applicable — Grid has no content semantics. Labels belong to the interactive children. |
| Keyboard | Grid is not focusable. Tab order follows DOM order even when CSS Grid reflows visual order (e.g. order, grid-area, autoFlow="dense"). Verify visual order matches focus order for keyboard users. |
| Reduced motion | Grid does not animate. Layout shifts on resize follow CSS defaults. |
- Inline cluster of children. Supports grow (equal-width children)
- and attached (edge-merged children).
+ Inline cluster — flex row by default, intended for toolbars, button groups and
+ segmented controls. Adds two cluster-specific behaviours on top of
+ <Flex>: grow (every child stretches
+ to equal width via flex: 1 1 0) and attached (children share an
+ edge — used for segmented controls). For vertical clusters use
+ <Stack>; for chips that should wrap across lines
+ use <Wrap>; for the full flex API drop down to
+ <Flex>.
| 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 |
- ||
attached behavior, generalized here.
-
+ Group is eidos-native — no soma split. Five
+ container props plus two cluster-specific booleans (grow,
+ attached) on top of <Flex>. Wrap is intentionally
+ disabled — use <Wrap> when you need wrapping.
+
{somaSnippet}
+ {eidosSnippet}
+
+ Group narrows <Flex>'s
+ direction to row | column (no reverse variants), drops
+ wrap entirely (use <Wrap> for that), and adds two cluster-specific booleans.
+
| Prop | Type | Default | Notes |
|---|---|---|---|
| direction | row | column | 'row' | Narrowed from Flex's full direction union. |
| gap | number | string | attached ? 0 : — | Number → var(--space-N). attached implies gap=0; explicit prop wins. |
| align | stretch | start | center | end | baseline | flex-start | flex-end | — | align-items — cross-axis. |
| justify | start | center | end | space-between | space-around | space-evenly | stretch | flex-start | flex-end | — | justify-content — main-axis. |
| Prop | Type | Default | Notes |
|---|---|---|---|
| grow | boolean | false | Every child gets flex: 1 1 0 — fills the row evenly. |
| attached | boolean | false | Adjacent children share an edge: inner radii squared, 1px overlap absorbs doubled borders. Implies gap=0 unless explicitly set. |
+ Every BoxProps field is forwarded through Flex:
+ padding*, margin*, width, height,
+ position, overflow*, plus item-side props.
+
| Library | Closest equivalent | Difference |
|---|---|---|
| radix-themes | <Flex> | Radix doesn't ship a separate Group — they recommend Flex with gap. UIX adds attached for segmented controls and grow for equal-width toolbars. |
| chakra-ui | <ButtonGroup> / <HStack> | Chakra splits attached behaviour into ButtonGroup (button-specific). UIX's Group is element-agnostic — works for any cluster. |
| mantine | <Group> | Mantine has the same name and shape; grow maps 1:1. Mantine doesn't have attached — UIX adds it. |
| Field | Value |
|---|---|
| name | {groupMorfo.name} |
| kebab | {groupMorfo.kebab} |
| scope | {groupMorfo.scope.join(', ')} |
| parts | {partsList.length} |
| events | 0 |
| kebab | marker | element | archetype | optional |
|---|---|---|---|---|
| {part.kebab} | +[{part.marker}] |
+ <{part.defaultElement}> | +{part.archetype} | +{part.optional ? 'yes' : 'no'} | +
+ Group composes through <Flex> which composes through
+ <Box>, so the rendered DOM is a single
+ div[data-box][data-flex][data-group] shell with optional
+ data-grow / data-attached attributes for the cluster
+ behaviours.
+
+ Group declares no semantic events. As a passive layout primitive, it does not commit,
+ emerge, or react to anything — it just arranges its children. The interactive elements
+ inside the group (buttons, toggles) own their own sema. ARIA grouping semantics
+ (role="group", role="toolbar", role="radiogroup")
+ belong to the consumer who knows what kind of cluster this is.
+
+ Recipe lives in src/uix/eidos/components/group/group.css and layers the
+ grow and attached behaviours on top of the Flex recipe.
+
| Selector | Owner | Purpose |
|---|---|---|
[data-group] |
+ morfo | +Provider marker. | +
[data-group][data-grow] > * |
+ eidos | +Apply flex: 1 1 0 to every direct child. |
+
[data-group][data-attached] > * |
+ eidos | +Squash inner radii and negative-margin overlap to absorb adjacent borders. | +
| Concern | Contract |
|---|---|
| Role | None implicit. Group is shape-agnostic — assign role="group", role="toolbar" or role="radiogroup" on the Group provider via {`{...rest}`} when the cluster has semantic meaning. Reach for the <Toolbar> primitive for true toolbar semantics. |
| Label | Provide aria-label or aria-labelledby on the Group when assigning a grouping role. |
| Keyboard | Group itself is not focusable. Tab order follows DOM order. For arrow-key navigation between children use the headless toolbar / radio-group soma. |
| Reduced motion | Group does not animate. |
- Top-level page section with consistent block-axis padding scaled by
- size.
+ Top-level page section with consistent block-axis padding. Four size tokens
+ (sm · md · lg · xl) map to --section-padding-block-{`{size}`}.
+ Despite the name, Section currently renders as <div> — there is no
+ as prop yet, so when you need a real <section> landmark,
+ wrap or nest one. For centred max-width content use
+ <Container>; for non-semantic padding use
+ <Box>.
- Section content (size: {size}).
+
+ Section is eidos-native — no soma split. The
+ horizontal hatched background shows how the block padding shifts content vertically
+ as size changes. Note the rendered element is a <div>
+ — see the A11y tab for guidance on adding a real landmark.
+
{somaSnippet}
+ {eidosSnippet}
+
+ Section is a one-prop specialization of <Box>: it sets width: 100%, picks a default
+ padding-block from size, and renders as <div>.
+ If you pass paddingY directly, your value wins over the size-keyed
+ default.
+
| Prop | Type | Default | Notes |
|---|---|---|---|
| size | sm | md | lg | xl | 'lg' | Maps to --section-padding-block-{`{size}`}: sm→8 / md→12 / lg→16 / xl→24 space steps. |
| paddingY | number | string | SIZE_TO_PADDING[size] | Override the size-keyed default per-instance. |
+ Every BoxProps field is forwarded:
+ padding*, margin*, width (locked to 100% by
+ Section), height, position, overflow*. Pass
+ paddingX for horizontal padding (no default).
+
+ Section currently has no as prop — the rendered element is always
+ <div>. Add a <section> wrapper outside Section
+ (or nest a <section> as the first child) when landmark semantics
+ are needed.
| 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 |
| Prop | -Default | -Notes | -
|---|---|---|
size |
- 'lg' | -sm · md · lg · xl | -
paddingY |
- — | -override the size-keyed default | -
… plus every BoxProps prop |
- ||
<section> — Section is a `<div>`
- in batch 1; wrap with a real <section> when you need landmark semantics.
- | Library | Closest equivalent | Difference |
|---|---|---|
| radix-themes | <Section> | Radix renders a real <section> by default and takes size="1..4"; UIX uses named tokens (sm..xl) and renders <div> pending an as prop. |
| chakra-ui | n/a | Chakra has no dedicated Section — they recommend <Box as="section" py="…">. UIX names the pattern as a primitive for consistency. |
| mantine | n/a | Mantine has no Section either. Sections are usually authored ad-hoc with Stack + AppShell. |
| Field | Value |
|---|---|
| name | {sectionMorfo.name} |
| kebab | {sectionMorfo.kebab} |
| scope | {sectionMorfo.scope.join(', ')} |
| parts | {partsList.length} |
| events | 0 |
| kebab | marker | element | archetype | optional |
|---|---|---|---|---|
| {part.kebab} | +[{part.marker}] |
+ <{part.defaultElement}> | +{part.archetype} | +{part.optional ? 'yes' : 'no'} | +
+ Section composes through <Box>, so the rendered DOM is a single
+ div[data-box][data-section] shell with a data-size
+ attribute. Recipe lives at
+ src/uix/eidos/components/section/section.css and declares the
+ --section-padding-block-* tokens locally.
+
+ Section declares no semantic events. As a passive layout primitive, it does not + commit, emerge, or react to anything — it just pads its children. Content inside the + section owns its own sema. +
+
+ Recipe lives in src/uix/eidos/components/section/section.css. The
+ --section-padding-block-* tokens are defined locally on the
+ [data-section] selector (intentionally not in the foundation — Section
+ is the sole consumer).
+
| Selector | Owner | Purpose |
|---|---|---|
[data-section] |
+ morfo | +Provider marker. Declares the per-size padding tokens and locks inline-size: 100%. |
+
| Concern | Contract |
|---|---|
| Role | None implicit. Despite the name, Section renders as <div> — there is no implicit region role. Wrap Section in a real <section aria-labelledby="…"> or use a heading inside to provide landmark structure. |
| Label | Not applicable to Section itself. If you wrap a real <section> around it, label it with aria-labelledby pointing at the heading. |
| Keyboard | Section is not focusable. Tab order follows DOM order. |
| Reduced motion | Section does not animate. |
- Vertical (default) or horizontal stack. Thin alias over Flex with
- direction='column'.
+ Direction-controlled flex stack — vertical by default, horizontal when
+ direction="row". Thin specialization of
+ <Flex> with locked nowrap and 4 ergonomic props
+ (direction, gap, align, justify). For
+ row-only clusters with edge-to-edge behaviour use
+ <Group>; for the full flex API (including
+ wrap and per-axis gaps) drop down to <Flex>.
| 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 |
- ||
direction prop.
-
+ Stack is eidos-native — no soma split. The
+ 4 container props below pass through to the underlying <Flex>.
+ Stack also inherits every Box prop (padding,
+ margin, size, position) via Flex composition.
+
{somaSnippet}
+ {eidosSnippet}
+
+ Stack is a thin, opinionated specialization of <Flex>: it locks wrap to nowrap, defaults direction to
+ column, and exposes only the four most-used container props. Drop down to
+ <Flex> when you need wrap, rowGap,
+ columnGap or inline.
+
| Prop | Type | Default | Notes |
|---|---|---|---|
| direction | row | row-reverse | column | column-reverse | 'column' | flex-direction. |
| gap | number | string | — | Number → var(--space-N). |
| align | stretch | start | center | end | baseline | flex-start | flex-end | — | align-items — cross-axis. |
| justify | start | center | end | space-between | space-around | space-evenly | stretch | flex-start | flex-end | — | justify-content — main-axis. |
+ Every BoxProps field is forwarded through Flex:
+ padding*, margin*, width, height,
+ position, top/right/bottom/left, overflow*, plus
+ item-side props (alignSelf, gridColumn, …).
+
+ wrap, inline, rowGap, columnGap —
+ Stack intentionally omits these. Use <Flex> when you need them.
+
| Library | Closest equivalent | Difference |
|---|---|---|
| radix-themes | <Flex direction="column"> | Radix doesn't ship a separate Stack — they use Flex with direction. UIX names the direction-locked variant for the most common case. |
| chakra-ui | <Stack> / <VStack> / <HStack> | Chakra exposes three components (Stack/VStack/HStack). UIX collapses to a single Stack with a direction prop — <Group> covers the row-cluster case. |
| mantine | <Stack> / <Group> | Mantine has the same split: vertical Stack + horizontal Group. UIX matches Mantine here. |
| Field | Value |
|---|---|
| name | {stackMorfo.name} |
| kebab | {stackMorfo.kebab} |
| scope | {stackMorfo.scope.join(', ')} |
| parts | {partsList.length} |
| events | 0 |
| kebab | marker | element | archetype | optional |
|---|---|---|---|---|
| {part.kebab} | +[{part.marker}] |
+ <{part.defaultElement}> | +{part.archetype} | +{part.optional ? 'yes' : 'no'} | +
+ Stack composes through <Flex> which composes through
+ <Box>, so the rendered DOM is a single
+ div[data-box][data-flex][data-stack] shell. Recipe lives at
+ src/uix/eidos/components/stack/stack.css — currently just the marker; all
+ behaviour comes from the Flex layer.
+
+ 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. +
+
+ Recipe lives in src/uix/eidos/components/stack/stack.css — currently
+ declaration-only (the [data-stack] marker exists so consumers can target
+ stacks specifically). All visual behaviour layers on top of the Flex recipe.
+
| Selector | Owner | Purpose |
|---|---|---|
[data-stack] |
+ morfo | +Provider marker. Used by consumers for targeting; behaviour inherited from Flex. | +
| Concern | Contract |
|---|---|
| Role | None implicit. Default element is <div> — semantically neutral. Wrap Stack in a landmark (<nav>, <section>, <ul role="list">) when content needs one. |
| Label | Not applicable — Stack has no content semantics. Labels belong to the interactive children. |
| Keyboard | Stack is not focusable. Tab order follows DOM order even when direction visually reverses (row-reverse / column-reverse). Verify visual order matches focus order for keyboard users. |
| Reduced motion | Stack does not animate. |
- Flex row that always wraps. Use for tag clouds, chip groups and any
- row-of-children that should reflow on narrow viewports.
+ Flex row that always wraps. Use for tag/chip clusters, filter bars or anything
+ that should reflow across multiple lines on narrow viewports. Equivalent to
+ <Flex wrap="wrap"> with two extra ergonomic
+ gap-axis shorthands (rowGap, columnGap). For nowrap clusters
+ use <Group>; for arbitrary flex direction use
+ <Flex>.
| Prop | -Notes | -
|---|---|
gap / rowGap / columnGap |
- independent row/column gaps | -
align / justify |
- main- and cross-axis alignment | -
… plus FlexProps minus direction and wrap |
- |
+ Wrap is eidos-native — no soma split. Locks
+ flex-wrap: wrap and exposes 5 container props on top of <Flex>.
+ The stage caps width at 32rem to demonstrate reflow.
+
{somaSnippet}
+ {eidosSnippet}
+
+ Wrap is a narrow specialization of <Flex>: locks wrap to 'wrap', drops direction
+ (always row), and elevates rowGap / columnGap as
+ first-class props because per-axis gaps are the typical reason to reach for Wrap.
+
| Prop | Type | Default | Notes |
|---|---|---|---|
| gap | number | string | — | Shorthand for both axes. Number → var(--space-N). |
| rowGap | number | string | — | Gap between wrapped lines. |
| columnGap | number | string | — | Gap between items within a line. |
| align | stretch | start | center | end | baseline | flex-start | flex-end | — | align-items per line — cross-axis. |
| justify | start | center | end | space-between | space-around | space-evenly | stretch | flex-start | flex-end | — | justify-content — main-axis. |
+ Every BoxProps field is forwarded through Flex:
+ padding*, margin*, width, height,
+ position, overflow*, plus item-side props.
+
+ direction (Wrap is always row), wrap (locked to wrap),
+ inline — Wrap intentionally omits these. Use <Flex> when you need them.
+
| Library | Closest equivalent | Difference |
|---|---|---|
| radix-themes | <Flex wrap="wrap"> | Radix doesn't ship a separate Wrap — they recommend Flex with the wrap prop. UIX names it for the common chip-cluster case. |
| chakra-ui | <Wrap> / <WrapItem> | Chakra wraps each child in a <WrapItem> internally. UIX has no item wrapper — gap is owned by the container. |
| mantine | <Group wrap="wrap"> | Mantine collapses both into Group with a prop. UIX splits Group (nowrap) and Wrap (wrap) because their visual intent is different. |
| Field | Value |
|---|---|
| name | {wrapMorfo.name} |
| kebab | {wrapMorfo.kebab} |
| scope | {wrapMorfo.scope.join(', ')} |
| parts | {partsList.length} |
| events | 0 |
| kebab | marker | element | archetype | optional |
|---|---|---|---|---|
| {part.kebab} | +[{part.marker}] |
+ <{part.defaultElement}> | +{part.archetype} | +{part.optional ? 'yes' : 'no'} | +
+ Wrap composes through <Flex wrap="wrap"> which composes through
+ <Box>, so the rendered DOM is a single
+ div[data-box][data-flex][data-wrap] shell.
+
+ Wrap declares no semantic events. As a passive layout primitive, it does not commit, + emerge, or react to anything — it just arranges its children. Tags/chips/buttons + inside the Wrap own their own sema. +
+
+ Recipe lives in src/uix/eidos/components/wrap/wrap.css. The
+ data-wrap marker is mostly for consumer targeting; the actual
+ flex-wrap: wrap behaviour is set by the underlying Flex layer because Wrap
+ renders as <Flex wrap="wrap">.
+
| Selector | Owner | Purpose |
|---|---|---|
[data-wrap] |
+ morfo | +Provider marker. Used by consumers for targeting; flex-wrap comes from the Flex layer. |
+
| Concern | Contract |
|---|---|
| Role | None implicit. Default element is <div>. When wrapping a list of tags/filters, consider role="list" + role="listitem" on the children for screen-reader semantics. |
| Label | Not applicable — Wrap has no content semantics. |
| Keyboard | Wrap is not focusable. Tab order follows DOM order; wrapped lines do not change the focus sequence. For arrow-key cluster navigation use a tag-group or toolbar soma. |
| Reduced motion | Wrap does not animate. Reflow on resize follows CSS defaults. |