Two defaults, both chosen so the common case needs no thought.
An icon inside a labelled control is decoration. Announcing it again is noise, so the
component is aria-hidden unless you give it a label.
import { MorphIcon } from "@/lib/morph/react";
import { coreBundle } from "@/lib/morphs.generated";
// The default. An icon inside a labelled control is decoration, and announcing it again
// is noise, so it is aria-hidden unless you say otherwise.
export function InsideAButton() {
return (
<button type="button" aria-label="Close">
<MorphIcon bundle={coreBundle} icon="cross" />
</button>
);
}A label makes it role="img" with a <title>, which is right for an icon carrying
meaning on its own.
import { MorphIcon } from "@/lib/morph/react";
import { coreBundle } from "@/lib/morphs.generated";
// A `label` makes it role="img" with a <title>, for an icon that carries meaning alone.
export function StandsAlone() {
return <MorphIcon bundle={coreBundle} icon="check" label="Saved" />;
}The driver checks prefers-reduced-motion on every morphTo, not once at startup, so
changing the setting mid-session is respected. When it is set, the morph becomes an instant
swap to the target's resting geometry.
Compiled CSS handles it too: every emitted stylesheet carries a
@media (prefers-reduced-motion: reduce) block.
The component renders an <svg> and nothing else. It does not create a focusable element,
add a hit target, or wrap itself in a button, because a morphing icon is not inherently
interactive and guessing otherwise produces a control keyboard users cannot reach.
Put it inside your own <button>, and label that.