About
A media card that shows what glass can do over artwork. It carries no audio: connect onPlayingChange to your player.
Use it when
- Music, podcast or radio sites.
- A widget over a hero or a 3D background.
Skip it when
- As a full audio player with a queue: build that on top.
Install
- 1Add the VibeBox registry to
components.json:{ "registries": { "@vibebox": "https://motion.vibeboxph.com/r/{name}.json" } } - 2Add the component with the shadcn CLI:
npx shadcn@latest add @vibebox/glass-player
AI prompt
Everything an AI coding tool needs to rebuild this component the way it looks here: colours, fonts, the art it draws, props and rules.
Read the prompt
Build the VibeBox Motion component "Glass Player" (Liquid glass) in a Next.js App Router project with Tailwind CSS v4.
What it is
- A now-playing card on glass with art, a progress bar and play, back and forward controls.
- A music player card: album art (an image or a token gradient), title and artist, a progress bar that rides one scaleX, timecodes and transport buttons. Audio is yours to wire.
Install
- `npx shadcn@latest add @vibebox/glass-player`
- Registry: add `"@vibebox": "https://motion.vibeboxph.com/r/{name}.json"` to components.json, then run the command. The source lands in `components/vibebox/glass-player` and is yours to edit.
Visual spec
- Colours (the VibeBox spectrum, the same in light and dark):
- --spectrum-1: #00c5fc (cyan)
- --spectrum-2: #1d6ef0 (blue)
- --spectrum-3: #5225ed (indigo)
- --spectrum-4: #e434dc (magenta)
- --spectrum-5: #fd3aa9 (pink)
- --spectrum-6: #f98472 (coral)
- --spectrum-7: #fcbe72 (amber)
- Ink surface (dark): background #05070c, card #0a0d15, text #f5f6fa, muted text #a3abbd, border rgba(255,255,255,0.08).
- Paper surface (light): background #f3f0ea, card #fbfaf7, text #0c0e14, muted text #4a4f5c, border rgba(12,14,20,0.12).
- Fonts:
- Display and body: Figtree (400, 500, 600, 700), falling back to ui-sans-serif, system-ui.
- Emphasis: Fraunces italic, for one or two words of a heading.
- Labels and code: JetBrains Mono (400, 500), uppercase labels with 0.14em tracking.
- Shape: radius 8 to 12px on cards and panels; pills may be fully round. Hairline borders, soft glow instead of heavy shadow.
- Motion: animate transform and opacity only; fast ease-out (cubic-bezier(0.16, 1, 0.3, 1)), 140ms for micro-interactions, 320 to 520ms for reveals.
Art and media
- Built from HTML, CSS and inline SVG only; no image files or video.
Props
- `title`: `string` (default "Midnight Reverie"). Track title.
- `artist`: `string` (default "Luna Park"). Artist.
- `art`: `string`. Album art URL; a token gradient is used without it.
- `artAlt`: `string` (default ""). Alt text for the art.
- `duration`: `number` (default 214). Track length in seconds.
- `playing`: `boolean`. Controlled playing state.
- `onPlayingChange`: `(playing: boolean) => void`. Called when play or pause is pressed.
- `tone`: `"auto" | "dark" | "light"` (default "auto"). Tints the glass and sets the text colour for the page behind it.
- `className`: `string`. Merged with cn(); sets the width and margins.
Behaviour and access
- Reduced motion: The pointer sheen and sliding are off; state changes are instant.
- Keyboard: Buttons are native and focusable; Space or Enter toggles play.
- Screen readers: A section named Now playing; the progress is a progressbar with its value; play is a pressed toggle.
- Touch: Buttons are 40 to 48 px.
- Budget: about 3 KB of JavaScript, WebGL none, GPU low. It pauses while off screen.
Theming
The surface reads `--g-tint`, `--g-edge`, `--g-ink`, `--g-blur` and `--g-radius` from its root; the demo backdrop uses `--spectrum-*`.
Notes
Add the VibeBox Motion Glass Player with `npx shadcn@latest add @vibebox/glass-player`. Pass the track `title`, `artist`, an `art` image URL with alt text and the `duration`, and wire `onPlayingChange` to your audio element. Keep the progress bar on a single scaleX and the 15-second skip buttons.
Rules
- Keep the licence header on every installed file.
- Original code only: do not copy components from other libraries, and do not add GSAP.
- Use tokens for colour, type and radius; no hard-coded brand values.
- Server Components by default; `"use client"` only for the island.Code
2 filescomponents/vibebox/glass-player/glass-player.client.tsx
/**
* VibeBox Motion · Glass Player
* Copyright (c) 2026 VibeBox. Licensed under the VibeBox Motion Free License:
* https://motion.vibeboxph.com/license
* Use it in unlimited personal, commercial and client projects.
* Do not sell or redistribute it as a component, kit, template or builder.
*/
"use client";
import { useEffect, useState } from "react";
import { cn } from "@/lib/utils";
import { Glass, GlassGlyphIcon } from "@/lib/vibebox/glass";
export type GlassPlayerProps = {
title?: string;
artist?: string;
/** Album art: an image URL. Without it the art is a gradient from the spectrum tokens. */
art?: string;
/** Alt text for the art. */
artAlt?: string;
/** Track length in seconds. */
duration?: number;
/** Starts playing (the progress runs; no audio is played, you wire that). */
playing?: boolean;
onPlayingChange?: (playing: boolean) => void;
tone?: "auto" | "dark" | "light";
className?: string;
};
const time = (seconds: number) => `${Math.floor(seconds / 60)}:${String(Math.floor(seconds % 60)).padStart(2, "0")}`;
/**
* A now-playing card. The progress runs on a timer while playing and rides one `scaleX`; the audio
* itself is yours to wire to `onPlayingChange`. Art is an image or a gradient from the tokens.
*/
export function GlassPlayer({ title = "Midnight Reverie", artist = "Luna Park", art, artAlt = "", duration = 214, playing, onPlayingChange, tone, className }: GlassPlayerProps) {
const [own, setOwn] = useState(false);
const [elapsed, setElapsed] = useState(62);
const on = playing ?? own;
useEffect(() => {
if (!on) return;
const id = window.setInterval(() => setElapsed((value) => (value + 0.5 >= duration ? 0 : value + 0.5)), 500);
return () => window.clearInterval(id);
}, [on, duration]);
const toggle = () => {
setOwn(!on);
onPlayingChange?.(!on);
};
const step = (delta: number) => setElapsed((value) => Math.min(duration, Math.max(0, value + delta)));
return (
<Glass as="section" data-vb="glass-player" aria-label="Now playing" tone={tone} className={cn("w-full max-w-[22rem] p-4", className)}>
<div className="flex items-center gap-3.5">
<div className="relative size-[4.5rem] flex-none overflow-hidden rounded-lg border border-white/25 shadow-[0_12px_24px_-12px_rgb(0_0_0/0.6)]">
{art ? (
// eslint-disable-next-line @next/next/no-img-element
<img src={art} alt={artAlt} className="size-full object-cover" />
) : (
<span aria-hidden="true" className="absolute inset-0 bg-[radial-gradient(circle_at_30%_25%,var(--spectrum-3),transparent_62%),radial-gradient(circle_at_75%_80%,var(--spectrum-5),transparent_60%),linear-gradient(135deg,var(--spectrum-1),var(--spectrum-6))]" />
)}
</div>
<div className="min-w-0">
<p className="truncate text-[1rem] leading-tight font-semibold">{title}</p>
<p className="mt-0.5 truncate text-[0.84rem] opacity-70">{artist}</p>
</div>
</div>
<div className="mt-4">
<div role="progressbar" aria-label="Track progress" aria-valuemin={0} aria-valuemax={duration} aria-valuenow={Math.round(elapsed)} className="h-1 overflow-hidden rounded-full bg-white/20">
<span className="block h-full origin-left rounded-full bg-white/90 transition-transform duration-500 ease-linear motion-reduce:transition-none" style={{ transform: `scaleX(${elapsed / duration})` }} />
</div>
<div className="mt-1.5 flex justify-between font-mono text-[0.68rem] opacity-65">
<span>{time(elapsed)}</span>
<span>-{time(duration - elapsed)}</span>
</div>
</div>
<div className="mt-2 flex items-center justify-center gap-3">
<button type="button" aria-label="Back 15 seconds" onClick={() => step(-15)} className="grid size-10 place-items-center rounded-full transition-colors hover:bg-white/15 focus-visible:outline-2 focus-visible:outline-ring">
<GlassGlyphIcon name="prev" />
</button>
<button
type="button"
aria-label={on ? "Pause" : "Play"}
aria-pressed={on}
onClick={toggle}
className="grid size-12 place-items-center rounded-full border border-white/35 bg-gradient-to-b from-white/45 to-white/15 shadow-[inset_0_1px_0_rgb(255_255_255/0.6)] transition-transform active:scale-95 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ring"
>
<GlassGlyphIcon name={on ? "pause" : "play"} className="size-6" />
</button>
<button type="button" aria-label="Forward 15 seconds" onClick={() => step(15)} className="grid size-10 place-items-center rounded-full transition-colors hover:bg-white/15 focus-visible:outline-2 focus-visible:outline-ring">
<GlassGlyphIcon name="next" />
</button>
</div>
</Glass>
);
}components/vibebox/glass-player/index.tsx
/**
* VibeBox Motion · Glass Player
* Copyright (c) 2026 VibeBox. Licensed under the VibeBox Motion Free License:
* https://motion.vibeboxph.com/license
* Use it in unlimited personal, commercial and client projects.
* Do not sell or redistribute it as a component, kit, template or builder.
*/
export * from "./glass-player.client";Props
| Prop | Type | Default | Description |
|---|---|---|---|
| title | string | "Midnight Reverie" | Track title. |
| artist | string | "Luna Park" | Artist. |
| art | string | Album art URL; a token gradient is used without it. | |
| artAlt | string | "" | Alt text for the art. |
| duration | number | 214 | Track length in seconds. |
| playing | boolean | Controlled playing state. | |
| onPlayingChange | (playing: boolean) => void | Called when play or pause is pressed. | |
| tone | "auto" | "dark" | "light" | "auto" | Tints the glass and sets the text colour for the page behind it. |
| className | string | — | Merged with cn(); sets the width and margins. |
Accessibility and motion
- Keyboard
- Buttons are native and focusable; Space or Enter toggles play.
- Screen readers
- A section named Now playing; the progress is a progressbar with its value; play is a pressed toggle.
- Touch
- Buttons are 40 to 48 px.
- Reduced motion
- The pointer sheen and sliding are off; state changes are instant.
Related

Glass Panel
FreeThe base liquid glass surface: blurred, rimmed and sheened, as a titled card.
Liquid glass · CSS

Glass Controls
FreeA control-centre panel: glass toggle tiles and sliders that are real range inputs.
Liquid glass · CSS

Glass Dialog
FreeA confirmation dialog on glass over a dimmed scrim, with focus kept inside and Escape to cancel.
Liquid glass · CSS