You Might Also Like
Mobile Fitness Screen — Free HTML CSS JS UI Snippet
Mobile Fitness Screen · Mobile · Plain HTML, CSS & JS · Live preview
What's included
Features
About this UI Snippet
Mobile Fitness Screen — Activity Rings UI

A fitness home screen leads with the activity rings — concentric progress arcs for move, exercise, and stand — then backs them with daily stats and a workout list. This snippet builds a complete one inside a CSS phone frame on a dark theme: three SVG rings that animate from empty to their day's progress on load, a three-up stat grid, and a tappable workout history — in HTML, CSS, and vanilla JavaScript with no charting dependency.
The three concentric rings
Each ring is an SVG <circle> with a grey track behind it and a colored arc on top. Progress is drawn with the classic stroke-dasharray / stroke-dashoffset technique: the dash length is set to the circle's full circumference (2πr), and the offset is reduced toward zero to reveal more of the arc. Each ring is rotated -90 degrees around its center so the arc starts at twelve o'clock, exactly like the rings you know from wearables.
Animating from empty on load
The rings start fully offset (empty), then a double requestAnimationFrame sets the target offset on the next frame so the browser registers a transition from empty to filled rather than jumping straight to the final state. The stroke-dashoffset transition uses a smooth easing curve over just over a second, so all three rings sweep up together when the screen appears — the satisfying "closing your rings" moment.
Per-ring circumference math
Because the three rings have different radii, each needs its own circumference for the dash math. The code stores each radius, computes 2πr per ring, and drives the fill from a percentage, so a ring at 84% lands at the right visual arc regardless of its size. This is the correct way to build nested progress rings — sharing one circumference would misalign the inner arcs.
Stats and workouts on a dark canvas
Below the rings, three stat tiles show steps, calories, and distance, and a workout list pairs a tinted activity icon with duration and calorie details. The dark theme uses near-black cards on a deep navy background so the vivid ring colors and stat numbers pop, which is the standard look for fitness apps.
Accessibility and performance
The rings are decorative SVG, so their meaning must live in text for non-visual users: the legend beside them already spells out each metric with its current and goal value, which is what a screen reader should read rather than the arcs themselves. When you adapt this, mark the SVG aria-hidden and expose the same numbers through the legend, or give each ring an aria-label describing its progress. The workout rows are real buttons, so the history is keyboard-operable. Performance is a highlight of the dash-offset technique: each ring animates a single stroke-dashoffset value that the browser can composite smoothly, with no per-frame JavaScript and no canvas redraws, so all three rings sweep up together without touching the main thread after the initial set. Because the fill is driven by CSS transition rather than a timer, it respects the user's reduced-motion preference if you gate it behind a media query. Feeding real percentages is a single style write per ring, so live updates stay cheap.
Reusing it
Feed each ring a real percentage from your health data, update the stats and workouts from your API, and wire the workout rows to detail screens. Lift the rings out of the phone frame for a responsive web dashboard, or keep them framed beside a set of activity rings to present a full tracker.
Build with AI
Build, Understand, Optimize, and Extend It With AI
You do not have to work out the dash-offset math in your head to trust it. Paste this snippet's HTML, CSS, and JS into an AI coding assistant like Claude and ask it to explain exactly why each ring needs its own circumference computed from 2 times pi times its own radius rather than sharing one value across all three, or why the code sets the dashoffset inside a double nested requestAnimationFrame instead of a single one. The same assistant can help optimize it — ask whether the stroke-dashoffset transition could stutter on lower-end devices with many rings, or how you would gate the fill-on-load animation behind a prefers-reduced-motion check. It is just as useful for extending the effect: have it add a fourth ring for a custom metric, make the rings tappable to reveal a detail sheet, or animate a ring update live when new activity data arrives instead of only on page load. Treat the code less like a finished artifact and more like a starting point for a conversation.
Prompt to recreate it
Copy this into your AI assistant of choice to build the effect from scratch, or as a jumping-off point for your own variant:
Build a mobile fitness/activity screen with concentric SVG progress rings in plain HTML, CSS, and JavaScript inside a phone-frame container — no charting library, no canvas.
Requirements:
- Three nested SVG circles as background tracks plus three matching colored circles as progress rings, each ring rotated -90 degrees around its own center so its arc begins at the twelve o'clock position.
- Each ring's fill must be computed independently: for a given radius, its own circumference is 2 times pi times that radius, its stroke-dasharray must equal that circumference, and its stroke-dashoffset must equal circumference times (1 minus the ring's percent complete), so rings of different sizes at the same percentage look visually correct relative to each other.
- On page load, every ring must start fully offset (visually empty) and then animate to its target offset using a CSS transition on stroke-dashoffset, triggered from JavaScript by setting the starting offset, then inside two nested requestAnimationFrame calls setting the final target offset, so the browser reliably registers and plays the transition instead of skipping straight to the end state.
- A legend beside the rings listing each metric's label, current value, and goal value, plus a row of stat tiles below (for example steps, calories, distance) and a tappable list of workout entries, each giving brief press feedback by flashing its background color for under 200ms on click.
- All colors must be shared correctly between each ring and its corresponding legend dot so the mapping between ring and metric is unambiguous.Want to tighten it up first? Run this prompt through the AI Prompt Studio to score it across 8 quality dimensions, catch anti-patterns, and tune the wording for Claude, ChatGPT, or Gemini before you paste it in.
Step by step
How to Use
- 1Paste HTML, CSS, and JSA dark fitness screen renders and the three activity rings animate up from empty.
- 2Watch the rings fillMove, exercise, and stand arcs sweep to their day's progress together on load.
- 3Read the legendEach ring's current and goal values sit beside a matching color dot.
- 4Scan the stat gridSteps, calories, and distance tiles summarize the day.
- 5Tap a workoutRows in the history list flash to acknowledge the tap.
- 6Bind your health dataSet each ring's percentage and update stats from your API.
Real-world uses
Common Use Cases
Got questions?
Frequently Asked Questions
Each ring is an SVG circle with a grey track behind it. The colored arc uses stroke-dasharray set to the circle's circumference (2 times pi times the radius) and stroke-dashoffset to hide part of it. Reducing the offset toward zero reveals more of the arc, and a -90 degree rotation makes it start at the top.
The rings start fully offset, then a double requestAnimationFrame sets the target offset on a later frame. That two-frame delay lets the browser register a transition between the empty and filled states rather than painting the final value immediately, so the CSS transition on stroke-dashoffset animates the sweep on load.
The three rings have different radii, so their circumferences differ. The dash math must use each ring's own 2-pi-r value; sharing one circumference would make the inner rings land at the wrong arc length for the same percentage. The code stores each radius and computes the length per ring.
Each ring is driven by a pct value between 0 and 1. The final offset is circumference times (1 minus pct), so 0.84 fills 84 percent of the arc. Replace the hard-coded percentages with values derived from your health data and the rings will fill to match.
Hold each ring's percentage in state and bind stroke-dashoffset to a computed offset. Trigger the fill-on-mount in a useEffect (React), onMounted (Vue), or ngAfterViewInit (Angular), setting the target after the first paint. Render stats and workouts from your data. Tailwind expresses the dark cards and layout with utilities while the SVG stays as-is.