KQ-1699 · LAST/SLOT

Last Slot

A project grid at its plan's limit: the used projects, then one ghost slot carrying a lock. Hovering or focusing the ghost unlatches the lock on springs.snap (a shackle swings, a bolt slides out of its keeper, or a dial spins and pops) and a beat later the card turns over on springs.glide, about Y in the grid and about X when it is a wide row, to show the plan that holds more projects and its price while the header's meter glides to the new limit. Every project stays a working button with arrow-key travel by the measured columns; the ghost is one labelled button that upgrades (admins) or asks an admin (members), with Compare plans and Not now beside it.

KQ-1699 · LAST/SLOT

Projects

Starter
5 of 5 projects
  • New projectStarter holds 5 projects
    Team plan · 50 projects
    $12a seat / month
    Upgrade

5 of 5 projects · starter plan

Install

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

$ pnpm dlx shadcn@latest add @kinetiq/last-slot

Props

projects
LastSlotProject[]
default defaultLastSlotProjects
The projects in the grid: { id, name, owner, updatedAt, tasks?, done?, next?, members?, tint? }.
usage
{ used: number; limit: number }
default { used: projects.length, limit: plan.limit }
How many projects are used and allowed; at the limit the next cell carries the lock.
plan
LastSlotPlan
default defaultLastSlotPlan
The workspace's plan: { id, name, limit, price?, unit?, period?, perks? }.
offer
LastSlotPlan
default defaultLastSlotOffer
The plan that holds more projects, shown on the back of the slot with its price and two perks.
viewerRole
"admin" | "member"
default "admin"
Admins upgrade from the slot; members ask an admin.
adminName
string
default "Mara"
Who members ask.
onUpgrade
(offer: LastSlotPlan) => void
default —
The slot was pressed by an admin at the limit.
onAskAdmin
(offer: LastSlotPlan) => void
default —
The slot was pressed by a member at the limit.
onCompare
() => void
default —
Compare plans was pressed.
onDismiss
() => void
default —
Not now was pressed: the slot steps aside and a limit chip appears in the header.
dismissed / defaultDismissed
boolean
default — / false
Whether the slot has been set aside, controlled or initial.
onDismissedChange
(dismissed: boolean) => void
default —
Fires from Not now (true) or the header's chip (false).
onOpenProject
(project: LastSlotProject) => void
default —
A project card was pressed.
onCreate
() => void
default —
The slot was pressed while there is still room under the limit (it shows a plus, not a lock).
now
number
default defaultLastSlotNow
The moment edit times count from, in ms; never the wall clock.
formatPrice
(amount: number) => string
default US dollars
Formats the offer's price; cents only when there are some.
slotStyle
"dashed" | "frosted" | "grid"
default "dashed"
The empty slot: a dashed outline, frosted glass over a faint card, or blueprint grid paper.
lockStyle
"shackle" | "bolt" | "dial"
default "shackle"
A padlock whose shackle lifts and swings, a door bolt that slides out of its keeper, or a combination dial that spins and pops.
accent
"cobalt" | "signal" | "amber" | "graphite" | string
default "cobalt"
The open lock, the offer's eyebrow and checks, the grid paper and the meter's offer run.
status
"ready" | "loading" | "error"
default "ready"
Whether the projects have arrived: placeholder cards while loading, a retry on error.
onRetry
() => void
default —
Try again was pressed after the projects failed to load.
title
string
default "Projects"
The heading.
disabled
boolean
default false
Holds every press; the slot no longer turns.
className
string
default —
Classes for the surface; give it a height and the grid scrolls inside it.