Usage

Install

Each icon installs into components/icons/ as source. The shared runtime comes with it, and is reused by every icon after the first.

$ npx shadcn@latest add "https://phosphor-animated.com/r/bell.json"
import { Bell } from "@/components/icons/bell";

export function Nav() {
  return <Bell className="size-6" />;
}

Weights

Five Phosphor weights, from one component. The line weights share a single drawing; duotone adds its filled backdrop.

<Trash weight="thin" />
<Trash weight="bold" />
<Trash weight="duotone" />
thin
light
regular
bold
duotone

Triggers

Hover by default. Change it with one prop.

<Heart />                    // hover
<Heart trigger="click" />
<Heart trigger="in-view" />
<Heart trigger="loop" />
hover
click
in-view
loop

Control it yourself

Set trigger="none" and drive the icon from a ref — for animating on a state change rather than a pointer.

const ref = useRef<AnimatedIconHandle>(null);

<Bell ref={ref} trigger="none" />
<button onClick={() => ref.current?.play()}>
  Notify
</button>

Speed

A multiplier on playback rate.

<Spinner trigger="loop" speed={2} />
speed={0.5}
speed={1}
speed={2}

Reduced motion

Every icon reads prefers-reduced-motion and stays still when the system asks it to. Nothing to configure, and nothing to remember at each call site.

Editing an animation

The keyframes in an installed icon are ordinary data. Change a number and the motion changes — there is nothing to reinstall and nothing pointing back at this site.

const STROKE: Choreography = {
  duration: 0.7,
  parts: {
    0: { rotate: [0, 17, -12, 7, 0], origin: [128, 48] },
    1: { rotate: [0, 12, -9, 5, 0], origin: [128, 48] },
  },
};