shape-morph

React

Shape component and morph hooks for React.

The shape-morph/react entry point provides a component and hooks for rendering shapes and morphing in React. Requires React 18+.

<Shape> component

Renders a shape as an inline SVG.

import { Shape } from "shape-morph/react";

<Shape name="Heart" size={32} fill="red" />
<Shape name="Cookie9Sided" size={48} fill="#6750A4" stroke="white" />

Props:

PropTypeDefaultDescription
nameShapeNamerequiredShape to render
sizenumber48Width and height in pixels
fillstring"currentColor"Fill color
strokestring-Stroke color
strokeWidthnumber-Stroke width
classNamestring-Class on the <svg> element
styleCSSProperties-Style on the <svg> element

useMorph

Animates between two shapes with a requestAnimationFrame loop and ease-in-out interpolation.

import { useMorph } from "shape-morph/react";

function Avatar({ hovered }: { hovered: boolean }) {
  const { clipPath, progress } = useMorph("Circle", "Heart", {
    progress: hovered ? 1 : 0,
    duration: 300,
  });

  return (
    <img
      style={{
        clipPath,
        transform: `rotate(${progress * 90}deg)`,
      }}
      src="/avatar.png"
    />
  );
}

Change the target progress and the hook smoothly animates from the current position.

Options

OptionTypeDefaultDescription
progressnumber0Target progress (0-1)
durationnumber300Animation duration in ms. Cannot use with lerp or spring.
easing(t: number) => numbereaseInOutEasing function for duration-based animation. Cannot use with lerp or spring.
lerpnumber-Lerp factor (0-1). Each frame moves this fraction toward target. Cannot use with duration, easing, or spring.
springSpringConfig-Spring physics config. Cannot use with duration, easing, or lerp.
samplesnumber4Samples per cubic for polygon output
sizenumber100Output size for pathD

Return value

PropertyTypeDescription
pathDstringSVG path d attribute
clipPathstringCSS clip-path: polygon(...)
progressnumberCurrent animated progress. May overshoot 0-1 in spring mode.

The progress return value is the animated value but not the target. In spring mode it can temporarily overshoot past 0 or 1, which is useful for driving bouncy effects like rotation or scale. The shape output (pathD, clipPath) is always clamped to valid 0-1 range.

Lerp mode

Instead of a fixed duration, lerp mode moves a fraction toward the target each frame. This gives a natural, springy feel without specifying a duration.

const { clipPath } = useMorph("Clover4Leaf", "Heart", {
  progress: hovered ? 1 : 0,
  lerp: 0.1,
});

Spring mode

Spring mode simulates physical spring dynamics. The value overshoots the target, bounces back, and settles naturally. No duration needed. See Spring for details on tuning stiffness and damping.

const { clipPath, progress } = useMorph("Circle", "Heart", {
  progress: hovered ? 1 : 0,
  spring: { stiffness: 180, damping: 12 },
});

<div style={{
  clipPath,
  // progress can overshoot 0-1, giving a bouncy rotation
  transform: `rotate(${progress * 90}deg)`,
}} />

Custom easing

Override the default easing function with any (t: number) => number function. See Easing for built-in presets.

import { easeInOutCubic } from "shape-morph";

const { clipPath } = useMorph("Circle", "Heart", {
  progress: hovered ? 1 : 0,
  duration: 500,
  easing: easeInOutCubic,
});

useShape

Returns path data for a single shape. No animation.

import { useShape } from "shape-morph/react";

const { pathD, clipPath } = useShape("Heart", 100);

Return value

PropertyTypeDescription
pathDstringSVG path d attribute
clipPathstringCSS clip-path: polygon(...)
polygonRoundedPolygonThe underlying polygon instance

On this page