Skip to content
Esc
↑↓navigate↵open⌘Jpreview
On this page

Variants

Name animation states and orchestrate them across a tree of elements.

Overview

Variants are named targets. Define them in variants and refer to them by name in initial, animate, exit and the while* gestures — exactly like Motion:

<script lang="ts">
	import { mercury, type Variants } from '@omicrxn/mercury';

	let open = $state(false);
	const variants: Variants = {
		open: { opacity: 1, height: 'auto' },
		closed: { opacity: 0, height: 0 }
	};
</script>

<div {@attach mercury({ variants, initial: 'closed', animate: open ? 'open' : 'closed' })} />

Propagation

A variant label flows down to every descendant with a mercury() attachment that has variants but doesn’t set the prop itself. So changing the parent’s animate animates the whole tree, and children inherit gestures too (whileHover: 'hover' on the parent animates children’s hover variants).

Parents and children are found through the DOM, so they can live in different components. Set inherit: false on a child to cut it off from its parent’s labels.

Orchestration

A variant’s transition can sequence its children:

  • delayChildren — delay before the children start (a number, or Motion’s stagger()).
  • staggerChildren — delay between each child, in document order.
  • staggerDirection — -1 staggers from the last child.
  • when — 'beforeChildren' or 'afterChildren' runs the parent’s own animation first or last.
<script lang="ts">
	const list: Variants = {
		open: { transition: { staggerChildren: 0.06, delayChildren: 0.05 } },
		closed: { transition: { staggerChildren: 0.04, staggerDirection: -1 } }
	};
	const item: Variants = {
		open: { opacity: 1, y: 0 },
		closed: { opacity: 0, y: 12 }
	};
</script>

<ul {@attach mercury({ initial: 'closed', animate: open ? 'open' : 'closed', variants: list })}>
	{#each items as label (label)}
		<li {@attach mercury({ variants: item })}>{label}</li>
	{/each}
</ul>
<script lang="ts">
	import { mercury, type Variants } from '@omicrxn/mercury';

	let open = $state(false);

	const list: Variants = {
		open: { transition: { staggerChildren: 0.06, delayChildren: 0.05 } },
		closed: { transition: { staggerChildren: 0.04, staggerDirection: -1 } }
	};
	const item: Variants = {
		open: { opacity: 1, y: 0, filter: 'blur(0px)' },
		closed: { opacity: 0, y: 12, filter: 'blur(4px)' }
	};
	const items = ['Inbox', 'Drafts', 'Sent', 'Archive'];
</script>

<div class="flex w-48 flex-col gap-3">
	<button
		type="button"
		aria-expanded={open}
		class="min-h-11 rounded-md border border-demo-stroke bg-demo-fill px-4 text-sm font-medium text-demo-fill-foreground"
		onclick={() => (open = !open)}
		{@attach mercury({ whileTap: { scale: 0.97 } })}
	>
		{open ? 'Close' : 'Open'} menu
	</button>
	<ul
		class="flex flex-col gap-2"
		{@attach mercury({
			initial: 'closed',
			animate: open ? 'open' : 'closed',
			variants: list,
			transition: { type: 'spring', duration: 0.35, bounce: 0 }
		})}
	>
		{#each items as label (label)}
			<li
				class="rounded-md border border-demo-stroke px-3 py-2 text-sm"
				inert={!open}
				{@attach mercury({ variants: item })}
			>
				{label}
			</li>
		{/each}
	</ul>
</div>

Children added later (say, a new {#each} item) animate into the parent’s current variant on their own. Several added at once are staggered when the parent’s delayChildren is Motion’s stagger(): delayChildren: stagger(0.05).

Dynamic variants

A variant can be a function of custom, returning a target:

<script lang="ts">
	const variants: Variants = {
		visible: (i: number) => ({ opacity: 1, transition: { delay: i * 0.1 } })
	};
</script>

{#each items as item, i (item.id)}
	<li {@attach mercury({ custom: i, variants, initial: { opacity: 0 }, animate: 'visible' })} />
{/each}

Functions also receive the element’s current values and velocities as their second and third arguments.

For exits, the custom passed to presence wins, because an exiting element no longer receives props — the slideshow example relies on this.

Multiple labels

Pass an array to apply several variants at once: animate: ['visible', 'highlighted'].

Was this page helpful?