The zero-runtime path, start to finish. The output is a stylesheet and some markup.
In a build script, never in a component.
import { compileAuto } from "@/lib/morph/compile";
import { coreRig } from "@/lib/morph/sets/core";
const { morph, strategy, reason } = compileAuto(coreRig, "menu", "cross");
// "polar" for this pair: it holds a real rotation, so two keyframes would cut the chord.
console.log(strategy, reason);compileAuto decides whether the pair needs polar sampling and tells you why, so a
surprising keyframe count arrives with its own explanation.
import { emitCss, emitSvg } from "@/lib/morph/compile";
const css = emitCss(morph, {
prefix: "icon",
// Appended to the pair class, so whatever it tests must live inside that element.
activeSelector: '[data-morph="to"]',
duration: "280ms",
// A sampled morph cannot be a transition, and an animation plays one direction.
...(morph.offsets.length === 2 ? {} : { mode: "keyframes" as const }),
});
// Markup with each slot's resting `d` already on it, which is what makes the
// no-CSS-`d`-property case degrade to a static icon rather than to nothing.
const svg = emitSvg(morph, { prefix: "icon" });Two things in there are worth understanding rather than copying.
activeSelector is appended to the pair class, so whatever it tests has to live inside
the element carrying that class. The default is an attribute on the element itself and
always works. A structural test like :has(input:checked) needs the pair class on a
wrapper that contains the checkbox.
The mode switch matters because a transition needs exactly two keyframes. A sampled morph
gets @keyframes, and an animation plays one direction, so the way back cuts.
emitSvg gives you markup with each slot's resting d already on the element. Keep that
attribute: Safari has no CSS d property, so geometry living only in the stylesheet
renders as nothing there rather than as a static icon.
Click any pair on this site's home page to see it. Those are compiled with exactly this pipeline and the page ships no JavaScript for them.
If the toggle never needs to reverse mid-flight, this is the whole story and it costs nothing. If it does, or you want interruption, or you want to scrub it, you want the driver.