Dynamic Island
InteractiveAn 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
Midnight City
M83 · Hurry Up, We're Dreaming
Installation
npx shadcn@latest add https://parable-three.vercel.app/r/dynamic-island.jsonRuns 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" >
| Prop | Type | Default | Description |
|---|---|---|---|
| state | DynamicIslandState | — | Controlled state. Pair with `onStateChange`. |
| defaultState | DynamicIslandState | "idle" | Initial state when uncontrolled. |
| onStateChange | (state: DynamicIslandState) => void | — | Fires on every state change (both controlled and uncontrolled). |
| idleContent | React.ReactNode | — | Compact content shown in the `idle` pill. Falls back to a pulsing dot + `label`. |
| label | string | "Live" | Label used by the default idle content when `idleContent` is not supplied. |
| accent | string | "#8b5cf6" | Accent colour for the idle dot and the ring stroke. Defaults to Parable violet (`--pb-violet`). |
| background | string | "#0f0f10" | Pill face colour. Defaults to Parable ink `#0f0f10` (`--pb-ink`). |
| ringProgress | number | 0.62 | Progress `0..1` for the `ring` state. |
| ringContent | React.ReactNode | — | Centre content for the `ring` state (e.g. a countdown or icon). |
| collapseOnOutsideClick | boolean | true | Collapse to `idle` when a pointer press lands outside the island while expanded. |
| children | React.ReactNode | — | Expanded panel body — title, text, actions, anything. |
npm dependencies
- motion