2022Design LeadWahoo Fitness

Building Responsive Metric Displays

How I tackled an interesting challenge of building a scalable, responsive metric display for the Wahoo workout recorder—ensuring real-time metrics remain clear and legible across all screen sizes and layouts.

.
Lessons learned from designing a responsive workout view.
.
The workout view is fully responsive, data fields are always glanceable.
.

Overview

Wahoo’s ELEMNT bike computer UI has always been designed around a specific resolution and aspect ratio. What set it apart from competitors was its dynamic approach to organizing information, known as Perfect Zoom—a feature that allows users to zoom in and out to adjust metric sizes on the fly. This was essential for on-the-ride usability, ensuring key workout data remained easily glanceable without distracting from the ride.

Perfect Zoom in action on the Wahoo ELEMNT bike computer. Video courtesy of Cycling Weekly.

As we rebuilt the Wahoo app from the ground up, we wanted to bring the same level of usability into the in-app workout recorder—extending the ELEMNT workout experience to phones, tablets, and even TVs. However, this introduced a new challenge: creating a fully responsive workout view for the first time.


The Challenge: Responsive Font Strategy for Real-Time Metrics

Designing a responsive workout view brought unique challenges in optimizing font size for real-time metrics. Unlike our bike computers, which have fixed aspect ratios and resolutions, these views had to adapt to various screen sizes and orientations.

The design needed to support multiple metric types while ensuring readability. Users could zoom in and out, dynamically adjusting the number of visible metric cards. This required a robust font size strategy to handle unpredictable data and layout shifts. The non-negotiable requirements were:

Why Container Queries Didn’t Work

My initial approach was to use container queries to determine the optimal font size. However, this method proved complex and required extensive calculations. It ultimately wasn’t scalable due to the following challenges:

See the interactive example below. Adjust the text and layout using the font size slider and columns dropdown to find the best fit within the card. When the text is properly sized, the setup will indicate “acceptable.” The six different metric types highlight the challenge of varying digit lengths.

Heart Rate
90
Time
1:00:12
Lap
0:12
Power
300
Distance (km)
0.4
Calories
456

Finding a font size that adapts to varying digit counts and layout adjustments wasn’t straightforward. This complexity led me to rethink the problem and explore alternative solutions.


Rethinking the Approach

Instead of manually adjusting font sizes based on digit count and layout changes, I treated the metric card like a scalable viewport, similar to how videos adjust to different screens without distortion.

Just like how object-fit: contain ensures an image fits its frame without cropping, this approach allowed the text to scale proportionally within its allocated space—no manual font sizing needed. This meant:

The Solution: SVG & ForeignObject

To solve the scaling challenge, I turned to SVG as the mechanism for rendering text graphics. Unlike traditional CSS-based scaling techniques, SVG provides a resolution-independent way to display content, making it ideal for this use case.

However, instead of using the standard <text> element—which is limited in terms of formatting and layout control—I leveraged the <foreignObject> tag.

By utilizing <foreignObject/> within an SVG container, I was able to embed HTML content (or React components) directly into the SVG. This allowed me to render text while ensuring it scaled proportionally to fit its allocated space.

<svg viewBox="0 0 100 20">
  <foreignObject width="100%" height="100%">
    // This can be any HTML element
    <span>120</span>
  </foreignObject>
</svg>

Check out the example below to see the solution in action. The metric values scale proportionally within the card, adapting to digit count and layout changes. Adjust the height, width, and number of cards (up to 9) to observe how the text resizes dynamically. You can also enable visual outlines to view the allocated space and SVG container.

Full implementation in React

For a real-world implementation, I used this technique inside a React component. It dynamically measures the text dimensions and updates the SVG container accordingly. This ensures the text scales proportionally within the card, adapting to varying digit counts.

React component example
function MetricCardWithSVG({ label, value }) {
  const valueRef = useRef < HTMLSpanElement > null;
  const [height, setHeight] = useState(0);
  const [width, setWidth] = useState(0);

  // When value changes, update the dimensions. This takes
  // care of the dynamic digit counts.
  useEffect(() => {
    setHeight(valueRef.current?.getBoundingClientRect().height || 0);
    setWidth(valueRef.current?.getBoundingClientRect().width || 0);
  }, [value]);

  return (
    <div className="card">
      <span className="label">{label}</span>
      <div className="allocated-space">
        {/* 1. This is the hidden value element used to measure 
        the dimensions */}
        <span
          ref={valueRef}
          className="label"
          style={{ visibility: "hidden", position: "absolute" }}
          aria-hidden="true"
        >
          {value}
        </span>

        {/* 2. SVG container with viewbox dimensions based on the 
        measured value above */}

        <svg viewBox={`0 0 ${width} ${height}`}>
          {/* 3. ForeignObject with 100% width and height to auto resize 
          itself to the svg container */}
          <foreignObject width="100%" height="100%">
            <span className={valueTextCn}>{value}</span>
          </foreignObject>
        </svg>
      </div>
    </div>
  );
}

View Codesandbox


Key Learnings

This project was a valuable learning exprience for me. Through experimentation and iteration, I discovered a novel approach to handling dynamic text scaling within a fixed container.

I am still on the lookout for better solutions and would love to hear your ideas if you have any. Please holla at me on Twitter! 🤘🏼

More from Che Wei Lee