DEV Community

Great Work
Great Work

Posted on Originally published at multimeterapp.com

Build your first face

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>
Enter fullscreen mode Exit fullscreen mode

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); }
Enter fullscreen mode Exit fullscreen mode

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;
Enter fullscreen mode Exit fullscreen mode

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
}
Enter fullscreen mode Exit fullscreen mode

"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 }))
Enter fullscreen mode Exit fullscreen mode

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.

Next

Top comments (0)