# Cascade
A thin **explicit** orchestrator that staggers its children's enter/exit. It owns **no recipe
and no timing of its own** — it rides the foundation stagger + the existing state-presets. It
is the *explicit* / content-animation way to sequence a set of elements (you designed them to
stagger), as opposed to the *event-driven* path where a component's own `emerge` firma + the
same foundation stagger do it (see "Two ways" below).
```svelte
{#each items as item (item.id)}
{item.label}
{/each}
```
`--motion-stagger-each: 0` (the default) → **parallel**; `N` → **cascade**. The keyframe,
duration, easing and reduced-motion all come from the `motion` preset.
## How it works — it reuses everything
Nothing here is a new system. The cascade is the *existing declared motion* plus one
foundation rule:
| Piece | Where it's declared / derived |
| --- | --- |
| the per-item **animation** (keyframe, duration, ease, reduced-motion) | a **state-preset** declared in `EidosConfig.motion` (`fade` / `scale-fade` / …), generated by eidos to `generated/base.css` and registered with `uix.motion` |
| the **stagger delay** (`index × --motion-stagger-each`, default 0 = parallel) | already in **every preset's** enter/exit rule (`lib/render-css.ts`) |
| the **structural index** (`--motion-stagger-index` / `-rev`) | the one new foundation rule: `[data-stagger] > *:nth-child / :nth-last-child` (generated by `lib/render-css.ts`) — **nobody writes it**, soma never writes a visual var |
`` just **wires** those: it marks the container `[data-stagger]`, reflects `open` as
`data-state`, and passes `open` + the chosen preset to its items (context), so each item
carries `data-animation-style` + `data-state`. Enter counts up (`:nth-child`); exit counts
down (`:nth-last-child`) so the last item leaves first.
## Props
### ``
| Prop | Type | Default | Notes |
| --- | --- | --- | --- |
| `open` | `boolean` | `true` | Drives the staggered enter (forward) / exit (reverse) via `data-state`. |
| `motion` | `MotionPresetName` | `'fade'` | The state-preset every item plays (`fade` / `scale-fade` / `slide-fade` / …). Unified with the overlays' selector (RFC §D.12). |
Plus any `HTMLAttributes`. Set the layout + the rhythm (`--motion-stagger-each`)
here.
### ``
A participant. On removal it flips to `data-state='closed'` (the existing **exit** preset
plays, with the reverse structural index), is retained (`out:`) for the eidos-declared exit
duration, then unmounts. Accepts any `HTMLAttributes`.
## Ownership
| | Owns | Never touches |
| --- | --- | --- |
| **soma** (the wrappers) | **state** (`open` → `data-state`, propagated to items) + **lifecycle** (`out:` retention) | any `--motion-*` / visual variable — it reads the eidos duration via the ActiveDom `getComputedStyle`, writes nothing |
| **eidos** | **all the visual** — the preset (keyframe/duration/ease/reduced-motion) + the foundation stagger index | the semantics / state |
## Two ways to stagger children
1. **Explicit** — `` (this component). You want these elements to stagger; there is no
semantic event. The content/intrinsic animation domain.
2. **Event-driven** — a real component (a menu, a list) declares its own `emerge` firma (the
container flourish + sound) **and** marks its item container `[data-stagger]`; its items
carry a preset (`data-animation-style`). Same foundation stagger, no `` wrapper —
the appearance flows from the declared event. The container-driven preset rule
`[data-stagger][data-state='open'] > [data-animation-style]` fires the children's enter off
the *container's* `data-state` (which soma already writes via the morfo `commits`), so the
items carry **no per-item `data-state`** — a `menuitem` isn't open/closed; the menu is.
(The firma is *generic by event*: "children of an emerging container present together, by
structural order"; the concrete timing — parallel/cascade, ms, keyframe — is the
per-component realization.)
**First concrete consumer: ``** — items fade in staggered on open
(`src/uix/eidos/components/dropdown-menu/`). See its README → *Motion*. The container-driven
rule is **enter-only**; the coordinated *exit* cascade (retain the container until its
children finish) is the deferred soma `PresenceGroup` work.
## Known limitation — bulk removal
When **several** items are removed at once (e.g. a slider jumping 10 → 3), the leaving nodes
unmount one-by-one as each finishes, so the grid reflows during the cascade and the remaining
items' `:nth-last-child` index re-evaluates. In practice it reads fine, but the glitch-free
version wants the **container to retain the leaving set as a unit** (one reflow at the end) —
a soma **lifecycle** concern (what the retired `pending` did, without touching the visual).
Dragging the slider down step-by-step (a sequence of lone removals) is always clean.
## Decisions
- **No recipe, no `--cascade-*` namespace, no bespoke keyframe.** An earlier prototype forked
all of that — it reinvented the existing `--motion-stagger-*` + state-preset system. This
replaces it by reusing them; the only new code is the foundation structural-index writer.
- **`Cascade` is the EXPLICIT domain.** Event-driven children appearance rides the firma +
the same foundation stagger, declared in the component's own morfo — not this wrapper.
- **Index from `:nth-child`, capped 1–24** (the foundation writer). Beyond the cap items share
the last index until `sibling-index()` is broadly supported.