Feedback feedbackprogressringcircularmeter

Progress Ring Counter

Circular SVG progress gauge with smooth stroke dashoffset transition, customizable track colors, and centered value readout.

Preview

75% COMPLETED

Usage

Copy the full block below to use this component with its imports.

astro
---
import { ProgressRingCounter } from 'astro-component-kit';
---

<ProgressRingCounter value={78} max={100} size={110} strokeWidth={8} color="var(--c-primary)" label="Score" />
--- import { ProgressRingCounter } from 'astro-component-kit'; --- <ProgressRingCounter value={78} max={100} size={110} strokeWidth={8} color="var(--c-primary)" label="Score" />

Manual Installation

If you are not using the npm package, create a new file src/components/lib/ProgressRingCounter.astro and paste the following code:

astro
---
/**
 * ProgressRingCounter — Circular SVG progress ring with centered countdown or metric value.
 * 
 * @param {number} value - Current counter or progress value. Default is 75.
 * @param {number} max - Maximum value threshold. Default is 100.
 * @param {number} size - Pixel diameter of the ring. Default is 96.
 * @param {number} strokeWidth - Thickness of the ring stroke. Default is 6.
 * @param {string} color - Primary gauge fill color. Default is '#d4a017'.
 * @param {string} label - Subtitle label beneath figure. Default is 'COMPLETED'.
 */
interface Props {
  value?: number;
  max?: number;
  size?: number;
  strokeWidth?: number;
  color?: string;
  label?: string;
}

const {
  value = 75,
  max = 100,
  size = 96,
  strokeWidth = 6,
  color = '#d4a017',
  label = 'COMPLETED',
} = Astro.props;

const radius = (size - strokeWidth) / 2;
const circumference = 2 * Math.PI * radius;
const percent = Math.min(100, Math.max(0, (value / max) * 100));
const strokeDashoffset = circumference - (percent / 100) * circumference;
---

<div class="ring-counter" style={`--ring-size: ${size}px; --ring-color: ${color};`}>
  <svg width={size} height={size} class="ring-counter__svg">
    <circle
      class="ring-counter__bg"
      cx={size / 2}
      cy={size / 2}
      r={radius}
      stroke-width={strokeWidth}
    />
    <circle
      class="ring-counter__meter"
      cx={size / 2}
      cy={size / 2}
      r={radius}
      stroke-width={strokeWidth}
      stroke-dasharray={circumference}
      stroke-dashoffset={strokeDashoffset}
    />
  </svg>

  <div class="ring-counter__center">
    <span class="val">{Math.round(percent)}%</span>
    {label && <span class="lbl">{label}</span>}
  </div>
</div>

<style>
  .ring-counter {
    position: relative;
    width: var(--ring-size);
    height: var(--ring-size);
    display: inline-flex;
    align-items: center;
    justify-content: center;
    font-family: var(--font-sans, system-ui, sans-serif);
    user-select: none;
  }

  .ring-counter__svg {
    transform: rotate(-90deg);
    width: 100%;
    height: 100%;
  }

  .ring-counter__bg {
    fill: none;
    stroke: rgba(255, 255, 255, 0.08);
  }

  .ring-counter__meter {
    fill: none;
    stroke: var(--ring-color);
    stroke-linecap: round;
    transition: stroke-dashoffset 0.8s cubic-bezier(0.16, 1, 0.3, 1);
    filter: drop-shadow(0 0 6px rgba(212, 160, 23, 0.35));
  }

  .ring-counter__center {
    position: absolute;
    inset: 0;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    text-align: center;
  }

  .ring-counter__center .val {
    font-size: 1.25rem;
    font-weight: 800;
    color: var(--c-text-1, #ffffff);
    line-height: 1;
  }

  .ring-counter__center .lbl {
    font-size: 0.55rem;
    font-weight: 700;
    letter-spacing: 0.08em;
    color: var(--c-text-3, #737373);
    margin-top: 4px;
    text-transform: uppercase;
  }
</style>
--- /** * ProgressRingCounter — Circular SVG progress ring with centered countdown or metric value. * * @param {number} value - Current counter or progress value. Default is 75. * @param {number} max - Maximum value threshold. Default is 100. * @param {number} size - Pixel diameter of the ring. Default is 96. * @param {number} strokeWidth - Thickness of the ring stroke. Default is 6. * @param {string} color - Primary gauge fill color. Default is '#d4a017'. * @param {string} label - Subtitle label beneath figure. Default is 'COMPLETED'. */ interface Props { value?: number; max?: number; size?: number; strokeWidth?: number; color?: string; label?: string; } const { value = 75, max = 100, size = 96, strokeWidth = 6, color = '#d4a017', label = 'COMPLETED', } = Astro.props; const radius = (size - strokeWidth) / 2; const circumference = 2 * Math.PI * radius; const percent = Math.min(100, Math.max(0, (value / max) * 100)); const strokeDashoffset = circumference - (percent / 100) * circumference; --- <div class="ring-counter" style={`--ring-size: ${size}px; --ring-color: ${color};`}> <svg width={size} height={size} class="ring-counter__svg"> <circle class="ring-counter__bg" cx={size / 2} cy={size / 2} r={radius} stroke-width={strokeWidth} /> <circle class="ring-counter__meter" cx={size / 2} cy={size / 2} r={radius} stroke-width={strokeWidth} stroke-dasharray={circumference} stroke-dashoffset={strokeDashoffset} /> </svg> <div class="ring-counter__center"> <span class="val">{Math.round(percent)}%</span> {label && <span class="lbl">{label}</span>} </div> </div> <style> .ring-counter { position: relative; width: var(--ring-size); height: var(--ring-size); display: inline-flex; align-items: center; justify-content: center; font-family: var(--font-sans, system-ui, sans-serif); user-select: none; } .ring-counter__svg { transform: rotate(-90deg); width: 100%; height: 100%; } .ring-counter__bg { fill: none; stroke: rgba(255, 255, 255, 0.08); } .ring-counter__meter { fill: none; stroke: var(--ring-color); stroke-linecap: round; transition: stroke-dashoffset 0.8s cubic-bezier(0.16, 1, 0.3, 1); filter: drop-shadow(0 0 6px rgba(212, 160, 23, 0.35)); } .ring-counter__center { position: absolute; inset: 0; display: flex; flex-direction: column; align-items: center; justify-content: center; text-align: center; } .ring-counter__center .val { font-size: 1.25rem; font-weight: 800; color: var(--c-text-1, #ffffff); line-height: 1; } .ring-counter__center .lbl { font-size: 0.55rem; font-weight: 700; letter-spacing: 0.08em; color: var(--c-text-3, #737373); margin-top: 4px; text-transform: uppercase; } </style>

Quick Info

Category
Feedback
Filename
ProgressRingCounter.astro
Dependencies
None — pure Astro + CSS
Tags
feedbackprogressringcircularmeter