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.
"use client"
import * as React from "react"
import { Minus, Plus, Sun, Volume2, VolumeX } from "lucide-react"
import { Hud, useTransientHud, type HudKind } from "@/components/ballmac/hud"
export default function HudDemo() {
const hud = useTransientHud()
const [kind, setKind] = React.useState<HudKind>("volume")
const [volume, setVolume] = React.useState(50)
const [brightness, setBrightness] = React.useState(70)
const [muted, setMuted] = React.useState(false)
const value = kind === "volume" ? volume : brightness
const bump = (kind: HudKind, delta: number) => {
setKind(kind)
if (kind === "volume") {
setMuted(false)
setVolume((v) => Math.max(0, Math.min(100, v + delta)))
} else setBrightness((v) => Math.max(0, Math.min(100, v + delta)))
hud.show()
}
const btn =
"inline-flex h-9 items-center gap-1.5 rounded-full bg-black/35 px-3.5 text-sm font-medium text-white outline-none backdrop-blur hover:bg-black/45 focus-visible:ring-[3px] focus-visible:ring-white/70"
return (
<div className="relative isolate h-[380px] w-full max-w-3xl overflow-hidden rounded-2xl border border-foreground/10">
<div aria-hidden="true" className="absolute inset-0 -z-10 dark:brightness-[0.6]">
<div className="absolute inset-0 bg-linear-to-br from-chart-1 via-chart-4 to-chart-5" />
<div className="absolute -inset-x-1/4 top-1/2 h-full rounded-[50%] bg-white/25 blur-2xl" />
</div>
<div className="flex h-full flex-wrap content-start items-center justify-center gap-2.5 px-4 pt-10">
<button type="button" className={btn} onClick={() => bump("volume", -10)}><Minus className="size-4" aria-hidden="true" /><Volume2 className="size-4" aria-hidden="true" /><span className="sr-only">Volume down</span></button>
<button type="button" className={btn} onClick={() => bump("volume", 10)}><Plus className="size-4" aria-hidden="true" /><Volume2 className="size-4" aria-hidden="true" /><span className="sr-only">Volume up</span></button>
<button type="button" className={btn} onClick={() => { setKind("volume"); setMuted((m) => !m); hud.show() }}><VolumeX className="size-4" aria-hidden="true" />Mute</button>
<button type="button" className={btn} onClick={() => bump("brightness", -10)}><Minus className="size-4" aria-hidden="true" /><Sun className="size-4" aria-hidden="true" /><span className="sr-only">Brightness down</span></button>
<button type="button" className={btn} onClick={() => bump("brightness", 10)}><Plus className="size-4" aria-hidden="true" /><Sun className="size-4" aria-hidden="true" /><span className="sr-only">Brightness up</span></button>
</div>
<Hud kind={kind} value={value} muted={muted} visible={hud.visible} onVisibleChange={hud.onVisibleChange} />
</div>
)
}Installation
$ pnpm dlx shadcn@latest add @ballmac/hudInstall the dependencies.
$ pnpm add motion@^12 lucide-reactCopy the source into your project.
components/ballmac/hud.tsx// Ballmac UI: HUD. https://ui.ballmac.com/components/hud "use client" import * as React from "react" import { Keyboard, Sun, SunMedium, Volume, Volume1, Volume2, VolumeX } from "lucide-react" import { AnimatePresence, motion, useReducedMotion } from "motion/react" import { cn } from "@/lib/utils" type HudKind = "volume" | "brightness" | "keyboard" type HudProps = Omit<React.ComponentProps<"div">, "children"> & { /** What is being adjusted. Chooses the icon and the spoken name. */ kind?: HudKind /** Level from 0 to 100. */ value: number /** Show a muted speaker instead of the level (volume only). */ muted?: boolean /** Whether the HUD is on screen. */ visible: boolean /** Called with false when the HUD times out. */ onVisibleChange?: (visible: boolean) => void /** How long the HUD stays after the last change, in milliseconds. */ duration?: number /** "mac" is the dark square with a segmented bar; "pill" is a slim capsule with a smooth bar. */ variant?: "mac" | "pill" } const NAMES: Record<HudKind, string> = { volume: "Volume", brightness: "Brightness", keyboard: "Keyboard brightness" } function HudIcon({ kind, value, muted }: { kind: HudKind; value: number; muted?: boolean }) { if (kind === "brightness") return value > 50 ? <Sun aria-hidden="true" /> : <SunMedium aria-hidden="true" /> if (kind === "keyboard") return <Keyboard aria-hidden="true" /> if (muted || value === 0) return <VolumeX aria-hidden="true" /> if (value < 34) return <Volume aria-hidden="true" /> if (value < 67) return <Volume1 aria-hidden="true" /> return <Volume2 aria-hidden="true" /> } /** * The macOS volume and brightness HUD. It sits at the bottom center of its positioned parent (a capsule sits at the top), * pops in, restarts its timer whenever `value` changes, and fades out. Screen readers hear the new level. */ function Hud({ kind = "volume", value, muted = false, visible, onVisibleChange, duration = 1800, variant = "mac", className, ...props }: HudProps) { const reduce = useReducedMotion() const level = Math.max(0, Math.min(100, Math.round(value))) const shown = muted && kind === "volume" ? 0 : level const name = NAMES[kind] React.useEffect(() => { if (!visible) return const timer = setTimeout(() => onVisibleChange?.(false), duration) return () => clearTimeout(timer) }, [visible, level, muted, duration, onVisibleChange]) const filled = Math.round((shown / 100) * 16) const pill = variant === "pill" const motionProps = reduce ? { initial: { opacity: 0 }, animate: { opacity: 1 }, exit: { opacity: 0 } } : pill ? { initial: { opacity: 0, y: -14, scale: 0.96 }, animate: { opacity: 1, y: 0, scale: 1 }, exit: { opacity: 0, y: -8, scale: 0.98 } } : { initial: { opacity: 0, scale: 0.9 }, animate: { opacity: 1, scale: 1 }, exit: { opacity: 0, scale: 0.96 } } return ( <> <p role="status" className="sr-only"> {visible ? (muted && kind === "volume" ? "Volume muted" : `${name} ${level} percent`) : ""} </p> <AnimatePresence> {visible && ( <motion.div key="hud" data-slot="hud" data-variant={variant} aria-hidden="true" className={cn( "pointer-events-none absolute left-1/2 z-[60] -translate-x-1/2 text-white", pill ? "top-4 flex h-11 w-[min(18rem,calc(100%-2rem))] items-center gap-3 rounded-full border border-white/15 bg-neutral-900/75 px-4 shadow-[0_12px_32px_-8px_rgb(0_0_0/0.55)] backdrop-blur-2xl backdrop-saturate-150" : "bottom-[8%] flex aspect-square w-[11.5rem] max-w-[60%] flex-col items-center justify-between rounded-[26px] border border-white/10 bg-neutral-900/70 px-[1.1rem] pt-[1.5rem] pb-[1.4rem] shadow-[0_20px_50px_-10px_rgb(0_0_0/0.6)] backdrop-blur-3xl backdrop-saturate-150", className )} transition={{ type: "spring", stiffness: 520, damping: 34, mass: 0.7 }} {...(motionProps as object)} {...(props as object)} > {pill ? ( <> <span className="flex size-6 shrink-0 items-center justify-center [&_svg]:size-5"> <HudIcon kind={kind} value={shown} muted={muted} /> </span> <span className="h-1.5 flex-1 overflow-hidden rounded-full bg-white/25"> <span className="block h-full rounded-full bg-white transition-[width] duration-150 motion-reduce:transition-none" style={{ width: `${shown}%` }} /> </span> <span className="w-9 text-end text-sm font-medium tabular-nums">{shown}%</span> </> ) : ( <> <span className="flex flex-1 items-center justify-center [&_svg]:size-[4.4rem] [&_svg]:stroke-[1.4]"> <HudIcon kind={kind} value={shown} muted={muted} /> </span> <span className="flex w-full justify-between gap-[3px]"> {Array.from({ length: 16 }, (_, i) => ( <span key={i} className={cn("h-[9px] flex-1 rounded-[1.5px] transition-colors duration-100 motion-reduce:transition-none", i < filled ? "bg-white" : "bg-white/30")} /> ))} </span> </> )} </motion.div> )} </AnimatePresence> </> ) } /** State for a HUD: `show()` makes it visible; the HUD hides itself after its `duration`, and a new `value` restarts that timer. */ function useTransientHud() { const [visible, setVisible] = React.useState(false) const show = React.useCallback(() => setVisible(true), []) const hide = React.useCallback(() => setVisible(false), []) return { visible, show, hide, onVisibleChange: setVisible } } export { Hud, useTransientHud, type HudProps, type HudKind }Update the import paths to match your project setup.
Usage
import { Hud, useTransientHud } from "@/components/ballmac/hud"The full example is in the Code tab above.
Examples
Capsule variant
"use client"
import * as React from "react"
import { Hud, useTransientHud } from "@/components/ballmac/hud"
export default function HudPill() {
const hud = useTransientHud()
const [value, setValue] = React.useState(35)
return (
<div className="relative isolate h-[240px] w-full max-w-xl overflow-hidden rounded-2xl border border-foreground/10">
<div aria-hidden="true" className="absolute inset-0 -z-10 bg-linear-to-b from-chart-2 to-chart-1 dark:brightness-[0.6]" />
<div className="flex h-full items-end justify-center gap-4 p-6">
<label className="flex w-full max-w-xs flex-col gap-2 rounded-xl bg-black/35 p-3 text-sm font-medium text-white backdrop-blur">
Volume
<input
type="range"
min={0}
max={100}
value={value}
onChange={(e) => {
setValue(Number(e.target.value))
hud.show()
}}
className="w-full accent-white"
/>
</label>
</div>
<Hud variant="pill" value={value} visible={hud.visible} onVisibleChange={hud.onVisibleChange} />
</div>
)
}API reference
| Prop | Type | Default |
|---|---|---|
kindWhat is being adjusted. Chooses the icon and the spoken name. | HudKind | "volume" |
value*Level from 0 to 100. | number | — |
mutedShow a muted speaker instead of the level (volume only). | boolean | false |
visible*Whether the HUD is on screen. | boolean | — |
onVisibleChangeCalled with false when the HUD times out. | (visible: boolean) => void | — |
durationHow long the HUD stays after the last change, in milliseconds. | number | 1800 |
variant"mac" is the dark square with a segmented bar; "pill" is a slim capsule with a smooth bar. | "mac" | "pill" | "mac" |
Also accepts the standard attributes of its root element.
Accessibility
| Key | Action |
|---|---|
| Screen readers | A polite status message says the new level; the visual HUD is hidden from them |
| Reduced motion | Fades without scaling |
Use with AI
<Hud kind="volume" value={60} visible={visible} onVisibleChange={setVisible} /> inside a relative container. useTransientHud() gives { visible, show, hide, onVisibleChange }. With the shadcn MCP server set up (guide), ask your agent:
Add the Ballmac UI HUD (@ballmac/hud) to this project with the shadcn MCP, then use it where it fits.
Use it for
- Keyboard-driven level changes in desktop UI demos
- Transient feedback for hardware-style controls
Not for
- Toasts and messages (toast)
- Persistent controls (slider)
Registry JSON: https://ui.ballmac.com/r/hud.json
Credits
Free to use in personal and commercial projects.
- Registry
- shadcn/utils
Pairs well with
Control Center
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.
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.
Keyboard Shortcuts
A searchable shortcut cheat sheet, inline or in a dialog opened by pressing ?, with Command or Ctrl keys chosen for the viewer's platform and spoken key names.
Desktop Icons
A macOS desktop of icons: box-select with the pointer, drag to move with snapping to grid cells, double-click or Return to open, arrow keys to move focus and Alt plus arrows to move an icon.