About
Settings that feel like a device. Use it for smart-home demos, dashboards and product tours.
Use it when
- Quick settings, smart-home and device demos.
Skip it when
- Forms that need validation or long labels.
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-controls
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 Controls" (Liquid glass) in a Next.js App Router project with Tailwind CSS v4.
What it is
- A control-centre panel: glass toggle tiles and sliders that are real range inputs.
- A control panel on glass: toggle tiles that switch to a bright face, and sliders built on native range inputs, so keyboard and screen readers work.
Install
- `npx shadcn@latest add @vibebox/glass-controls`
- Registry: add `"@vibebox": "https://motion.vibeboxph.com/r/{name}.json"` to components.json, then run the command. The source lands in `components/vibebox/glass-controls` 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
- `toggles`: `{ id: string; label: string; glyph: GlassGlyph; on?: boolean }[]`. The toggle tiles.
- `sliders`: `{ id: string; label: string; glyph: GlassGlyph; value: number }[]`. The sliders (0 to 100).
- `onToggle`: `(id: string, on: boolean) => void`. Called when a tile flips.
- `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: Tiles are buttons with aria-pressed; sliders are range inputs (arrow keys, Home, End).
- Screen readers: Toggles expose pressed state; sliders carry their label and value.
- Touch: Tiles are 68 px tall; sliders 44 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 Controls with `npx shadcn@latest add @vibebox/glass-controls`. Pass `toggles` and `sliders` with a glyph name each, listen to `onToggle`, and keep sliders as native range inputs so keyboard and assistive tech keep working.
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-controls/glass-controls.client.tsx
/**
* VibeBox Motion · Glass Controls
* 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 { useState } from "react";
import { cn } from "@/lib/utils";
import { Glass, GlassGlyphIcon, type GlassGlyph } from "@/lib/vibebox/glass";
export type GlassToggle = { id: string; label: string; glyph: GlassGlyph; on?: boolean };
export type GlassControlsProps = {
/** The toggle tiles, two per row. */
toggles?: GlassToggle[];
/** Slider names and their starting values (0 to 100). */
sliders?: { id: string; label: string; glyph: GlassGlyph; value: number }[];
onToggle?: (id: string, on: boolean) => void;
tone?: "auto" | "dark" | "light";
className?: string;
};
const TOGGLES: GlassToggle[] = [
{ id: "wifi", label: "Wi-Fi", glyph: "wifi", on: true },
{ id: "bluetooth", label: "Bluetooth", glyph: "bluetooth", on: true },
{ id: "focus", label: "Focus", glyph: "moon" },
{ id: "alerts", label: "Alerts", glyph: "bell", on: true },
];
const SLIDERS = [
{ id: "brightness", label: "Brightness", glyph: "sun" as GlassGlyph, value: 72 },
{ id: "volume", label: "Volume", glyph: "volume" as GlassGlyph, value: 40 },
];
/**
* A control centre: glass tiles that switch on to a bright face, and sliders that are real range
* inputs (so keyboard and screen readers work) with a glass track.
*/
export function GlassControls({ toggles = TOGGLES, sliders = SLIDERS, onToggle, tone, className }: GlassControlsProps) {
const [state, setState] = useState<Record<string, boolean>>(() => Object.fromEntries(toggles.map((toggle) => [toggle.id, Boolean(toggle.on)])));
const [levels, setLevels] = useState<Record<string, number>>(() => Object.fromEntries(sliders.map((slider) => [slider.id, slider.value])));
return (
<Glass as="section" data-vb="glass-controls" aria-label="Controls" tone={tone} still className={cn("w-full max-w-[20rem] p-3", className)}>
<ul className="grid grid-cols-2 gap-2">
{toggles.map((toggle) => {
const on = Boolean(state[toggle.id]);
return (
<li key={toggle.id}>
<button
type="button"
aria-pressed={on}
onClick={() => {
setState((value) => ({ ...value, [toggle.id]: !on }));
onToggle?.(toggle.id, !on);
}}
className={cn(
"flex h-[4.25rem] w-full flex-col justify-between rounded-lg border p-2.5 text-left transition-colors duration-200 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ring",
on ? "border-white/60 bg-white/85 text-[#101521] shadow-[0_8px_20px_-10px_rgb(255_255_255/0.6)]" : "border-white/20 bg-white/10 hover:bg-white/15",
)}
>
<GlassGlyphIcon name={toggle.glyph} className="size-[1.15rem]" />
<span className="flex items-center justify-between text-[0.8rem] font-medium">
{toggle.label}
<span className="font-mono text-[0.62rem] opacity-60">{on ? "On" : "Off"}</span>
</span>
</button>
</li>
);
})}
</ul>
<ul className="mt-2 flex flex-col gap-2">
{sliders.map((slider) => (
<li key={slider.id} className="relative h-11 overflow-hidden rounded-lg border border-white/20 bg-white/10">
<span aria-hidden="true" className="absolute inset-0 origin-left bg-white/70" style={{ transform: `scaleX(${(levels[slider.id] ?? 0) / 100})` }} />
<GlassGlyphIcon name={slider.glyph} className="pointer-events-none absolute top-1/2 left-3 size-[1.1rem] -translate-y-1/2 mix-blend-difference" />
<input
type="range"
min={0}
max={100}
value={levels[slider.id] ?? 0}
aria-label={slider.label}
onChange={(event) => setLevels((value) => ({ ...value, [slider.id]: Number(event.target.value) }))}
className="absolute inset-0 size-full cursor-pointer appearance-none bg-transparent opacity-0 focus-visible:opacity-100 focus-visible:outline-2 focus-visible:outline-ring"
/>
</li>
))}
</ul>
</Glass>
);
}components/vibebox/glass-controls/index.tsx
/**
* VibeBox Motion · Glass Controls
* 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-controls.client";Props
| Prop | Type | Default | Description |
|---|---|---|---|
| toggles | { id: string; label: string; glyph: GlassGlyph; on?: boolean }[] | The toggle tiles. | |
| sliders | { id: string; label: string; glyph: GlassGlyph; value: number }[] | The sliders (0 to 100). | |
| onToggle | (id: string, on: boolean) => void | Called when a tile flips. | |
| 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
- Tiles are buttons with aria-pressed; sliders are range inputs (arrow keys, Home, End).
- Screen readers
- Toggles expose pressed state; sliders carry their label and value.
- Touch
- Tiles are 68 px tall; sliders 44 px.
- Reduced motion
- The pointer sheen and sliding are off; state changes are instant.
Related

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

Glass Dock
FreeA floating dock of glass icons that swell toward the pointer, with labels and badges.
Liquid glass · CSS

Glass Nav
FreeA floating pill header on glass with a highlight that glides between links.
Liquid glass · CSS