A branching variant explorer for any element driven by CSS custom properties (or any state through callbacks): “Generate 4 variants” sprouts mutated children of the applied variant (a Spread slider sets how bold the mutations are), shown as a tidy tree of live thumbnails — scaled, inert clones of the target, so it works with any component without screenshots. Keep favorites, discard dead ends into a “3 variants discarded” stub, pin up to four variants in a real-size Compare strip with labeled diffs (“hue 212 → 18”), cross two of them param by param, and check out any node: the target tweens to it. Horizontal tree on wide containers, indented outline on narrow ones, seedable, serializable, one-click CSS export.
Id of the element to explore (looked up in the same root; waited for if rendered later). Without it, the first child element is the target.
params
string
Explorable parameters, same syntax as data-vary on the target (which is used when this attribute is absent).
data-vary
string
On the target: “--hue: 0..360; --radius: 0..28px step 2 as corners; --weight: 400|600|800”. Numeric ranges (unit taken from the bounds, optional step, wrap for circular values — a 0..360 hue wraps by default) or discrete sets separated by |. “as <label>” names the parameter in the UI; otherwise the last segment of the property name is used. Plain CSS properties (opacity…) work too.
count
number
4
Children created per generation (1–12).
spread
number (0–1)
0.4
Mutation strength, mirrored by the Spread slider: subtle nudges and neighbor values when low, far jumps when high. At least one parameter always changes, siblings never duplicate each other.
seed
number
Seeds the random generator: the same seed and actions give the same tree (demos, tests, shareable explorations).
label
string
Explore variants
Panel title.
max-previews
number
16
Maximum live clones in the tree. Priority: applied path, pinned, kept, then newest; the others show a cheap bar glyph of their parameters.
layout
auto | horizontal | vertical
auto
Tidy horizontal tree, or indented outline. auto switches to the outline under 520px of component width (ResizeObserver).
Properties
Name
Type
Description
params
Param[] | string
Parsed parameters ({ name, label, type: "range", min, max, unit, step, wrap } | { name, label, type: "set", values }). Assign an array or a string to redefine them (restarts the tree).
Serializable tree. Assign a saved array to restore an exploration (can be set before the element is defined).
current
{ id, params, css }
The applied variant, with its CSS declarations ready to paste (read-only).
mutate
(state, rng, spread) => state
Custom mutation for arbitrary state (return a new object or mutate the copy). rng() is the seeded generator.
apply
(state, el) => void
Custom rendering: called on the target and on every preview clone. Replaces the default custom-property writes (no tween).
Methods
Name
Description
generate(n?, { from?, animate? })
Creates n mutated children of the applied node (or from) and returns their ids. animate: false skips the sprout animation (seeding).
checkout(id, { animate? })
Applies a node to the target; numeric parameters tween (hue the short way around), discrete ones switch.
favorite(id?, force?)
Toggles the star (defaults to the applied node).
pin(id?, force?)
Toggles a node in the Compare strip (4 at most, the oldest makes room).
combine(a, b, picks?)
Creates and applies a hybrid child of a: for each differing parameter, the value of a or b (picks: { name: "a" | "b" }, random but mixed when omitted). Returns its id, or null if it would equal a parent.
prune(id?) / restore(id)
Folds a node and its descendants into a stub / unfolds a node or every pruned child of id.
reset(state?)
Starts over from state (default: the target's current computed values).
refresh()
Re-clones the previews (content changes inside the target are also picked up automatically).
Events
Name
Description
mv-generate
New children. detail: { parent, ids, params[] }.
mv-checkout
A node was applied. detail: { id, params }.
mv-combine
A hybrid was created. detail: { id, a, b, params }.
mv-favorite
detail: { id, favorite }.
mv-prune
detail: { id, count }.
Content structure
Name
Description
(content)
Optional: the target itself, when for is not used.
CSS classes
Name
Description
mv-variant-tree-panel / -head / -bar
Generated panel, header (title, status, Copy variables) and toolbar acting on the applied variant.
Scrollable tree canvas, tiles (role treeitem) with their live thumbnail, discarded-variants stub and SVG edges (data-path on the applied path, data-kind="mate" for a hybrid's second parent).
mv-variant-tree-combine / -compare / -chip
Crossover panel, Compare strip and diff chips.
CSS variables
Name
Default
Description
--mv-variant-tree-accent
var(--mv-accent)
Applied node, path and primary actions.
--mv-variant-tree-favorite
var(--mv-warning)
Star of kept variants.
--mv-variant-tree-mate
var(--mv-info)
Side B of a crossover and the hybrid's second edge.
--mv-variant-tree-thumb-bg
var(--mv-bg-subtle)
Background behind the clones.
--mv-variant-tree-bg
var(--mv-surface)
Panel background.
--mv-variant-tree-max-height
42rem
Height of the tree view before it scrolls.
Accessibility
The tree is a real role="tree": each tile is a treeitem with aria-level, aria-setsize, aria-posinset, aria-expanded and aria-selected (the applied variant), named by its parameter summary (“Variant 3 · hue 18, radius 12px · kept · in comparison, slot 1”). Roving tabindex: Up/Down/Home/End browse in reading order, Right/Left expand, collapse or move to child/parent, Enter or Space applies, F keeps, P compares, G generates from the focused node, Delete discards (focus moves to the stub, which Enter restores). Thumbnails are aria-hidden and inert clones (ids, names and labels stripped, never focusable). Applying, generating, keeping, comparing, combining and copying are announced in a polite live region; the Spread slider has an aria-valuetext (“Moderate, 45%”); unavailable actions stay focusable with aria-disabled and explain why when used. Diff chips read “hue 212 becomes 18”. Focus rings everywhere. Reduced motion (OS or data-motion="reduce"): no sprout, bloom, edge growth or glide, and the target switches instantly instead of tweening.