The macOS Control Center panel in frosted glass: toggle tiles with round badges, grouped Wi-Fi, Bluetooth and AirDrop rows, pill sliders with the icon in the track, and a Now Playing tile. Real toggle buttons and Radix sliders.
Display
Sound
Midnight City
M83
import { Radio, Bluetooth, Moon, Monitor, Music, Sun, Volume2, Wifi, Keyboard } from "lucide-react"
import {
ControlCenter,
ControlCluster,
ControlNowPlaying,
ControlRow,
ControlSlider,
ControlTile,
} from "@/components/ballmac/control-center"
export default function ControlCenterDemo() {
return (
<div className="relative isolate flex w-full max-w-[640px] justify-center overflow-hidden rounded-2xl border border-foreground/10 px-4 py-10 sm:px-10">
<div aria-hidden="true" className="pointer-events-none absolute inset-0 -z-10 dark:brightness-[0.55]">
<div className="absolute inset-0 bg-linear-to-br from-chart-4 via-chart-1 to-chart-2" />
<div className="absolute -inset-x-1/4 top-1/2 h-full rounded-[50%] bg-white/25 blur-2xl" />
</div>
<ControlCenter>
<ControlCluster>
<ControlRow icon={<Wifi />} label="Wi-Fi" status="Home" defaultPressed />
<ControlRow icon={<Bluetooth />} label="Bluetooth" status="On" defaultPressed />
<ControlRow icon={<Radio />} label="AirDrop" status="Contacts" />
</ControlCluster>
<ControlTile icon={<Moon />} label="Focus" status="Off" />
<ControlTile icon={<Monitor />} label="Stage Manager" status="Off" />
<ControlSlider label="Display" icon={<Sun />} defaultValue={72} />
<ControlSlider label="Sound" icon={<Volume2 />} defaultValue={46} />
<ControlTile icon={<Keyboard />} label="Keyboard" status="50%" defaultPressed />
<ControlTile icon={<Music />} label="Shuffle" status="Off" />
<ControlNowPlaying title="Midnight City" artist="M83" defaultPlaying />
</ControlCenter>
</div>
)
}Installation
$ pnpm dlx shadcn@latest add @ballmac/control-centerInstall the dependencies.
$ pnpm add radix-ui lucide-reactAdd the Ballmac items it builds on.
$ pnpm dlx shadcn@latest add @ballmac/i18n @ballmac/mediaCopy the source into your project.
components/ballmac/control-center.tsx// Ballmac UI: Control Center. https://ui.ballmac.com/components/control-center "use client" import * as React from "react" import { Pause, Play, SkipBack, SkipForward } from "lucide-react" import { Slider as SliderPrimitive } from "radix-ui" import { cn } from "@/lib/utils" import { useMessages } from "@/lib/ballmac/i18n" import { Media, isMediaImage, type MediaSource } from "@/components/ballmac/media" /** Pressed state that works controlled or not. */ function usePressed(pressed: boolean | undefined, defaultPressed: boolean, onChange?: (pressed: boolean) => void) { const [state, setState] = React.useState(defaultPressed) const value = pressed ?? state return [ value, () => { setState(!value) onChange?.(!value) }, ] as const } type ControlCenterProps = React.ComponentProps<"section"> & { /** Accessible name of the panel. */ label?: string } /** The Control Center panel: frosted glass holding a grid of tiles, sliders and a media player. */ function ControlCenter({ label, className, ...props }: ControlCenterProps) { const msg = useMessages() label ??= msg("control-center.label", "Control Center") return ( <section data-slot="control-center" aria-label={label} className={cn( "grid w-[22.5rem] max-w-full grid-cols-2 gap-2.5 rounded-[26px] border border-white/20 bg-background/55 p-3 text-foreground shadow-[0_24px_60px_-12px_rgb(0_0_0/0.45),inset_0_1px_0_rgb(255_255_255/0.25)] backdrop-blur-3xl backdrop-saturate-200 [--mac-accent:oklch(0.53_0.2_258)] dark:bg-neutral-900/60", className )} {...props} /> ) } const tile = "rounded-[18px] bg-foreground/[0.07] dark:bg-white/[0.09]" type ControlTileProps = Omit<React.ComponentProps<"button">, "onChange"> & { /** Icon in the round badge. */ icon: React.ReactNode /** Name of the control. */ label: string /** Second line, such as the current state. */ status?: string /** Controlled on state. */ pressed?: boolean /** Initial on state when uncontrolled. */ defaultPressed?: boolean /** Called with the new on state. */ onPressedChange?: (pressed: boolean) => void /** Stack the badge over the label (a square tile) instead of beside it. */ stacked?: boolean } /** A toggle tile: a round icon badge that fills with the accent color when on. It is a real toggle button. */ function ControlTile({ icon, label, status, pressed, defaultPressed = false, onPressedChange, stacked, className, onClick, ...props }: ControlTileProps) { const [on, toggle] = usePressed(pressed, defaultPressed, onPressedChange) return ( <button type="button" data-slot="control-tile" aria-pressed={on} onClick={(e) => { onClick?.(e) if (!e.defaultPrevented) toggle() }} className={cn( tile, "group flex min-h-[4.25rem] items-center gap-2.5 p-2.5 text-start outline-none transition-[background-color,transform] duration-150 hover:bg-foreground/[0.11] focus-visible:ring-[3px] focus-visible:ring-ring/60 active:scale-[0.98] motion-reduce:transition-none motion-reduce:active:scale-100", stacked && "flex-col items-start justify-between", className )} {...props} > <span aria-hidden="true" className="flex size-9 shrink-0 items-center justify-center rounded-full bg-foreground/15 text-foreground transition-colors duration-200 group-aria-pressed:bg-(--mac-accent) group-aria-pressed:text-white motion-reduce:transition-none [&_svg]:size-[1.1rem]" > {icon} </span> <span className="min-w-0 leading-tight"> <span className="block truncate text-[13px] font-semibold">{label}</span> {status && <span className="block truncate text-xs text-muted-foreground">{status}</span>} </span> </button> ) } /** A tile that groups related toggle rows, such as Wi-Fi, Bluetooth and AirDrop. Place it first in the grid; it spans two rows. */ function ControlCluster({ className, ...props }: React.ComponentProps<"div">) { return <div data-slot="control-cluster" role="group" className={cn(tile, "row-span-2 flex flex-col justify-around gap-1 p-2.5", className)} {...props} /> } type ControlRowProps = Omit<ControlTileProps, "stacked"> /** One row inside a cluster: round badge plus name and state. */ function ControlRow({ icon, label, status, pressed, defaultPressed = false, onPressedChange, className, onClick, ...props }: ControlRowProps) { const [on, toggle] = usePressed(pressed, defaultPressed, onPressedChange) return ( <button type="button" data-slot="control-row" aria-pressed={on} onClick={(e) => { onClick?.(e) if (!e.defaultPrevented) toggle() }} className={cn( "group flex items-center gap-2.5 rounded-xl p-1 text-start outline-none transition-colors hover:bg-foreground/[0.06] focus-visible:ring-[3px] focus-visible:ring-ring/60 motion-reduce:transition-none", className )} {...props} > <span aria-hidden="true" className="flex size-9 shrink-0 items-center justify-center rounded-full bg-foreground/15 transition-colors duration-200 group-aria-pressed:bg-(--mac-accent) group-aria-pressed:text-white motion-reduce:transition-none [&_svg]:size-[1.1rem]" > {icon} </span> <span className="min-w-0 leading-tight"> <span className="block truncate text-[13px] font-semibold">{label}</span> {status && <span className="block truncate text-xs text-muted-foreground">{status}</span>} </span> </button> ) } type ControlSliderProps = Omit<React.ComponentProps<typeof SliderPrimitive.Root>, "value" | "defaultValue" | "onValueChange" | "min" | "max"> & { /** Name shown above the slider and used as its accessible name. */ label: string /** Icon drawn in the thumb's track (left side). */ icon?: React.ReactNode /** Controlled value from 0 to 100. */ value?: number /** Initial value when uncontrolled. */ defaultValue?: number /** Called with the new value. */ onValueChange?: (value: number) => void } /** A full-width pill slider with the icon inside the track and a white thumb, like Display and Sound in Control Center. */ function ControlSlider({ label, icon, value, defaultValue = 50, onValueChange, className, ...props }: ControlSliderProps) { const [inner, setInner] = React.useState(defaultValue) const current = value ?? inner return ( <div data-slot="control-slider" className={cn(tile, "col-span-2 px-3 pt-2 pb-2.5", className)}> <p className="mb-1.5 text-[13px] font-semibold">{label}</p> <SliderPrimitive.Root value={[current]} min={0} max={100} step={1} onValueChange={([v]) => { setInner(v!) onValueChange?.(v!) }} className="group relative flex h-7 w-full touch-none items-center select-none" {...props} > <SliderPrimitive.Track className="relative h-full grow overflow-hidden rounded-full bg-foreground/15 shadow-[inset_0_1px_2px_rgb(0_0_0/0.15)]"> <SliderPrimitive.Range className="absolute h-full bg-foreground/85 dark:bg-white/90" /> {icon && ( <span aria-hidden="true" className="pointer-events-none absolute inset-y-0 start-2 flex items-center text-background mix-blend-normal dark:text-neutral-800 [&_svg]:size-4"> {icon} </span> )} </SliderPrimitive.Track> <SliderPrimitive.Thumb aria-label={label} className="block size-7 rounded-full border border-black/10 bg-white shadow-[0_1px_4px_rgb(0_0_0/0.3)] outline-none focus-visible:ring-[3px] focus-visible:ring-ring/70" /> </SliderPrimitive.Root> </div> ) } type ControlNowPlayingProps = Omit<React.ComponentProps<"div">, "title"> & { /** Track title. */ title: string /** Artist or show. */ artist: string /** Artwork: an image URL, an object with alt text and a dark-mode file, or any element. Decorative: the title and artist carry the meaning. */ artwork?: MediaSource /** Whether it is playing (controlled). */ playing?: boolean /** Initial state when uncontrolled. */ defaultPlaying?: boolean /** Called with the new playing state. */ onPlayingChange?: (playing: boolean) => void /** Called for the previous button. */ onPrevious?: () => void /** Called for the next button. */ onNext?: () => void } /** A media tile with artwork, title, artist and previous, play or pause, next buttons. */ function ControlNowPlaying({ title, artist, artwork, playing, defaultPlaying = false, onPlayingChange, onPrevious, onNext, className, ...props }: ControlNowPlayingProps) { const msg = useMessages() const [on, toggle] = usePressed(playing, defaultPlaying, onPlayingChange) const btn = "flex size-9 items-center justify-center rounded-full outline-none transition-colors hover:bg-foreground/10 focus-visible:ring-[3px] focus-visible:ring-ring/60 motion-reduce:transition-none [&_svg]:size-[1.15rem] [&_svg]:fill-current" return ( <div data-slot="control-now-playing" className={cn(tile, "col-span-2 flex items-center gap-3 p-2.5", className)} {...props}> <div aria-hidden="true" className="size-14 shrink-0 overflow-hidden rounded-xl bg-gradient-to-br from-chart-4 to-chart-5 shadow-md [&>img]:size-full [&>img]:object-cover"> {isMediaImage(artwork) ? <Media media={artwork} alt="" fill className="size-full" /> : artwork} </div> <div className="min-w-0 flex-1 leading-tight"> <p className="truncate text-[13px] font-semibold">{title}</p> <p className="truncate text-xs text-muted-foreground">{artist}</p> <div className="-ms-2 mt-1 flex items-center"> <button type="button" aria-label={msg("control-center.previousTrack", "Previous track")} onClick={onPrevious} className={btn}> <SkipBack aria-hidden="true" /> </button> <button type="button" aria-label={on ? msg("control-center.pause", "Pause") : msg("control-center.play", "Play")} onClick={toggle} className={btn}> {on ? <Pause aria-hidden="true" /> : <Play aria-hidden="true" />} </button> <button type="button" aria-label={msg("control-center.nextTrack", "Next track")} onClick={onNext} className={btn}> <SkipForward aria-hidden="true" /> </button> </div> </div> </div> ) } export { ControlCenter, ControlTile, ControlCluster, ControlRow, ControlSlider, ControlNowPlaying, type ControlCenterProps, type ControlTileProps, type ControlSliderProps, type ControlNowPlayingProps, }Update the import paths to match your project setup.
Usage
import { ControlCenter, ControlTile, ControlCluster, ControlRow, ControlSlider, ControlNowPlaying } from "@/components/ballmac/control-center"The full example is in the Code tab above.
Examples
Tiles only
Display
Wi-Fi on, brightness 60%
"use client"
import * as React from "react"
import { Bluetooth, Moon, Plane, Sun, Wifi } from "lucide-react"
import { ControlCenter, ControlSlider, ControlTile } from "@/components/ballmac/control-center"
export default function ControlCenterCompact() {
const [wifi, setWifi] = React.useState(true)
const [brightness, setBrightness] = React.useState(60)
return (
<div className="flex w-full flex-col items-center gap-3 py-2">
<ControlCenter label="Quick settings" className="w-[19rem]">
<ControlTile stacked icon={<Wifi />} label="Wi-Fi" status={wifi ? "Connected" : "Off"} pressed={wifi} onPressedChange={setWifi} />
<ControlTile stacked icon={<Bluetooth />} label="Bluetooth" status="On" defaultPressed />
<ControlTile stacked icon={<Plane />} label="Airplane" status="Off" />
<ControlTile stacked icon={<Moon />} label="Focus" status="Off" />
<ControlSlider label="Display" icon={<Sun />} value={brightness} onValueChange={setBrightness} />
</ControlCenter>
<p className="text-sm text-muted-foreground" role="status">
Wi-Fi {wifi ? "on" : "off"}, brightness {brightness}%
</p>
</div>
)
}API reference
<ControlCenter>
| Prop | Type | Default |
|---|---|---|
labelAccessible name of the panel. | string | — |
<ControlTile>
| Prop | Type | Default |
|---|---|---|
icon*Icon in the round badge. | React.ReactNode | — |
label*Name of the control. | string | — |
statusSecond line, such as the current state. | string | — |
pressedControlled on state. | boolean | — |
defaultPressedInitial on state when uncontrolled. | boolean | false |
onPressedChangeCalled with the new on state. | (pressed: boolean) => void | — |
stackedStack the badge over the label (a square tile) instead of beside it. | boolean | — |
<ControlSlider>
| Prop | Type | Default |
|---|---|---|
label*Name shown above the slider and used as its accessible name. | string | — |
iconIcon drawn in the thumb's track (left side). | React.ReactNode | — |
valueControlled value from 0 to 100. | number | — |
defaultValueInitial value when uncontrolled. | number | 50 |
onValueChangeCalled with the new value. | (value: number) => void | — |
<ControlNowPlaying>
| Prop | Type | Default |
|---|---|---|
title*Track title. | string | — |
artist*Artist or show. | string | — |
artworkArtwork: an image URL, an object with alt text and a dark-mode file, or any element. Decorative: the title and artist carry the meaning. | MediaSource | — |
playingWhether it is playing (controlled). | boolean | — |
defaultPlayingInitial state when uncontrolled. | boolean | false |
onPlayingChangeCalled with the new playing state. | (playing: boolean) => void | — |
onPreviousCalled for the previous button. | () => void | — |
onNextCalled for the next button. | () => void | — |
Also accepts the standard attributes of its root element.
Accessibility
| Key | Action |
|---|---|
| Tab / Space / Enter | Every tile and row is a button with aria-pressed |
| Arrow keys | Adjust a slider; Home and End jump to the ends |
| Reduced motion | No press scale or color transitions |
Use with AI
<ControlCenter> is a 2-column grid. Put <ControlCluster> with <ControlRow> toggles first, then <ControlTile>, <ControlSlider label icon> and <ControlNowPlaying>. Toggles are controlled with pressed / onPressedChange or uncontrolled. With the shadcn MCP server set up (guide), ask your agent:
Add the Ballmac UI Control Center (@ballmac/control-center) to this project with the shadcn MCP, then use it where it fits.
Use it for
- Mac system UI mockups
- Quick-settings popovers
Not for
- Full settings pages (use field, switch, slider)
Registry JSON: https://ui.ballmac.com/r/control-center.json
Credits
Free to use in personal and commercial projects.
Pairs well with
Menu Bar
A translucent macOS menu bar on Radix Menubar: a bold app menu, menus with key equivalents, submenus and checkmarks, a status area with icon buttons and a hydration-safe live clock.
Mac Window
macOS window chrome: traffic lights whose glyphs appear on hover, a centered title or unified toolbar, an optional translucent sidebar with vibrancy, and active and inactive appearances.
HUD
The macOS volume and brightness HUD: a dark frosted square with a big icon and a 16-segment bar, or a slim capsule with a smooth bar. Pops in, restarts its timer when the value changes, fades out and announces the new level.
Switch
A Radix toggle switch in two sizes with a CSS-animated thumb, for settings that take effect immediately. Stops animating under reduced motion.