KQ-2112 · MILESTONE/RINGS

Milestone Rings

A project sidebar where every row wraps its icon in a ring whose arc is progress toward the project's next milestone, with a tally of tiny flags for the milestones already reached. When the data says one is reached, the arc closes on springs.glide, flashes once with a halo on a tween, a flag lifts off the ring and flies to the tally (x on glide, y on springs.snap, landing on springs.recoil), and the ring resets on snap — the arc empties forward and the next milestone's share draws in as the meta line changes. Rows are a listbox where Up and Down walk, Enter chooses, Alt+M opens the focused milestone and Alt with an arrow moves the row, a grip drags rows 1:1 while the others glide aside, and the sidebar folds to a rail where the rings stay wrapped round their icons.

KQ-2112 · MILESTONE/RINGS

Up and Down move between projects, Enter opens one, Alt+M opens its next milestone, Alt with Up or Down moves the project.

Gauge Mini · Tooling sign-off 82% · 2 flags

Install

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

$ pnpm dlx shadcn@latest add @kinetiq/milestone-rings

Props

projects
MilestoneProject[]
default defaultMilestoneProjects
The projects in their starting order: { id, name, icon?, milestones: { id, name, due (ISO day), progress 0–1, done? }[] }. The first milestone not reached is the next one.
current / defaultCurrent
string
default the first project
The chosen project, controlled or initial.
onNavigate
(projectId: string) => void
default —
A project was chosen with a click, Enter or Space.
onOpenMilestone
(projectId: string, milestone: Milestone | null) => void
default —
Alt+M on a row, or a milestone in the side pane, asked to open a milestone; null when the project has none upcoming.
onReorder
(ids: string[]) => void
default —
The rows were reordered by a grip drag or Alt with an arrow; the new id order.
folded / defaultFolded
boolean
default false
The sidebar as a 60px rail of rings, controlled or initial.
onFoldedChange
(folded: boolean) => void
default —
Fires from the button that folded or opened the sidebar.
now
string | number | Date
default "2026-10-14"
Today, for due and overdue; read in UTC so server and browser agree.
ringSize
number
default 26
The ring's diameter in px; icons and row heights scale with it.
overdueStyle
"tint" | "hatch" | "label"
default "tint"
How a late milestone shows: the arc and date in danger, a hatched danger track, or a calm ring with a "4d late" chip.
accent
"cobalt" | "plum" | "mint" | "ink" | string
default "cobalt"
Arcs, the current row and flags. A name or any CSS colour.
flagLimit
number
default 4
Flags drawn in a row's tally before it reads "+n".
status
"ready" | "loading" | "error"
default "ready"
Whether the projects have arrived: still skeleton rows while loading, a message and Retry on error.
onRetry
() => void
default —
Retry was pressed after an error.
workspace
string
default "Gaugeworks"
The workspace in the header.
title
string
default "Product lines"
The list's heading and accessible name.
frameHeight
number
default 520
The frame's height in px; the list scrolls inside it.
disabled
boolean
default false
Read only: nothing can be chosen, opened or moved.
className
string
default —
Classes for the frame.