KQ-1338 · SUBSCRIPTION/MANAGER

Subscription Manager

A subscription whose schedule is a row of dots you can push around: every upcoming delivery is a dot keyed by its place in the schedule, so skipping hollows it and slides the Next flag to the box that will ship on snap, a new cadence keeps the next box where it is and sends every later dot to its new day on glide, and a pause draws a hatched band across the days it covers. The strip of days drags 1:1, rubber-bands and coasts on glide with the throw's velocity (or turns month pages by direction), the cadence is chips or a detent slider that reshapes the dots live, and the next box swaps by dragging a coffee off the shelf onto a slot, both products flying from where they were on glide. Dots are toggle buttons with roving arrows, the cadence a radiogroup or slider, and a held product is placed with the arrows and Enter.

KQ-1338 · SUBSCRIPTION/MANAGER

Subscription

Fernworks Coffee · The Roaster's Box

Active · every 2 weeks

Next box

Thu, Oct 8

Charged Oct 6 to Waylight Pay ···· 4417

$34.45incl. $3.95 delivery

Deliver every

Every 2 weeks

The next box stays on Oct 8; the rest move.

Upcoming

Drag the strip or use Left and Right to move between deliveries.

Press a box to skip it; press again to bring it back.

In the next box

$30.50
  1. Kiln Road EspressoWhole bean · 250 g
    $12.50
  2. Harrow Valley FilterGround · 250 g
    $11.50
  3. Oat ShortbreadTin of 12
    $6.50

Swap in

Drag onto a slot, or press to pick up

next box thu oct 8 · every 2 weeks · $34.45

Install

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

$ pnpm dlx shadcn@latest add @kinetiq/subscription-manager

Props

calendar
"strip" | "month"
default "strip"
How the schedule is drawn: a strip of days that drags, coasts and rubber-bands, or month pages (two side by side from 40rem) whose dots hop between days on a cadence change.
frequency
"chips" | "slider"
default "chips"
How the cadence is chosen: a radiogroup of the common cadences with a sliding pill, or a detent slider from 1 to maxCadence weeks that reshapes the dots live as the thumb crosses each detent.
swap
"shelf" | "menu"
default "shelf"
How the next box is changed: drag (or pick up and place) a product from a shelf of alternatives onto a slot, or a Swap button on each slot that opens a listbox with the price difference.
plan
SubscriptionPlan
default defaultSubscriptionPlan
The merchant, plan name, the anchor delivery date (ms), per-box shipping, the payment it is charged to and how many days before a delivery it is charged.
products
SubscriptionProduct[]
default defaultSubscriptionProducts
Everything that can go in a box: name, detail, price, a packaging drawing (beans, tin, tea) and a tint.
value / defaultValue
SubscriptionValue
default defaultSubscriptionValue
The cadence in weeks, the skipped dates (YYYY-MM-DD), the resume date of a pause or null, and the product ids in the next box. Controlled or uncontrolled.
onValueChange
(value: SubscriptionValue) => void
default —
Fires from the press, drag or key that changed the plan. A cadence change also drops skips that fall off the new schedule.
onSkip
(date: string, skipped: boolean) => void
default —
A delivery was skipped or brought back.
onSwap
(slot: number, fromId: string, toId: string) => void
default —
A slot's product was swapped.
onPause
(until: string | null) => void
default —
Paused until a date, or resumed with null.
onCadenceChange
(weeks: number) => void
default —
The cadence changed: once per chip, once per detent the slider crosses.
cadences
number[]
default [1, 2, 4]
The chips' cadences, in weeks.
maxCadence
number
default 8
The slider's longest cadence, in weeks.
horizon
number
default 10
Weeks of schedule shown on the strip and across the month pages.
freeShippingOver
number
default 40
Boxes at or over this amount ship free; the total rolls as a swap crosses it.
now
number | Date
default defaultSubscriptionNow
Today on the calendar and the base for the pause choices, never the wall clock.
formatPrice
(amount: number) => string
default US dollars
Turns amounts into text.
title
string
default "Subscription"
The heading, and the region's name unless label is given.
status
"ready" | "loading" | "error"
default "ready"
Whether the plan has arrived: loading shows skeleton panels, error a message with Try again.
onRetry
() => void
default —
Try again was pressed after the plan failed to load.
label
string
default —
The region's accessible name, when the visible title is not enough.
sound
boolean
default false
Clicks for skips, detents, pick-ups and pauses; a swish when a swap lands or a cadence chip sends the dots travelling.
disabled
boolean
default false
Read only: the schedule and box still show, nothing changes.
className
string
default —
Classes for the root. It is at most 560px tall and scrolls inside itself.