Tirup Mehta
›Components›Song Bubble

Component

Song Bubble

Install guide

A floating Spotify-style mini player: a small cover orb that springs into a draggable song card, streaming through a real YouTube engine inside it. Open it from anywhere in your app with one exported command, mount it once, and it never unmounts. Here on this page, live, then one install command. Zero dependencies, MIT licensed.

The card opens docked bottom-left. Drag it anywhere — it parks and remembers the spot while you browse.

01

Install

npx shadcn@latest add https://tirup.in/components/r/song-bubble.json
import { SongBubble, toggleSongBubble } from "@/components/song-bubble"

export function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html>
      <body>
        {children}
        {/* Mount once: it survives client-side navigation, so the song keeps
            playing while visitors move between pages */}
        <SongBubble />
      </body>
    </html>
  )
}

// Anything in your app can open it — a footer dot, a nav button, an easter egg
export function HeroDot() {
  return (
    <button
      type="button"
      onClick={toggleSongBubble}
      aria-label="Open the song bubble"
    >
      ♪
    </button>
  )
}

The player lives in your root layout so navigation can't kill it. Read more in the install guide.

02

Props

PropTypeDefaultDescription
defaultVideoUrlstringundefinedA song to load automatically the first time the card opens in a session — perfect for demo pages; leave it off for a quiet open.
defaultThumbnailstringundefinedCover art for the closed orb. Without it the orb draws a built-in vinyl disc.
accentstring"#1ed760"Accent color for the play button, wave bars, and focus rings. Green by default; pass any brand tone.
positionKeystring"song-bubble-pos"localStorage key for the parked position — use a unique key if you mount several.
zIndexnumber9999Stacking of the floating root.
03

Orb

Closed, the component is a 56px cover orb docked bottom-left (bottom-center on small screens) with a live pulse ring while audio plays. Click — or focus it and press Enter — and one frame at orb size is enough for the CSS spring to carry it into the 260px card, so the expansion always morphs instead of popping.

04

Park

Grab any quiet part of the card — the art, the title, blank space — and drag; buttons, inputs and sliders keep their own behavior. The card morphs between two sizes, so the clamp is checked against the larger size at every viewport: a spot that is legal for the orb cannot overflow once the card expands. On release the parked position lands in localStorage and survives reloads; closing fully resets the deck while the parking spot is the one thing that stays.

05

Engine

Audio is a hidden YouTube embed driven through the iframe postMessage API. Commands are buffered until the widget greets us — some embed boots crash silently if hammered before their api object attaches — then flushed in order, with a short 'listening' handshake poll for boots that only start broadcasting once addressed. A pasted link resolves through YouTube's oEmbed endpoint for the title, artist and artwork, and every seek / volume / state change is a one-line postMessage.

06

Skin

Every color reads your site's own theme tokens first (--bg, --fg, --border-color, --text-muted, --text-secondary) and falls back to the Spotify-dark values when a token doesn't exist, so the card looks at home in dark mode, light mode, and custom-tinted sites without a single prop. The accent is one CSS variable: pass an accent prop and the play button, wave bars, orb pulse and focus rings all follow.

07

Metrics

files

1

dependencies

0

engine

YouTube postMessage

persistence

localStorage

08

Honest limits

One song at a time; the embedded engine is YouTube (and obeys their embed rules, ads included). Closing the card forgets the song on purpose — the eraser is the point of an easter egg — though a parked position survives. The card is a fixed overlay, so it belongs on site chrome, not inside scrollable app panes; give it a single mount, not per-page anchors.

09

Inspiration credit

Inspired by Atomix, the floating mini player concept. The card, the drag engine, and the YouTube wiring here are from scratch.

10

Questions

What is Song Bubble?

Song Bubble is an open-source React component that floats on your site: a small cover orb that springs into a Spotify-style card with the song title, artist, a full-bleed progress line, transport and volume controls. Audio streams through a hidden YouTube embed driven by the iframe postMessage API, so any YouTube link becomes the song.

How do I install Song Bubble?

Run npx shadcn@latest add https://tirup.in/components/r/song-bubble.json in your Next.js or React project, mount <SongBubble /> once in your root layout, and open it from anywhere in your app with the exported toggle command. It reads your site's theme tokens and adapts to light and dark automatically.

Can I use Song Bubble in commercial projects?

Yes. It is MIT licensed: copy it, modify it, ship it in commercial projects. No keys, no lock-in.

Why mount it in the root layout?

The player is a fixed overlay with its own audio session. Mounted once at the layout level, client-side navigation never unmounts it: the song keeps playing while visitors move between pages. Per-page mounts would cut the audio every route change.

How do I open it from my own UI?

The same file exports the store commands the site itself uses: openSongBubble(), closeSongBubble(), and toggleSongBubble(), plus a useSongBubbleOpen() hook if you want to reflect the state. Everything from an easter-egg dot to a navbar button can be an entry point.

Does the card respect theme switching mid-session?

Yes. Every color is a CSS custom property read at render time (--bg, --fg, --border-color, --text-muted, --text-secondary) with sensible Spotify-dark fallbacks. Flip your theme or tint while a song plays and the card restyles in place without dropping the audio.

How does the YouTube engine actually work?

A hidden 320x180 iframe parks offscreen to keep the embed alive, commands are buffered until the widget is ready (some embed boots crash silently if hammered too early), and a short 'listening' handshake poll wakes boots that only start broadcasting once addressed. Metadata and artwork come from YouTube's oEmbed endpoint.