PParable
All components

Dynamic Island

Interactive

An iOS-style black pill that spring-morphs between a compact idle pill, an expanded card, and a circular ring state, with layout-animated size and corner-radius. Works controlled or uncontrolled; collapsed it is a button (Enter/Space expand), expanded it is a dialog that Escape, its close control, or an outside click collapses, with focus handed to the panel on open and back on close. Under prefers-reduced-motion the morph is skipped and states switch instantly.

Inspired by Cult UI — Dynamic Island (free, MIT) — independently built, not affiliated. Attribution policy

Installation

npx shadcn@latest add https://parable-three.vercel.app/r/dynamic-island.json

Runs the shadcn CLI — the component source is copied intocomponents/parable/dynamic-island.tsx. You own it from there.

Usage

The exact code rendering the preview above.

<div className="flex min-h-[240px] w-full items-center justify-center rounded-xl bg-neutral-950 p-6">
    <DynamicIsland
      defaultState="expanded"
      idleContent={
        <div className="flex items-center gap-2">
          <span className="size-2 rounded-full bg-violet-400" />
          <span className="text-[13px] font-medium">Now Playing</span>
        </div>
      }
    >
      <div className="flex items-center gap-3">
        <div className="grid size-11 shrink-0 place-items-center rounded-xl bg-violet-500/20 text-violet-300">
          <Music className="size-5" aria-hidden />
        </div>
        <div className="min-w-0 flex-1">
          <p className="truncate text-sm font-semibold text-white">Midnight City</p>
          <p className="truncate text-xs text-white/50">M83 · Hurry Up, We're Dreaming</p>
        </div>
      </div>
      <div className="mt-3 flex items-center gap-2">
        <button className="flex-1 rounded-lg bg-white/10 px-3 py-1.5 text-xs font-medium text-white transition-colors hover:bg-white/15">Pause</button>
        <button aria-label="Next track" className="grid size-8 place-items-center rounded-lg bg-white/10 text-white transition-colors hover:bg-white/15">
          <SkipForward className="size-4" aria-hidden />
        </button>
      </div>
    </DynamicIsland>
  </div>

Props

DynamicIslandProps extends Omit< React.HTMLAttributes<HTMLDivElement>, | "onAnimationStart" | "onAnimationEnd" | "onAnimationIteration" | "onDrag" | "onDragStart" | "onDragEnd" | "children" >

PropTypeDefaultDescription
stateDynamicIslandStateControlled state. Pair with `onStateChange`.
defaultStateDynamicIslandState"idle"Initial state when uncontrolled.
onStateChange(state: DynamicIslandState) => voidFires on every state change (both controlled and uncontrolled).
idleContentReact.ReactNodeCompact content shown in the `idle` pill. Falls back to a pulsing dot + `label`.
labelstring"Live"Label used by the default idle content when `idleContent` is not supplied.
accentstring"#8b5cf6"Accent colour for the idle dot and the ring stroke. Defaults to Parable violet (`--pb-violet`).
backgroundstring"#0f0f10"Pill face colour. Defaults to Parable ink `#0f0f10` (`--pb-ink`).
ringProgressnumber0.62Progress `0..1` for the `ring` state.
ringContentReact.ReactNodeCentre content for the `ring` state (e.g. a countdown or icon).
collapseOnOutsideClickbooleantrueCollapse to `idle` when a pointer press lands outside the island while expanded.
childrenReact.ReactNodeExpanded panel body — title, text, actions, anything.

npm dependencies

  • motion