Variants

A variant is a named target. An element lists them in variants, and initial, animate, exit and the while* props name one. Its children with variants of their own follow the same names, each with its own target, spaced out by the parent's transition.

Live demo
variants/solid2.tsx
import { stagger, w } from "@wardic/solid/unstable/v2"
import { createSignal, For } from "solid-js"

const ITEMS = ["Vases", "Bowls", "Plates", "Jugs"]

// The list names a variant; each item follows it with its own target. The list's transition spaces
// them out: in from the first, out from the last.
const list = {
  open: { transition: { delayChildren: stagger(0.07) } },
  closed: { transition: { delayChildren: stagger(0.05, { from: "last" }) } },
}
const item = {
  open: { opacity: 1, x: 0 },
  closed: { opacity: 0, x: -24 },
}

export default function Variants() {
  const [open, setOpen] = createSignal(true)
  return (
    <div class="sample-stack">
      <button class="sample-button" onClick={() => setOpen(!open())}>
        {open() ? "Close" : "Open"}
      </button>
      <w.ul class="sample-list" variants={list} initial="closed" animate={open() ? "open" : "closed"}>
        <For each={ITEMS}>{(name) => <w.li class="sample-item" variants={item}>{name}</w.li>}</For>
      </w.ul>
    </div>
  )
}
variants/solid1.tsx
import { stagger, w } from "@wardic/solid"
import { createSignal, For } from "solid-js"

const ITEMS = ["Vases", "Bowls", "Plates", "Jugs"]

// The list names a variant; each item follows it with its own target. The list's transition spaces
// them out: in from the first, out from the last.
const list = {
  open: { transition: { delayChildren: stagger(0.07) } },
  closed: { transition: { delayChildren: stagger(0.05, { from: "last" }) } },
}
const item = {
  open: { opacity: 1, x: 0 },
  closed: { opacity: 0, x: -24 },
}

export default function Variants() {
  const [open, setOpen] = createSignal(true)
  return (
    <div class="sample-stack">
      <button class="sample-button" onClick={() => setOpen(!open())}>
        {open() ? "Close" : "Open"}
      </button>
      <w.ul class="sample-list" variants={list} initial="closed" animate={open() ? "open" : "closed"}>
        <For each={ITEMS}>{(name) => <w.li class="sample-item" variants={item}>{name}</w.li>}</For>
      </w.ul>
    </div>
  )
}
variants/motion.tsx
import { motion, stagger } from "motion/react"
import { useState } from "react"

const ITEMS = ["Vases", "Bowls", "Plates", "Jugs"]

// The list names a variant; each item follows it with its own target. The list's transition spaces
// them out: in from the first, out from the last.
const list = {
  open: { transition: { delayChildren: stagger(0.07) } },
  closed: { transition: { delayChildren: stagger(0.05, { from: "last" }) } },
}
const item = {
  open: { opacity: 1, x: 0 },
  closed: { opacity: 0, x: -24 },
}

export default function Variants() {
  const [open, setOpen] = useState(true)
  return (
    <div className="sample-stack">
      <button className="sample-button" onClick={() => setOpen(!open)}>
        {open ? "Close" : "Open"}
      </button>
      <motion.ul className="sample-list" variants={list} initial="closed" animate={open ? "open" : "closed"}>
        {ITEMS.map((name) => (
          <motion.li key={name} className="sample-item" variants={item}>
            {name}
          </motion.li>
        ))}
      </motion.ul>
    </div>
  )
}

Children follow

A child with variants and no initial or animate of its own starts from its parent's initial and follows its animate. On a server-rendered page, every child arrives at its initial already, so the entrance starts from there with no flash. An element in between, without variants, passes the names through; inherit={false} keeps a child out.

Orchestration

The parent's transition times its children:

  • staggerChildren: 0.05 starts each child 50 ms after the one before.
  • delayChildren waits before the first, or with stagger(0.07, { from, startDelay, ease }), decides each child's whole delay: from: "last" runs them back to front.
  • when: "beforeChildren" or "afterChildren" runs the parent's own animation first or last.

Custom, and lists of names

A variant can be a function of custom, so each child computes its own target:

const item = { open: (i: number) => ({ opacity: 1, transition: { delay: i * 0.1 } }) }
<w.li variants={item} custom={index()} />

animate={["visible", "active"]} names more than one: they merge in order. And a gesture can name a variant too, whileHover="hover", which the children follow as they follow animate.