Skip to main content
ux design

Ring Stats

A weekend project: a calm macOS menu-bar glance at today's ring scores and battery, private by default and honest about freshness.

Product DesignmacOS AppHealth DataAccessibility
Ring Stats popover with Readiness, Sleep and Activity score rings, Heart rate, Stress, Resilience and ring battery

Services

Product Design, Interaction Design, Brand Identity, Accessibility, macOS Development

Duration

Sep 2026

Tools used

FigmaSwift / SwiftUI

Links

GitHub

Overview

Ring Stats is an independent, open-source macOS menu-bar app for Oura Ring owners. One click shows today's Readiness, Sleep and Activity scores, heart rate, stress and ring battery. One more click and it is gone.

The design challenge: answer “how am I doing today?” in five seconds without becoming another dashboard, and never show a number as current when it is not.

Context

A Five-Second Question

The ring collects the data and the phone app explains it. What was missing was the in-between moment: sitting at a Mac, wondering whether today is a push day or a rest day, without picking up a phone and falling into a feed of charts.

A solo project end to end: product definition, interaction and visual design, identity, SwiftUI and AppKit development, security review and release engineering. The scope was set by one rule from the start: smaller than a dashboard. No history, no coaching, no navigation and no health database on disk.

The Product

One Strip, One Blue

The popover is a single horizontal strip of metric tiles, a slim battery row and a compact options menu. No cards, tabs or headings compete with the numbers. Readiness, Sleep and Activity appear as score rings; Heart rate and Stress are not scores, so they get an icon and a value instead of a fabricated progress ring.

Every normal score shares one signal blue. Giving each metric its own color would have turned a calm status strip into a generic fitness dashboard, so color is saved for the moments that need attention.

Ring Stats theme: warm canvas, signal-blue score rings, Resilience enabled and battery at 76%

The default theme: warm canvas, dark ink and one signal blue for every score.

Landscape theme: the same stats in white over a dusky Nordic hillside photograph, with Resilience enabled

Landscape theme, same moment: icons over an original Nordic photograph. Same labels, same states, same order.

Holographic theme: dark score rings and labels over a marbled pastel iridescent background, with Resilience enabled and battery at 53%

Holographic theme: the marbled finish from the app icon, with dark ink kept for contrast.

Ring Stats menu with icons: Appearance, Connection, About & Credits, Diagnostics, Refresh Now, Reauthorize Permissions and Quit Ring Stats

One native menu for everything that is not a stat: settings, refresh, reauthorize and quit.

Design System

The Quiet Signal Strip

A small system with strict rules. Every tile has the same four zones in the same order: an 84-point visual, a value on a shared baseline, a title and one line of detail. Themes and metrics change the content of those zones, never the layout, so a Resilience level and a heart-rate number still line up.

Seven color tokens, three type roles and a four-step spacing scale cover the whole app. Themes change presentation only: a test fails the build if the two themes ever show different lines or labels for the same state.

In Figma, every view carries paired behavior and accessibility notes, so the spec says how each control works and how it is announced, not only how it looks.

Figma Application Views board with popover states, the About window, connection step 3 of 3 and the context menu, surrounded by blue Behavior and pink Accessibility notes linked to specific controls

The annotated spec: behavior in blue, accessibility in pink, each pinned to the control it describes.

Decisions

Three Calls That Shaped It

A status strip over a dashboard. Trends, history and recommendations already live in the phone app, where they are done well. Repeating them on the Mac would have made a slower copy of something better. The strip answers one question and gets out of the way.

Your own credentials over a shared secret. Each person registers their own developer application and enters its keys once. It adds a setup step, but no shared secret ships inside the app and no server sits between the ring and the Mac.

Hidden means not fetched. Turning a metric off in Appearance also removes it from the request and from the permissions asked for. Customization is a privacy control, not just a layout preference.

Ring Stats data flowScores travel from the ring through the phone app and the Oura API to Ring Stats on the Mac, with credentials kept in the Keychain.
Loading diagram
Diagram text alternative
  1. The ring syncs to the phone app.
  2. The phone app uploads to the Oura API V2, which returns enabled stats only.
  3. Ring Stats on the Mac, inside the App Sandbox, fetches those stats and keeps values in memory only.
  4. Ring Stats stores credentials and tokens in the macOS Keychain.

Honest Freshness

Old Numbers Say They Are Old

A wellness number shown as current when it is hours old is worse than no number at all. So a stat that fails to refresh keeps its last known value, dims and reads “Not updated” in text, not just in color. The status line says when data last arrived, confirms a successful refresh for three seconds and then fades away so the strip stays quiet.

Missing permissions get their own state and a direct fix instead of an endless partial refresh. Every one of these states is rendered by the test suite in both themes and at every supported width, so none of them is designed once and forgotten.

Partial refresh: Sleep dimmed and marked Not updated, battery marked Not updated, status reads Some stats not updated

A partial refresh: Sleep and battery keep their last values and say so.

Narrow popover while refreshing, with a small spinner in the status line

Refreshing, without hiding what is already known.

Failed refresh: status reads Update failed one hour ago, with a timeout explanation above the battery row

A failed refresh names the cause and the age.

Missing permission: an Enable Stress Access button below the metric strip

A missing permission comes with its own fix.

Onboarding

Three Steps to Connected Securely

Bring-your-own credentials is the least friendly part of the product, so it got the most design attention. Setup is three steps with a step indicator: create the application and learn why it is needed, add the callback address with a one-click copy, then enter the keys with a plain note that they stay in this Mac's Keychain.

The flow ends on a confirmation rather than a dialog dismissal: “Connected securely”, one sentence on where data lives and a single button that opens the stats.

Connection step 1 of 3: create an application in the developer portal

Step 1: why a developer application is needed.

Connection step 2 of 3: add the loopback callback URL, with a Copy button

Step 2: one address, one copy button.

Connection step 3 of 3: Client ID and Client Secret fields with a Keychain storage note

Step 3: keys go straight to the Keychain.

Connected securely confirmation with a shield icon and a Show My Stats button

The finish line says where the data lives.

Accessibility

Evidence, not Intent

Accessibility is tracked as a matrix where an item only counts as verified when a test or a dated manual check proves it. Small text meets 4.5:1 in both themes, including over the brightest pixel of the landscape photograph. Two failing tokens were caught this way and fixed before release.

macOS does not apply Dynamic Type to SwiftUI on the Mac, so Ring Stats has its own text size setting that scales type and tile geometry together. The stats row is fully keyboard operable: Tab to reach it, arrow keys to move and Option-arrow to reorder, with each move announced to VoiceOver.

Narrow popover at Extra Large text size, with larger rings and labels and no clipped text

Extra Large at the narrowest width: bigger type, bigger tiles, nothing clipped.

Brand Identity

Respectful of the Ring It Reads

Ring Stats is an independent project, not affiliated with or endorsed by Oura, and the identity makes that clear at a glance: a warm-neutral palette, native system type and icons, and an original mark. No borrowed logo, product silhouette, proprietary fonts or copied app chrome.

The mark is a ring seen at an angle, with a gap where the band turns away. The same mark works as the small icon in the menu bar and as the app icon.

The Ring Stats mark on its own: a split ring seen at an angle

The mark.

Ring Stats app icon: a white split ring on a black rounded square

The app icon.

Ring Stats app icon variant: a dark split ring on a pastel holographic gradient

A holographic edition.

Delivery

Signed, Notarized, Sandboxed

Ring Stats ships as a Developer ID-signed, Apple-notarized universal disk image with a published checksum. It runs in the App Sandbox with four entitlements, has no third-party runtime dependencies and no analytics. Releases carry a software bill of materials and a signed provenance attestation.

146 automated tests cover the app, including a state gallery that renders every popover state in both themes and at three widths. Version 1.2 added Shortcuts actions for any stat or the battery, so the numbers also work in automations. Next: a moderated usability study with five to eight ring owners, manual VoiceOver and keyboard verification, and battery-first features such as an optional low-battery notification.

Interested in working together?

Let's discuss how design systems, AI and thoughtful UX can elevate your product.