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.
Set up your workspace
Fernworks · 2 of 5 done
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.
| Prop | Type | Default | Description |
|---|---|---|---|
| tasks | SpokeTask[] | 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 | 5 | How many of the tasks sit on the hub, 3 to 6; the ring divides into that many segments. |
| completed / defaultCompleted | string[] | — / [] | The finished task ids, controlled or initial. |
| onCompletedChange | (ids: string[]) => void | — | Fires from the Finish press that completed a task, with every finished id. |
| defaultAnswers | SpokeAnswers | {} | 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 | — | Fires from the Finish press, with that task's answers. |
| onFinish | (answers: SpokeAnswers) => void | — | Fires from the press that finished the last task on the hub, with every answer. |
| onLaunch | () => void | — | The Ready state's button was pressed. |
| onOpenChange | (taskId: string | null, question: number) => void | — | Fires when a spoke's flow opens, moves to another question, or closes (null). |
| ringStyle | "gapped" | "solid" | "ticks" | "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 | "var(--accent-bright)" | Filled segments, finished spokes and checks, and the primary buttons. Any CSS colour. |
| workspace | string | "Fernworks" | The workspace being set up, in the subtitle and the announcements. |
| title | string | "Set up your workspace" | The heading. |
| launchLabel | string | "Open workspace" | The Ready state's button. |
| status | "ready" | "loading" | "error" | "ready" | Whether the tasks have loaded: loading shows a pulsing ring, error a message with Try again. |
| onReload | () => void | — | Try again was pressed after the tasks failed to load. |
| disabled | boolean | false | Read-only: spokes show their state but do not open. |
| className | string | — | Classes for the outer container. |