WebberUI

Play/Pause Morph

A graphical button that morphs smoothly between a play triangle and pause bars, with controlled and uncontrolled modes and an optional progress ring.

Loading preview…
npx shadcn@latest add https://webberui.com/r/play-pause-morph.json

Playground

Tune the props live — the code snippet updates as you go, so you can dial in the look you want before copying it.

0.4
<PlayPauseMorph />

Installation

npx shadcn@latest add https://webberui.com/r/play-pause-morph.json

Or, once registries are configured in components.json, install it as @webberui/play-pause-morph.

Usage

import { PlayPauseMorph } from "@/components/ui/play-pause-morph";

<PlayPauseMorph defaultPlaying={false} onPlayingChange={(p) => console.log(p)} />

Controlled mode and the progress ring

const [playing, setPlaying] = React.useState(false);

<PlayPauseMorph
  size="lg"
  playing={playing}
  onPlayingChange={setPlaying}
  progress={0.42}
/>

Passing playing enters controlled mode: the built-in state is disabled and the toggle is entirely driven by your state. Passing progress (0–1) adds a progress ring around the outside of the button.

Props

PropTypeDefaultDescription
playingbooleanControlled playing state; when provided, it is driven from outside
defaultPlayingbooleanfalseInitial playing state in uncontrolled mode
onPlayingChange(playing: boolean) => voidFires on toggle, returning the state after the toggle
variant"solid" | "soft" | "ghost""solid"Visual variant
size"sm" | "md" | "lg""md"Size (32 / 40 / 48px)
progressnumberOptional progress from 0–1; when provided, the outer progress ring is shown
playLabelstring"播放"aria-label while paused (pressing it will play)
pauseLabelstring"暫停"aria-label while playing (pressing it will pause)

All other native <button> attributes (disabled, onClick, className, and so on) are forwarded. Calling preventDefault() inside onClick intercepts that toggle (handy for doing an asynchronous authorization check first).

How it works

  • The play triangle is cut down its midline into two quadrilaterals, sharing the same four-point path structure as the two pause bars, which makes it possible to interpolate the SVG d attribute point by point so the shape genuinely "morphs" rather than cross-fading.
  • The morph uses a spring transition, so it settles with a natural spring back.
  • The progress ring normalizes pathLength to 1 and expresses the played ratio through strokeDashoffset, tweening smoothly as the value changes.

Accessibility

  • Built on a native <button>, so both Enter and Space trigger it and focus has a clear focus-visible outline.
  • The aria-label follows the state: "暫停" while playing and "播放" while paused, describing precisely what pressing it will do.
  • The graphics are hidden from assistive technology with aria-hidden, so no meaningless vector content is announced.
  • When the user has "reduce motion" enabled at the system level, the morph and the scale feedback snap between states, with no effect on functionality.

On this page