Component
Goal Progress Tracker
Fundraising goal tracker card with an animated gradient progress bar, milestone markers, live stats, and quick-amount sponsor actions. Reaching the goal flips the card into a funded success state.
- Component
- Tailwind
- HTML
- MIT
Preview
Live component
9:41 100%
devsnips.dev/library/tailwind/components/progress/goal-progress/ fluid · no fixed width 100%
Install
Add to your project
npx devsnips add Tailwind/Components/Progress/goal-progress Tailwind/Components/Progress/goal-progress <!--
Snippet Name: Goal Progress Tracker
Description: Fundraising goal card with an animated progress bar, milestone markers, live stats, and quick-amount sponsor actions. Reaching the goal flips the card into a funded success state.
Author: DevSnips Contributors
Usage Example: Drop into a sponsorship, crowdfunding, or savings-goal page. Adjust the goal/raised/backers values in the script to match your data.
-->
<div class="w-full max-w-2xl" data-goal-progress="goal-progress">
<div class="rounded-3xl bg-white p-6 shadow-sm ring-1 ring-slate-200/60 sm:p-8">
<div class="flex items-start justify-between gap-4">
<div class="min-w-0">
<p class="text-xs font-semibold uppercase tracking-widest text-indigo-600">Community goal</p>
<h3 class="mt-2 text-xl font-bold tracking-tight text-slate-900 sm:text-2xl">Fund the next DevSnips release</h3>
<p class="mt-2 text-sm leading-relaxed text-slate-600">Every pledge goes straight into maintainer time for v2.0 — new React families, a public Figma kit, and full RTL coverage.</p>
</div>
<span class="gp-pill inline-flex shrink-0 items-center gap-1.5 rounded-full bg-indigo-50 px-3 py-1.5 text-sm font-semibold tabular-nums text-indigo-700">74%</span>
</div>
<div class="mt-6 flex flex-wrap items-baseline gap-x-2 gap-y-1">
<span class="gp-raised text-3xl font-bold tracking-tight tabular-nums text-slate-900">$18,450</span>
<span class="text-sm text-slate-500">raised of <span class="font-semibold text-slate-700 tabular-nums">$25,000</span> goal</span>
</div>
<div class="relative mt-5">
<div
class="h-2.5 w-full overflow-hidden rounded-full bg-slate-100"
role="progressbar"
aria-label="Sponsorship goal progress"
aria-valuemin="0"
aria-valuemax="25000"
aria-valuenow="18450"
aria-valuetext="$18,450 of $25,000 raised (74%)"
>
<div class="gp-fill h-full w-0 rounded-full bg-gradient-to-r from-indigo-500 to-violet-500 motion-safe:transition-[width] motion-safe:duration-500 motion-safe:ease-out" style="width: 74%"></div>
</div>
<span class="gp-dot absolute top-1/2 h-3.5 w-3.5 -translate-x-1/2 -translate-y-1/2 rounded-full bg-indigo-500 ring-2 ring-white" style="left: 25%" data-threshold="25" aria-hidden="true"></span>
<span class="gp-dot absolute top-1/2 h-3.5 w-3.5 -translate-x-1/2 -translate-y-1/2 rounded-full bg-indigo-500 ring-2 ring-white" style="left: 50%" data-threshold="50" aria-hidden="true"></span>
<span class="gp-dot absolute top-1/2 h-3.5 w-3.5 -translate-x-1/2 -translate-y-1/2 rounded-full bg-slate-300 ring-2 ring-white" style="left: 75%" data-threshold="75" aria-hidden="true"></span>
<span class="gp-dot absolute top-1/2 h-3.5 w-3.5 -translate-x-1/2 -translate-y-1/2 rounded-full bg-slate-300 ring-2 ring-white" style="left: 100%" data-threshold="100" aria-hidden="true"></span>
</div>
<div class="relative mt-3 h-4 text-[11px] font-medium tabular-nums">
<span class="gp-dot-label absolute top-0 -translate-x-1/2 text-indigo-600" style="left: 25%" data-threshold="25">$6.25k</span>
<span class="gp-dot-label absolute top-0 -translate-x-1/2 text-indigo-600" style="left: 50%" data-threshold="50">$12.5k</span>
<span class="gp-dot-label absolute top-0 -translate-x-1/2 text-slate-500" style="left: 75%" data-threshold="75">$18.75k</span>
<span class="gp-dot-label absolute right-0 top-0 text-slate-500" data-threshold="100">$25k</span>
</div>
<dl class="mt-6 grid grid-cols-3 divide-x divide-slate-100 rounded-2xl bg-slate-50 py-4 text-center ring-1 ring-slate-200/60">
<div class="px-2">
<dt class="text-xs font-medium uppercase tracking-wider text-slate-500">Backers</dt>
<dd class="gp-backers mt-1 text-lg font-bold tabular-nums text-slate-900">214</dd>
</div>
<div class="px-2">
<dt class="text-xs font-medium uppercase tracking-wider text-slate-500">Days left</dt>
<dd class="mt-1 text-lg font-bold tabular-nums text-slate-900">12</dd>
</div>
<div class="px-2">
<dt class="text-xs font-medium uppercase tracking-wider text-slate-500">Avg. pledge</dt>
<dd class="gp-avg mt-1 text-lg font-bold tabular-nums text-slate-900">$86</dd>
</div>
</dl>
<div class="mt-6 flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between">
<div class="flex items-center gap-2" role="group" aria-label="Choose a pledge amount">
<button type="button" class="gp-chip inline-flex h-11 items-center justify-center rounded-full bg-white px-5 text-sm font-semibold text-slate-700 ring-1 ring-slate-200 transition-colors duration-200 hover:text-indigo-700 hover:ring-indigo-300 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-indigo-600 focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50" data-amount="10" aria-pressed="false">$10</button>
<button type="button" class="gp-chip inline-flex h-11 items-center justify-center rounded-full bg-indigo-600 px-5 text-sm font-semibold text-white ring-1 ring-indigo-600 transition-colors duration-200 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-indigo-600 focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50" data-amount="25" aria-pressed="true">$25</button>
<button type="button" class="gp-chip inline-flex h-11 items-center justify-center rounded-full bg-white px-5 text-sm font-semibold text-slate-700 ring-1 ring-slate-200 transition-colors duration-200 hover:text-indigo-700 hover:ring-indigo-300 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-indigo-600 focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50" data-amount="50" aria-pressed="false">$50</button>
</div>
<button type="button" class="gp-sponsor inline-flex h-11 items-center justify-center gap-2 rounded-full bg-indigo-600 px-6 text-sm font-semibold text-white shadow-sm transition-all duration-200 ease-out hover:bg-indigo-700 motion-safe:hover:-translate-y-0.5 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-indigo-600 focus-visible:ring-offset-2 motion-safe:active:translate-y-0 disabled:cursor-not-allowed disabled:opacity-50">
<svg class="h-4 w-4" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24" aria-hidden="true"><path stroke-linecap="round" stroke-linejoin="round" d="M21 8.25c0-2.485-2.099-4.5-4.688-4.5-1.935 0-3.597 1.126-4.312 2.733-.715-1.607-2.377-2.733-4.313-2.733C5.1 3.75 3 5.765 3 8.25c0 7.22 9 12 9 12s9-4.78 9-12z"/></svg>
<span class="gp-sponsor-label">Sponsor $25</span>
</button>
</div>
<p class="gp-status mt-4 flex items-center gap-2 text-sm text-slate-600" aria-live="polite">
<svg class="h-4 w-4 shrink-0 text-indigo-500" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24" aria-hidden="true"><path stroke-linecap="round" stroke-linejoin="round" d="M13 7h8m0 0v8m0-8l-8 8-4-4-6 6"/></svg>
<span class="gp-status-text">$6,550 to go · 12 days left.</span>
</p>
</div>
</div>
<script>
(function () {
const root = document.currentScript.closest('[data-goal-progress="goal-progress"]');
if (!root) return;
const GOAL = 25000;
let raised = 18450;
let backers = 214;
let selected = 25;
const fill = root.querySelector('.gp-fill');
const bar = root.querySelector('[role="progressbar"]');
const pill = root.querySelector('.gp-pill');
const raisedEl = root.querySelector('.gp-raised');
const backersEl = root.querySelector('.gp-backers');
const avgEl = root.querySelector('.gp-avg');
const statusText = root.querySelector('.gp-status-text');
const sponsor = root.querySelector('.gp-sponsor');
const sponsorLabel = root.querySelector('.gp-sponsor-label');
const chips = Array.from(root.querySelectorAll('.gp-chip'));
const dots = Array.from(root.querySelectorAll('.gp-dot'));
const dotLabels = Array.from(root.querySelectorAll('.gp-dot-label'));
const CHIP_ON = ['bg-indigo-600', 'text-white', 'ring-indigo-600'];
const CHIP_OFF = ['bg-white', 'text-slate-700', 'ring-slate-200', 'hover:text-indigo-700', 'hover:ring-indigo-300'];
function money(n) { return '$' + n.toLocaleString('en-US'); }
function render() {
const pct = Math.min(100, Math.round((raised / GOAL) * 100));
const funded = raised >= GOAL;
fill.style.width = pct + '%';
fill.classList.toggle('from-indigo-500', !funded);
fill.classList.toggle('to-violet-500', !funded);
fill.classList.toggle('from-emerald-500', funded);
fill.classList.toggle('to-teal-400', funded);
bar.setAttribute('aria-valuenow', String(raised));
bar.setAttribute('aria-valuetext', money(raised) + ' of ' + money(GOAL) + ' raised (' + pct + '%)');
if (funded) {
pill.className = 'gp-pill inline-flex shrink-0 items-center gap-1.5 rounded-full bg-emerald-50 px-3 py-1.5 text-sm font-semibold text-emerald-700';
pill.innerHTML = '<svg class="h-3.5 w-3.5" fill="none" stroke="currentColor" stroke-width="2.5" viewBox="0 0 24 24" aria-hidden="true"><path stroke-linecap="round" stroke-linejoin="round" d="M5 13l4 4L19 7"/></svg>Funded';
sponsor.disabled = true;
chips.forEach(function (c) { c.disabled = true; });
sponsorLabel.textContent = 'Goal reached';
statusText.textContent = 'Goal reached — thank you to all ' + backers + ' backers!';
} else {
pill.textContent = pct + '%';
sponsorLabel.textContent = 'Sponsor ' + money(selected);
statusText.textContent = money(GOAL - raised) + ' to go · 12 days left.';
}
raisedEl.textContent = money(raised);
backersEl.textContent = backers.toLocaleString('en-US');
avgEl.textContent = money(Math.round(raised / backers));
dots.forEach(function (dot) {
const hit = pct >= Number(dot.dataset.threshold);
dot.classList.toggle('bg-indigo-500', hit && !funded);
dot.classList.toggle('bg-emerald-500', hit && funded);
dot.classList.toggle('bg-slate-300', !hit);
});
dotLabels.forEach(function (label) {
const hit = pct >= Number(label.dataset.threshold);
label.classList.toggle('text-indigo-600', hit && !funded);
label.classList.toggle('text-emerald-600', hit && funded);
label.classList.toggle('text-slate-500', !hit);
});
}
chips.forEach(function (chip) {
chip.addEventListener('click', function () {
selected = Number(chip.dataset.amount);
chips.forEach(function (c) {
const on = c === chip;
c.setAttribute('aria-pressed', String(on));
CHIP_ON.forEach(function (cls) { c.classList.toggle(cls, on); });
CHIP_OFF.forEach(function (cls) { c.classList.toggle(cls, !on); });
});
sponsorLabel.textContent = 'Sponsor ' + money(selected);
});
});
sponsor.addEventListener('click', function () {
if (raised >= GOAL) return;
raised = Math.min(GOAL, raised + selected);
backers += 1;
render();
});
render();
})();
</script> # Goal Progress
Goal progress indicator with value and target.
## Usage
```bash
npx devsnips add Tailwind/Components/Progress/goal-progress
```
Copy from `code.html`. Tailwind CSS only.
## File Structure
`code.html` · `preview.html` · `metadata.json` · `README.md` 177 lines UTF-8 · LF · Spaces: 2
Continue browsing