Progress Ring Activity Rings
The rings share one grid cell and nest by size alone. Each ring tints its own track by setting --sv-progress-ring-track-clr to a transparent color-mix() of its fill color, and light-dark() adapts the palette to the color scheme. Assigning value animates each fill, with --sv-progress-ring-dur and --sv-progress-ring-ease shaping the motion.
- Move / 500 kcal
- Exercise / 30 min
- Stand / 12 h
Code
The following example assumes that you have imported the component and set up the theme.
<div class="activity-card">25 collapsed lines
<div class="activity-rings"> <sv-progress-ring class="move" aria-label="Move goal" value="85"></sv-progress-ring> <sv-progress-ring class="exercise" aria-label="Exercise goal" value="60"></sv-progress-ring> <sv-progress-ring class="stand" aria-label="Stand goal" value="40"></sv-progress-ring> </div>
<ul class="activity-legend"> <li class="move"> Move <output>425</output> / 500 kcal </li> <li class="exercise"> Exercise <output>18</output> / 30 min </li> <li class="stand"> Stand <output>5</output> / 12 h </li> </ul>
<button type="button" class="randomize-button">Randomize</button></div>
<script type="application/x-typescript" module>13 collapsed lines
const card = document.querySelector(".activity-card")!;
const goals = { move: 500, exercise: 30, stand: 12 };
card.querySelector(".randomize-button")?.addEventListener("click", () => { for (const [name, goal] of Object.entries(goals)) { const ring = card.querySelector<ProgressRing>(`sv-progress-ring.${name}`)!; const output = card.querySelector<HTMLOutputElement>(`.activity-legend .${name} output`)!;
ring.value = 10 + Math.random() * 90; output.value = String(Math.round((ring.value / 100) * goal)); } });</script>
<style>102 collapsed lines
.activity-card { --move-color: light-dark(#e11d48, #fa2e55); --exercise-color: light-dark(#65a30d, #a3e635); --stand-color: light-dark(#0891b2, #22d3ee);
display: grid; gap: 1.5rem; justify-items: center; max-inline-size: 22rem; padding: clamp(1rem, 4vw, 2rem); margin: auto; background-color: var(--sv-gray-7); border: 1px solid var(--sv-gray-5); border-radius: var(--sv-bdr-rad-lg); }
.activity-rings { display: grid; place-items: center;
> * { grid-area: 1 / 1; }
sv-progress-ring { --sv-progress-ring-track-thickness: 12px; --sv-progress-ring-fill-thickness: 12px; --sv-progress-ring-track-clr: color-mix(in srgb, var(--ring-color) 25%, transparent); --sv-progress-ring-fill-clr: var(--ring-color); --sv-progress-ring-dur: 1.2s; --sv-progress-ring-ease: cubic-bezier(0.22, 1, 0.36, 1); }
.move { --ring-color: var(--move-color);
inline-size: 9rem; }
.exercise { --ring-color: var(--exercise-color);
inline-size: 7rem; }
.stand { --ring-color: var(--stand-color);
inline-size: 5rem; } }
.activity-legend { display: flex; flex-wrap: wrap; gap: 0.5rem 1.25rem; padding: 0; margin: 0; font-size: 0.8rem; list-style: none;
li { display: inline-flex; gap: 0.4rem; align-items: center; }
li::before { inline-size: 0.6rem; block-size: 0.6rem; content: ""; background-color: var(--ring-color); border-radius: 50%; }
.move { --ring-color: var(--move-color); }
.exercise { --ring-color: var(--exercise-color); }
.stand { --ring-color: var(--stand-color); } }
.randomize-button { padding: 0.5em 1.5em; font: inherit; color: var(--sv-accent-content); cursor: pointer; background-color: var(--sv-accent); border: none; border-radius: var(--sv-bdr-rad-md);
&:focus-visible { outline: 2px solid var(--sv-accent); outline-offset: 2px; } }</style>