The macOS volume and brightness overlay: a frosted, click-through HUD that shows a level for a moment and updates in place while it is up, in the classic square or the macOS 26 pill.
When to use
- A Mac app (Tauri, Electron or a web app that looks native) that changes a level from a shortcut or a media key: volume, display or keyboard brightness, or your own level such as playback speed.
- Feedback for a key held down: every repeat moves the bar without the HUD flickering or re-entering.
- A desktop-style web demo where the overlay should feel like the system's own.
When not to use
- A level people set by dragging: use a slider, which already shows its value.
- Messages and confirmations ("Copied", "Saved"): announce them in a toast or inline status.
- Anything people must act on: the HUD ignores the pointer and leaves after 1.1 seconds. For a reversible action use Undo Pill.
Installation
npx shadcn add https://eased.dev/r/mac-hud.jsonThe command adds Mac HUD, everything it builds on and Eased’s tokens to your project.
Usage
useMacHud holds the state and the hold timer. Call show(kind, value) on every change and spread hud onto <MacHud>.
import { useEffect, useRef } from "react";import { MacHud, useMacHud } from "@/components/eased/mac-hud/mac-hud";export function Player() { const { hud, show } = useMacHud(); const volume = useRef(0.5); useEffect(() => { const onKey = (e: KeyboardEvent) => { if (e.key !== "ArrowUp" && e.key !== "ArrowDown") return; // One of sixteen segments per press, as on a Mac. const next = Math.round(volume.current * 16) + (e.key === "ArrowUp" ? 1 : -1); volume.current = Math.min(16, Math.max(0, next)) / 16; show("volume", volume.current); }; window.addEventListener("keydown", onKey); return () => window.removeEventListener("keydown", onKey); }, [show]); return <MacHud {...hud} />;}show while the HUD is up updates it in place and restarts the 1.1-second hold, so a held key keeps it on screen until the last repeat.
Examples
Pill
The macOS 26 capsule, 12px in from the top-right corner. Put it under your own menu bar by rendering it in a positioned box that starts below the bar.
<MacHud {...hud} variant="pill" />Muted
Pass muted with the volume: the speaker gets a slash and the bar empties, and the level comes back when you unmute.
show("volume", volume, { muted: true });Inside a container
position="absolute" places it in the nearest positioned parent instead of the viewport. The classic HUD sits 140px above the bottom, or centres in a container too short for that.
<div className="relative h-80"> <MacHud {...hud} position="absolute" /></div>In a desktop window
A HUD floats above every app, so in Tauri or Electron it lives in its own small window that is exactly the HUD's size. In both:
- The window draws real vibrancy, and the HUD takes
material="native"andposition="none". A CSS blur can't see the desktop through a transparent window, so"css"there gives an unblurred tint. - Make the page transparent with
html, body { background: transparent; }(shadcn'sbg-backgroundonbodycovers the vibrancy) and calluseSystemAppearance()from@/components/eased/lib/mac, so the glyphs follow the system's light or dark material. - The window ignores the pointer, never takes focus, and sits centred 140pt above the bottom of the primary display.
- Hide the window in
onExitComplete, or the empty vibrancy stays on screen after the HUD has left. Vibrancy belongs to the window, so it appears and disappears at once while the icon and bar animate.
For the pill, size the window 200×44 and place it 12pt below the menu bar at the right.
In a Tauri app
Checked with Tauri 2.12. The window, in tauri.conf.json. "focusable": false matters: without it show() takes focus, and a tray panel that hides on blur closes. windowEffects only works with transparent: true; macOSPrivateApi is a no-op since 2.12.1.
{ "app": { "windows": [ { "label": "hud", "url": "/hud", "width": 200, "height": 200, "visible": false, "transparent": true, "decorations": false, "focus": false, "focusable": false, "shadow": false, "resizable": false, "alwaysOnTop": true, "skipTaskbar": true, "visibleOnAllWorkspaces": true, "windowEffects": { "effects": ["hudWindow"], "state": "active", "radius": 18 } } ] }}A capability for the window, so its page may move, show, hide and click-through it:
{ "identifier": "hud", "platforms": ["macOS"], "windows": ["hud"], "permissions": [ "core:default", "core:window:allow-show", "core:window:allow-hide", "core:window:allow-set-position", "core:window:allow-set-ignore-cursor-events" ]}The page listens for levels, sent from Rust with app.emit_to("hud", "hud", level) (from a global shortcut handler, say) or from another window with emitTo. It places the window on the primary display each time: the hidden window's own monitor is wherever it was last shown.
import { listen } from "@tauri-apps/api/event";import { PhysicalPosition } from "@tauri-apps/api/dpi";import { getCurrentWindow, primaryMonitor } from "@tauri-apps/api/window";import { useEffect } from "react";import { useSystemAppearance } from "@/components/eased/lib/mac";import { MacHud, useMacHud, type MacHudKind } from "@/components/eased/mac-hud/mac-hud";type Level = { kind: MacHudKind; value: number; muted?: boolean };export function HudWindow() { useSystemAppearance(); const { hud, show } = useMacHud(); useEffect(() => { const win = getCurrentWindow(); win.setIgnoreCursorEvents(true); const off = listen<Level>("hud", async ({ payload }) => { const monitor = await primaryMonitor(); if (monitor) { const size = 200 * monitor.scaleFactor; const x = monitor.position.x + (monitor.size.width - size) / 2; const y = monitor.position.y + monitor.size.height - size - 140 * monitor.scaleFactor; await win.setPosition(new PhysicalPosition(Math.round(x), Math.round(y))); } await win.show(); show(payload.kind, payload.value, { muted: payload.muted }); }); return () => void off.then((unlisten) => unlisten()); }, [show]); return <MacHud {...hud} position="none" material="native" onExitComplete={() => void getCurrentWindow().hide()} />;}In an Electron app
The main process owns the window and sends it each level; showInactive() shows it without taking focus. Electron can't round vibrancy, and macOS gives a frameless window about 10pt corners, so pass radius={10} to match (the pill becomes a rounded rectangle there).
const path = require("node:path");const { app, BrowserWindow, ipcMain, screen } = require("electron");let hud;app.whenReady().then(() => { app.dock?.hide(); // A menu bar app: no Dock icon. hud = new BrowserWindow({ width: 200, height: 200, show: false, frame: false, transparent: true, backgroundColor: "#00000000", focusable: false, hasShadow: false, resizable: false, skipTaskbar: true, vibrancy: "hud", visualEffectState: "active", webPreferences: { preload: path.join(__dirname, "preload.cjs") }, }); hud.setAlwaysOnTop(true, "screen-saver"); hud.setVisibleOnAllWorkspaces(true, { visibleOnFullScreen: true }); hud.setIgnoreMouseEvents(true); hud.loadFile(path.join(__dirname, "../dist/index.html"), { query: { view: "hud" } });});// Call from a globalShortcut handler, for example.function showHud(level) { const { bounds } = screen.getPrimaryDisplay(); hud.setPosition(Math.round(bounds.x + (bounds.width - 200) / 2), bounds.y + bounds.height - 200 - 140); if (!hud.isVisible()) hud.showInactive(); hud.webContents.send("hud", level);}ipcMain.on("hud-closed", () => hud.hide());const { contextBridge, ipcRenderer } = require("electron");contextBridge.exposeInMainWorld("hud", { onLevel: (callback) => { const listener = (_event, level) => callback(level); ipcRenderer.on("hud", listener); return () => ipcRenderer.off("hud", listener); }, closed: () => ipcRenderer.send("hud-closed"),});import { useEffect } from "react";import { useSystemAppearance } from "@/components/eased/lib/mac";import { MacHud, useMacHud, type MacHudKind } from "@/components/eased/mac-hud/mac-hud";declare global { interface Window { hud: { onLevel: (callback: (level: { kind: MacHudKind; value: number; muted?: boolean }) => void) => () => void; closed: () => void }; }}export function HudWindow() { useSystemAppearance(); const { hud, show } = useMacHud(); useEffect(() => window.hud.onLevel(({ kind, value, muted }) => show(kind, value, { muted })), [show]); return <MacHud {...hud} position="none" material="native" radius={10} onExitComplete={window.hud.closed} />;}API reference
MacHud
| Prop | Type | Default | Description |
|---|---|---|---|
open | boolean | Show the HUD. Keep it mounted and toggle this so the exit animates; useMacHud drives it. | |
kind | "volume" | "brightness" | "keyboard" | Which level it shows: picks the icon and the announcement. | |
value | number | The level, 0 to 1, drawn in quarter segments (macOS's finest step, ⌥⇧ with a media key). | |
muted | boolean | false | Volume only: speaker with a slash, empty bar. |
variant | "classic" | "pill" | "classic" | The 200×200 square of macOS 15 and earlier, or the 200×44 capsule of macOS 26. |
position | "fixed" | "absolute" | "none" | "fixed" | Against the viewport, in the nearest positioned parent, or in flow for you to place (a Tauri HUD window). |
material | "css" | "native" | "css" | native drops the CSS blur and tint for a window that draws real vibrancy. |
radius | number | 18, or a capsule for pill | Corner radius, px. Match a native window's corner: 10 in Electron. |
onExitComplete | () => void | Called when the exit animation has finished (not on a variant swap): hide a native HUD window here. | |
labels | { volume?, brightness?, keyboard?, muted?: string } | "Volume", "Brightness", "Keyboard brightness", "muted" | Translate the announcement. |
className | string | "" | Appended to the HUD's classes; position is kept. |
useMacHud
useMacHud({ duration? }) returns { hud, show, hide }. show(kind, value, { muted? }) opens or updates the HUD and restarts the hold; hide() closes it at once. duration is the hold after the last show, in ms (default 1100). Spread hud onto <MacHud>.
useSystemAppearance
From @/components/eased/lib/mac. Call it once in a Tauri or Electron HUD window: it puts .dark on <html> while prefers-color-scheme is dark and follows changes, so the glyphs match the native vibrancy. It runs in an effect, so it is safe with server rendering. Eased otherwise follows .dark or data-theme="dark", so a web app keeps its own theme toggle.
MacHudIcon
The HUD's glyphs on their own, for media keys and menus: <MacHudIcon kind="volume" value={1} className="size-4" />. Takes kind, value, muted and className; colour is currentColor.
Accessibility
- A
role="status"live region, mounted before the HUD first shows, announces each change ("Volume 60%", "Volume, muted"), so screen readers hear the level without focus moving. - The visual HUD is
aria-hiddenand ignores the pointer: it never takes focus or blocks a click on what is under it. - It never holds the only copy of information: the level lives in your app, and the HUD only reflects it.
Motion
The HUD fades in on the enter curve while it scales from 0.96 on the snappy spring; the pill also drops 10px. It leaves in 150ms on the ease-in curve to 0.98 (the pill rises 6px), with no overshoot. While it is up, a new value never replays the entrance: classic segments snap in 100ms, the pill's bar slides on the smooth spring, and icon changes (a wave, the mute slash, longer sun rays) fade and un-blur on the text-swap tokens; a new kind swaps the whole glyph. Changing variant while it is up swaps one HUD for the other, without a morph. Reduced motion keeps 100ms opacity fades only and moves the bar at once.
Notes for AI
Use Mac HUD for transient feedback on a level changed by a shortcut or media key in a Mac-style app. Call show from useMacHud on every change, including repeats; render one HUD per window with {...hud} spread onto it. In Tauri or Electron, render it with material="native" and position="none" in a 200×200 vibrancy window that ignores the pointer and can't take focus, with a transparent page and useSystemAppearance(); hide the window in onExitComplete.
Do not unmount it to hide it, do not add a timer of your own (the hook owns the 1.1-second hold), and do not put controls in it: it is click-through. Do not give it a drop shadow, or a different radius except to match a native window's corner (radius={10} in Electron).