KQ-716 · GOAL/CARD

Goal Card

One card for one goal: a ring whose stroke is the share saved, the goal's name and figure beside it, an add control, and a details toggle that unfolds a panel underneath. Pressing Add mints a coin at the ring's rim that drops into the hub on recoil — the two visible bounces of a coin landing in a slot — and only when it lands does the ring grow to the new share on glide and the hub's percentage roll on snap, so the ring moves because the coin arrived; reaching the goal turns the ring success and draws a tick on flick. The panel's height is measured by a ResizeObserver and animated on glide with the chevron turning on snap. The ring is a meter that carries the share in words, the panel is inert while closed, and a status line announces each add once.

KQ-716 · GOAL/CARD
Winter tyres$360.00of $600.00 · $240.00 to go
Goal
$600.00
Saved
$360.00
Remaining
$240.00
Weeks left
8 at $30.00 a week

By 30 Nov

Saved $360.00 of $600.00 · 60% · Details closed

Install

One command — the source lands in your repo. Or copy it from the Code tab.

$ pnpm dlx shadcn@latest add @kinetiq/goal-card

Props

value / defaultValue
number
default 0
Controlled or initial amount saved. An increase mints a coin and the ring waits for it to land; a decrease shrinks the ring at once with no coin.
onValueChange
(value: number, added: number) => void
default
Fires from the Add press that changed the amount, with the new total and what was added.
goal
number
default
The target; the ring closes here and Add turns aria-disabled once it is reached.
step
number
default 25
The amount one Add press contributes, clamped so the last press lands exactly on the goal.
weekly
number
default step
Planned contribution per week; the panel projects the weeks left from it.
deadline
string
default
A quiet line in the panel ("By 30 Nov").
open / defaultOpen
boolean
default false
Controlled or initial state of the details panel.
onOpenChange
(open: boolean) => void
default
Fires from the Details toggle press.
format
(value: number) => string
default Intl.NumberFormat("en-US", { style: "currency", currency: "USD" })
Formats every amount. The default pins an explicit locale so server and client agree.
label
string
default
The goal's name; printed as the heading and read as the meter's label.