KQ-1727 · SPOKE/WIZARD

Spoke Wizard

A non-linear setup drawn as a hub and spokes: a ring divided into one segment per task, each task's node at the end of its spoke. A spoke opens its short flow, grown out of the spoke's direction on springs.glide as the hub recedes; finishing returns to the hub, where that spoke's segment fills with a glide sweep from its start, the node's check draws on springs.flick, the spoke line turns solid and the count rolls on springs.snap. When every spoke is done the gaps close into one ring and the centre lands a check on springs.recoil: Ready. The spokes are a list of buttons in ring order, so Tab walks them clockwise (arrow keys too), each flow is a form where Enter moves on and Escape returns to the hub, and under 34rem the hub becomes a list with a small ring.

KQ-1727 · SPOKE/WIZARD

Set up your workspace

Fernworks · 2 of 5 done

2 of 5 done

Billing next

2 of 5 done · Billing next

Install

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

$ pnpm dlx shadcn@latest add @kinetiq/spoke-wizard

Props

tasks
SpokeTask[]
default defaultSpokeTasks
The tasks around the hub, clockwise from twelve o'clock: { id, title, icon, blurb, questions }. Questions are text (required may carry its own message), choice or toggle, asked one at a time.
spokeCount
number
default 5
How many of the tasks sit on the hub, 3 to 6; the ring divides into that many segments.
completed / defaultCompleted
string[]
default — / []
The finished task ids, controlled or initial.
onCompletedChange
(ids: string[]) => void
default —
Fires from the Finish press that completed a task, with every finished id.
defaultAnswers
SpokeAnswers
default {}
Answers already given, by task and question; a finished spoke shows them as its summary and a reopened flow starts from them.
onTaskComplete
(taskId: string, answers: Record<string, string | boolean>) => void
default —
Fires from the Finish press, with that task's answers.
onFinish
(answers: SpokeAnswers) => void
default —
Fires from the press that finished the last task on the hub, with every answer.
onLaunch
() => void
default —
The Ready state's button was pressed.
onOpenChange
(taskId: string | null, question: number) => void
default —
Fires when a spoke's flow opens, moves to another question, or closes (null).
ringStyle
"gapped" | "solid" | "ticks"
default "gapped"
The ring's drawing: segments with gaps and round caps, one continuous track, or a ring of fine ticks that light up.
accent
string
default "var(--accent-bright)"
Filled segments, finished spokes and checks, and the primary buttons. Any CSS colour.
workspace
string
default "Fernworks"
The workspace being set up, in the subtitle and the announcements.
title
string
default "Set up your workspace"
The heading.
launchLabel
string
default "Open workspace"
The Ready state's button.
status
"ready" | "loading" | "error"
default "ready"
Whether the tasks have loaded: loading shows a pulsing ring, error a message with Try again.
onReload
() => void
default —
Try again was pressed after the tasks failed to load.
disabled
boolean
default false
Read-only: spokes show their state but do not open.
className
string
default —
Classes for the outer container.