KQ-1670 · THREAD/FIELDS
Thread Fields
A single-page form where a follow-up question appears directly beneath the answer that asked for it. Choosing that answer opens the follow-ups to their measured height on springs.glide while a thin thread draws from the chosen option down the gutter and hooks into each new question's label, its reveal riding the same value as the height so it reaches each label as the label arrives; changing the answer pulls the thread back up on the exit ease as those questions fold away, keeping their answers, and the new answer's thread drops from its own option. Choice questions are radiogroups whose arrows move and choose, checkbox groups move by arrow and toggle with Space, and nested follow-ups indent with threads of their own.
Book a studio
Coldbrook Studios · Fernworks Row
workshop · 2 follow-ups · 2 answered
Install
One command — the source lands in your repo. Or copy it from the Code tab.
$ pnpm dlx shadcn@latest add @kinetiq/thread-fields
Props
- questions
- ThreadQuestion[]
- default defaultThreadQuestions
- Every question, top-level and follow-up: { id, label, kind: "choice" | "multi" | "text" | "textarea" | "number" | "email", options?, required?, placeholder?, hint? }.
- conditions
- ThreadCondition[]
- default defaultThreadConditions
- { when, is, show }: the answer (or any of the answers) of `when` that reveals the questions in `show`. A question named in any `show` is a follow-up.
- values / defaultValues
- Record<string, string | string[]>
- default — / {}
- The answers, hidden follow-ups' included (they are kept), controlled or initial.
- onValuesChange
- (values) => void
- default —
- Fires from the edit that changed the answers, with all of them.
- onChange
- (id, value, visible) => void
- default —
- Fires from each edit with the question, its value and only the answers now visible.
- onReveal
- (ids: string[], by: string) => void
- default —
- Follow-ups appeared, with the question whose answer revealed them.
- onSubmit
- (answers) => void | Promise<void>
- default —
- Submit with every visible required question answered; receives only visible answers. A promise shows progress; a rejection keeps everything and offers Try again.
- threadStyle
- "elbow" | "curve" | "stitch"
- default "elbow"
- How the thread is drawn: square turns, a single swoop to each follow-up, or dashed tacking stitches.
- indent
- number
- default 24
- How far follow-ups sit in, in px: the gutter the thread runs in. Capped at 16 below 34rem.
- accent
- string
- default "var(--accent-bright)"
- The chosen option, the thread and its beads, any CSS colour.
- title
- string
- default "Book a studio"
- The form's heading and accessible name.
- subtitle
- string
- default "Coldbrook Studios · Fernworks Row"
- A quiet line under the heading.
- submitLabel
- string
- default "Request booking"
- The submit button's label.
- height
- number
- default 520
- Panel height in px; the form scrolls inside it.
- status
- "ready" | "loading"
- default "ready"
- Loading draws placeholder questions.
- disabled
- boolean
- default false
- Nothing can be answered or sent.
- className
- string
- default —
- Classes for the outer container.
| Prop | Type | Default | Description |
|---|---|---|---|
| questions | ThreadQuestion[] | defaultThreadQuestions | Every question, top-level and follow-up: { id, label, kind: "choice" | "multi" | "text" | "textarea" | "number" | "email", options?, required?, placeholder?, hint? }. |
| conditions | ThreadCondition[] | defaultThreadConditions | { when, is, show }: the answer (or any of the answers) of `when` that reveals the questions in `show`. A question named in any `show` is a follow-up. |
| values / defaultValues | Record<string, string | string[]> | — / {} | The answers, hidden follow-ups' included (they are kept), controlled or initial. |
| onValuesChange | (values) => void | — | Fires from the edit that changed the answers, with all of them. |
| onChange | (id, value, visible) => void | — | Fires from each edit with the question, its value and only the answers now visible. |
| onReveal | (ids: string[], by: string) => void | — | Follow-ups appeared, with the question whose answer revealed them. |
| onSubmit | (answers) => void | Promise<void> | — | Submit with every visible required question answered; receives only visible answers. A promise shows progress; a rejection keeps everything and offers Try again. |
| threadStyle | "elbow" | "curve" | "stitch" | "elbow" | How the thread is drawn: square turns, a single swoop to each follow-up, or dashed tacking stitches. |
| indent | number | 24 | How far follow-ups sit in, in px: the gutter the thread runs in. Capped at 16 below 34rem. |
| accent | string | "var(--accent-bright)" | The chosen option, the thread and its beads, any CSS colour. |
| title | string | "Book a studio" | The form's heading and accessible name. |
| subtitle | string | "Coldbrook Studios · Fernworks Row" | A quiet line under the heading. |
| submitLabel | string | "Request booking" | The submit button's label. |
| height | number | 520 | Panel height in px; the form scrolls inside it. |
| status | "ready" | "loading" | "ready" | Loading draws placeholder questions. |
| disabled | boolean | false | Nothing can be answered or sent. |
| className | string | — | Classes for the outer container. |