SVG Morph Button for React

Button with SVG path morph animation. Supports custom paths for idle, hover, loading, success.

Component

Installation

 

Usage

Import

Add the SvgMorphButton import.

import { SvgMorphButton } from "@/components/svg-morph-button";

Use

Use with children. Click to see loading → success morph.

<SvgMorphButton>Submit</SvgMorphButton>;

Guidelines

  • Default paths: plus (idle), x (hover), circle (loading), check (success).
  • Pass customPaths to override. Keys: idle, hover, loading, success.
  • morphDuration controls animation speed.

Props

All props are optional unless marked required. Use these to customize every aspect of the component.

PropTypeDefaultDescription
classNamestring-Additional CSS classes.
customPathsPartial<Record<string, string>>-Custom paths for idle, hover, loading, success.
morphDurationnumber0.35Morph animation duration in seconds.
idleLabelstring"Click me"Label when idle.
loadingLabelstring"Loading..."Label during loading state.
successLabelstring"Done!"Label when success.

Accessibility

  • Renders a native button element so it is focusable and operable with the keyboard via Enter and Space out of the box.
  • It provides no aria-label, aria-busy, or aria-live region, so the loading and success state transitions are conveyed only visually and are not announced to screen readers.
  • The morph icon is correctly marked aria-hidden true so assistive tech ignores the decorative SVG path.
  • The morph only reacts to mouse enter and leave with no focus or keyboard equivalent, so keyboard users never trigger the hover state.
  • There is no focus-visible ring styling beyond the browser default, so a clearer focus indicator should be added for keyboard users.
  • It does not check prefers-reduced-motion, so the path morph and the spinner always run and the component should be guarded for users who prefer reduced motion.

Performance

  • The morph animates the SVG path d attribute every frame by calling setAttribute with a freshly interpolated flubber path, which is main-thread geometry recomputation and path repaint rather than a GPU-composited transform or opacity change.
  • The loading spinner uses the Tailwind animate-spin class which is a CSS transform rotate and is GPU-composited and cheap.
  • gsap drives the tween on its own requestAnimationFrame loop and the effect cleanup calls tween.kill so the animation is stopped when state changes or the component unmounts.
  • The setTimeout timers that advance loading to success to idle are never cleared, so they can fire after unmount and attempt a state update on an unmounted component.
  • There is no will-change hint, IntersectionObserver visibility gating, or reduced-motion gating, so the morph runs even when offscreen or when the user prefers reduced motion.

Examples

Basic

Default button with plus → loading → check morph.

import { SvgMorphButton } from "@/components/svg-morph-button";

<SvgMorphButton>
  <span>Submit</span>
</SvgMorphButton>;

Custom paths

Custom SVG paths for each state.

import { SvgMorphButton } from "@/components/svg-morph-button";

const paths = {
  idle: "M12 2 L14 2 L14 10 L22 10 L22 12 L14 12 L14 22 L12 22 Z",
  hover: "M12 4 L20 12 L12 20 L4 12 Z",
  loading: "M12 4 A8 8 0 0 1 20 12 A8 8 0 0 1 12 20 Z",
  success: "M5 12 L10 17 L19 6 Z",
};

<SvgMorphButton customPaths={paths}>
  <span>Custom</span>
</SvgMorphButton>;

Last updated on Jun 19

Made with ❤️ by Pulkit &

© 2026 Pulkit. All rights reserved

Last updated: