New
Goal & Streak
A gamified progress tracker combining a weekly goal progress bar with streak counters. Composes Progress Bar, Avatar, and Badge. Reference: JobFlo's MacGoalStreakCard.
Demo
Goal-met state
Anatomy
| Column | Contents |
|---|---|
| Progress (2fr) | Label + count / goal + animated progress bar + goal-met checkmark |
| Current streak (1fr) | 🔥 or 💤 emoji + large rounded-digit number + “day streak” unit |
| Best streak (1fr) | 🏆 emoji + number + “best streak” unit |
States
| State | Behaviour |
|---|---|
| In progress | Bar fills with --accent; streak shows 🔥 if > 0, else 💤 |
| Goal met | Bar transitions to --status-success; card surface gets --accent / 0.06 tint (sfBrandLitSurface); checkmark appears |
| Zero progress | Bar empty; streak shows 💤; best streak preserved |
Animation
The progress bar animates on mount with a spring curve: spring(response: 0.5, dampingFraction: 0.75). This gives the bar a subtle overshoot that communicates momentum without being distracting.
Implementation
GoalStreakCard.tsx
tsx
"use client";
import { useState, useEffect } from "react";
interface GoalStreakProps {
label: string;
current: number;
goal: number;
currentStreak: number;
bestStreak: number;
unit?: string;
}
export function GoalStreakCard({
label,
current,
goal,
currentStreak,
bestStreak,
unit = "applications",
}: GoalStreakProps) {
const pct = Math.min(current / goal, 1);
const isGoalMet = current >= goal;
// Spring-animate the bar width on mount
const [width, setWidth] = useState(0);
useEffect(() => { requestAnimationFrame(() => setWidth(pct)); }, [pct]);
const fillColor = isGoalMet
? "rgb(var(--status-success))"
: "rgb(var(--accent))";
return (
<div
style={{
display: "grid",
gridTemplateColumns: "2fr 1fr 1fr",
borderRadius: 14,
overflow: "hidden",
background: "rgb(var(--surface))",
border: "1px solid rgb(var(--border))",
boxShadow: "var(--shadow-card)",
// sfBrandLitSurface when goal met
...(isGoalMet && {
background: "rgb(var(--accent) / 0.06)",
border: "1px solid rgb(var(--accent) / 0.25)",
}),
}}
>
{/* Progress column */}
<div style={{ padding: "16px 20px", borderRight: "1px solid rgb(var(--border))" }}>
<p style={{ fontSize: 11, fontWeight: 600, letterSpacing: "0.06em", textTransform: "uppercase", color: "rgb(var(--text-tertiary))", marginBottom: 8 }}>
{label}
</p>
<p style={{ fontSize: 13, color: "rgb(var(--text-secondary))", marginBottom: 10 }}>
<span style={{ fontWeight: 700, fontSize: 20, color: "rgb(var(--text-primary))", fontVariantNumeric: "tabular-nums" }}>{current}</span>
{" / "}{goal} {unit}
</p>
{/* Progress bar */}
<div style={{ height: 8, borderRadius: 99, background: "rgb(var(--progress-track))", overflow: "hidden" }}>
<div style={{
height: "100%",
width: `${width * 100}%`,
borderRadius: 99,
background: fillColor,
transition: "width 0.5s cubic-bezier(0.34, 1.56, 0.64, 1)",
}} />
</div>
{isGoalMet && (
<div style={{ display: "flex", alignItems: "center", gap: 6, marginTop: 10, color: "rgb(var(--status-success))", fontSize: 12, fontWeight: 600 }}>
<span style={{ width: 18, height: 18, borderRadius: "50%", background: "rgb(var(--status-success))", display: "flex", alignItems: "center", justifyContent: "center" }}>
<svg width="10" height="8" viewBox="0 0 10 8" fill="none"><path d="M1 4l3 3 5-6" stroke="#fff" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round"/></svg>
</span>
Goal met!
</div>
)}
</div>
{/* Current streak */}
<div style={{ padding: 16, textAlign: "center", borderRight: "1px solid rgb(var(--border))" }}>
<div style={{ fontSize: 22, marginBottom: 4 }}>{currentStreak > 0 ? "🔥" : "💤"}</div>
<div style={{ fontSize: 24, fontWeight: 800, fontVariantNumeric: "tabular-nums", letterSpacing: "-0.02em" }}>{currentStreak}</div>
<div style={{ fontSize: 11, color: "rgb(var(--text-tertiary))", marginTop: 2 }}>day streak</div>
</div>
{/* Best streak */}
<div style={{ padding: 16, textAlign: "center" }}>
<div style={{ fontSize: 22, marginBottom: 4 }}>🏆</div>
<div style={{ fontSize: 24, fontWeight: 800, fontVariantNumeric: "tabular-nums", letterSpacing: "-0.02em" }}>{bestStreak}</div>
<div style={{ fontSize: 11, color: "rgb(var(--text-tertiary))", marginTop: 2 }}>best streak</div>
</div>
</div>
);
}