This guide first appeared on multimeterapp.com. Multimeter is a Mac menu bar app that turns any number into a live meter.
In about fifteen minutes you’ll make a face that shows a colored dot and the value, turns yellow and red at your alert levels, and works with every source from the battery to a number you send. If you know a little HTML, CSS and JavaScript, you have everything you need.
1. Create the face
In Multimeter’s Settings, click Create Face… (Option-Command-N) and name it Dot. Multimeter makes a folder of starter files and shows it in Finder. Open the folder in your editor. To see a change, pick the face in Add Meter… or Customize This Meter….
Want a head start? The source of all 35 built-in faces, plus two example faces, is on GitHub at multimeterapp/multimeter-faces (MIT license). Copy any of them into your face folder and change what you like.
2. The markup
Replace index.html with a dot and a value:
index.html
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <link rel="stylesheet" href="styles.css"> </head> <body> <span id="dot"></span> <span id="value">--</span> <script src="meter.js"></script> </body> </html> 3. The styles
A meter is 22 points tall and transparent. Use the system font and tabular numbers so digits don’t jiggle:
styles.css
* { margin: 0; padding: 0; box-sizing: border-box; } body { height: 22px; overflow: hidden; background: transparent; display: flex; align-items: center; justify-content: center; gap: 4px; font: 600 11px/1 -apple-system, BlinkMacSystemFont, sans-serif; font-variant-numeric: tabular-nums; color: white; --dot: #30d158; } body.light-mode { color: black; } body.warning { --dot: #ffd60a; } body.critical { --dot: #ff453a; } #dot { width: 8px; height: 8px; border-radius: 50%; background: var(--dot); } Multimeter adds light-mode or dark-mode to body to match the menu bar, which follows the wallpaper, not only the system setting.
4. The script
Multimeter puts the data in window.meterData and fires meterUpdate whenever it changes. Draw once, draw on every update, then say you’re ready:
meter.js
const dot = document.getElementById('dot'); const value = document.getElementById('value'); function render(d) { value.textContent = d.available === false ? '--' : d.formatted; document.body.classList.toggle('light-mode', d.colorScheme === 'light'); document.body.classList.toggle('warning', d.state === 'warning'); document.body.classList.toggle('critical', d.state === 'critical'); // The color picked in Customize, while nothing needs attention if (d.tint && d.state === 'normal') document.body.style.setProperty('--dot', d.tint); else document.body.style.removeProperty('--dot'); } window.addEventListener('meterUpdate', (e) => render(e.detail)); if (window.meterData) render(window.meterData); window.meterReady = true; formatted is already localized and includes the unit (“23%”, “1.2 MB/s”, “4:12”), so the same face works for every source. The full list of fields is on Creating Meters.
5. The manifest
manifest.json
{ "name": "Dot", "description": "A colored dot and the value.", "version": "1.0.0", "author": "Your Name", "apiVersion": 2, "sources": ["any"], "defaultSource": "cpu", "width": 48, "minWidth": 36, "maxWidth": 80, "supportsLightMode": true, "supportsDarkMode": true } "sources": ["any"] offers the face for every source. A battery-only face would say ["battery"].
6. Test it
- Choose Add Meter… and try your face on CPU, Network, a Timer and a Sent Value. Long values like 1.2 MB/s should still fit at
minWidth. - Set a low warning level in Customize This Meter… to see the yellow and red states.
- Switch your wallpaper between light and dark to check both menu bars.
- Errors from your JavaScript show up in Console.app.
Faster loop: open your index.html in Safari and feed it data from the Web Inspector console:
Safari console
window.meterData = { formatted: '87%', level: 0.87, state: 'warning', colorScheme: 'dark', available: true } window.dispatchEvent(new CustomEvent('meterUpdate', { detail: window.meterData })) 7. Keep it light
Something we measured while building the built-in faces: a CSS transition on a value that updates often costs far more than it looks. A CPU Needle Gauge cost 7.5% CPU with a 0.9 second transition and 0.6% without. If your face animates a number that changes every few seconds, step it with a 30 frames-per-second timer instead, and skip animation when reduceMotion is true.
8. Share it
Compress the folder in Finder, rename the .zip to .multimeter, and submit it to the gallery. Anyone can add it with Import… (Command-O) or by double-clicking the file.
Check it worked
Your Dot face shows a green dot and the value in the menu bar, turns yellow and red at the alert levels, and reads correctly in light and dark menu bars.