webdevpuneet.com

Goal Progress Ring — Free Editable Circular Progress Snippet

Goal Progress Ring · Dashboards · Plain HTML, CSS & JS · Live preview

CategoryDashboards

Copy & paste

Source Code

<div class="gpr-card">
  <h2 class="gpr-title">Fundraising Goal</h2>
  <div class="gpr-ring-wrap">
    <svg class="gpr-ring" viewBox="0 0 200 200" id="gprRing">
      <circle class="gpr-track" cx="100" cy="100" r="86"></circle>
      <circle class="gpr-fill" id="gprFill" cx="100" cy="100" r="86"></circle>
    </svg>
    <div class="gpr-center">
      <span class="gpr-pct" id="gprPct">0%</span>
      <span class="gpr-badge" id="gprBadge">In progress</span>
    </div>
  </div>
  <div class="gpr-amounts">
    <span class="gpr-current" id="gprCurrentLabel">$3,200</span>
    <span class="gpr-of">of</span>
    <span class="gpr-goal" id="gprGoalLabel">$5,000</span>
  </div>
  <div class="gpr-controls">
    <label class="gpr-field">
      <span>Current amount</span>
      <input type="number" id="gprCurrentInput" value="3200" min="0" step="50">
    </label>
    <label class="gpr-field">
      <span>Goal amount</span>
      <input type="number" id="gprGoalInput" value="5000" min="1" step="50">
    </label>
  </div>
</div>

Extend with AI

Take This Snippet Further With AI

The AI prompt for this one is for Patreon supporters ($3 a month, 7 days free), along with a site with no ads. Supporters enter the passcode once and it is remembered in this browser.

· Become a supporter

What's included

Features

SVG stroke-dashoffset ring
Smoothly animated fill via real circle geometry.
Live-editable inputs
Current and goal amounts update the ring as you type.
Capped at 100%
Ratio never exceeds 1, so the ring never over-fills.
Celebratory complete state
Color and badge change at 100%, no confetti needed.
Currency formatting
Thousands separators via toLocaleString.
Smooth transitions
CSS-eased stroke-dashoffset and color changes.
Center percentage readout
Large, legible progress number at a glance.
Zero dependencies
Plain SVG and DOM, no chart library.

About this UI Snippet

Goal Progress Ring — Live-Editable Circular Progress Toward a Target

Screenshot of the Goal Progress Ring snippet rendered live

The goal progress ring is the fundraising and target-tracking widget that turns "$3,200 of $5,000 raised" into an instantly readable circle — the ring's fill proportion communicates progress before anyone reads a single number. This snippet builds a fully interactive version in plain HTML, CSS, and JavaScript using an SVG stroke-dashoffset ring.

How the ring fill actually works

The ring is an SVG circle with stroke-dasharray set to its full circumference (2 * Math.PI * 86 \u2248 540.35) and stroke-dashoffset animated to control how much of that dash is visible. update() computes ratio = current / goal (capped at 1 so overachieving a goal doesn't overflow the ring), then sets offset = CIRCUMFERENCE * (1 - ratio) — at ratio 0 the full circumference is offset (empty ring), at ratio 1 the offset is 0 (fully filled ring). The CSS transition on stroke-dashoffset makes every change animate smoothly rather than snapping.

Live-editable inputs

Two number inputs — current amount and goal amount — drive everything. Typing in either fires update() on the input event, so the ring fill, the center percentage, and the "$X of $Y" labels all recompute and animate in real time as you type, not just on blur or submit.

Celebratory completion state

When current >= goal, the ring's stroke color and the status badge both switch to green and the badge text changes to "Goal reached!" — a clear but restrained celebration (color and copy change, no confetti or overlay) that keeps the widget usable as a persistent dashboard element rather than a one-time animation.

Currency formatting

formatCurrency() uses toLocaleString('en-US') so amounts render with proper thousands separators regardless of how large the input numbers get.

Customizing it

Swap dollar amounts for any unit (steps, subscribers, pages read), change the ring's color or thickness, or make the inputs read-only and drive the ring from a live API value instead. Pair it with gradient stat ring, progress circle steps, or activity rings for a fuller goals dashboard.

Step by step

How to Use

  1. 1
    Paste HTML, CSS, and JSThe ring renders filled to the default current/goal ratio.
  2. 2
    Edit the current-amount inputThe ring fill, percentage, and label update live.
  3. 3
    Edit the goal-amount inputThe ratio recalculates against the new target.
  4. 4
    Reach or exceed the goalThe ring and badge switch to a green complete state.
  5. 5
    Swap the unitChange formatCurrency() to format steps, points, or any metric.

Real-world uses

Common Use Cases

Fundraising pages
Show live progress toward a campaign target, with editable current and goal amounts that update the ring as you type.
Sales quota dashboards
Track attainment toward a revenue goal, with the ratio capped at 100% so the ring never over-fills.
Fitness targets
Pair with activity rings for daily targets, using a celebratory colour and badge change when the goal is reached.
Crowdfunding platforms
Display backer totals against a funding goal, where the ring communicates progress before anyone reads a number.
Subscriber and course milestones
Track subscriber counts toward a milestone or course completion toward a certificate, using real circle geometry for a smooth stroke-dashoffset fill.
Related: Job Queue Depth Monitor — Live Backlog Trend with Threshold Alerts
See the Job Queue Depth Monitor — Live Backlog Trend with Threshold Alerts for a related dashboards pattern worth pairing with this one.

Got questions?

Frequently Asked Questions

It uses SVG stroke-dasharray set to the circle's full circumference and stroke-dashoffset to hide part of that dash. The JS computes ratio = current / goal, then sets offset = circumference * (1 - ratio) — an offset of 0 shows the full ring, an offset equal to the circumference shows none of it. A CSS transition animates the offset change smoothly.

The ratio is capped at 1 with Math.min(current / goal, 1), so the ring never fills past a full circle even if someone enters a current amount greater than the goal. The percentage and complete state still reflect the real numbers — the badge switches to "Goal reached!" as soon as current >= goal.

No — by design it's restrained: the ring's stroke color and the status badge both switch to green with updated copy ("Goal reached!"), with no confetti or animated overlay. That keeps it usable as a persistent dashboard widget rather than a one-time celebration effect that gets stale on repeat views.

Yes — replace formatCurrency() with whatever formatting your unit needs (or none at all), and update the input labels and title text. The ring-fill math (ratio, dashoffset) is unit-agnostic and works the same for any two numbers.

Move current and goal into component state, compute ratio and offset the same way in a derived value, and bind stroke-dashoffset as an inline style or SVG attribute bound to that computed value instead of writing to the DOM directly on input events.