Questionnaire
Questions one at a time, set large. The count rolls and a hairline fills; each question turns in from the side you're heading. At the end your answers are written into one sentence, in italic, arriving word by word.
Report an issue with QuestionnaireExample
Sentence
Interview
Definition
Sentence, the end
Interview, two asked
Interview, one skipped, the last pointed at
Definition, the end
Definition, right to left
Install
Run it in a project that has the shadcn CLI set up. It brings the 0nlyType base, button, field, fraction, and radio-group along.
npx shadcn@latest add https:// thedirectors.agency/ ui/ r/questionnaire.jsonUsage
"use client"import { Questionnaire, type Answers, type Question } from "@/components/ui/questionnaire"const says: Record<string, Record<string, string>> = {make: { "An identity": "an identity", "A website": "a website", "A film": "a film" },team: { "Just me": "on your own", "Two to ten": "with a team of two to ten", "More than ten": "with a team of more than ten" },when: { "This season": "this season", "Next season": "next season", "When it’s right": "when it’s right" },}const questions: Question[] = [{ id: "make", question: "What are you making?", options: Object.keys(says.make) },{ id: "team", question: "How big is your team?", options: Object.keys(says.team) },{ id: "when", question: "When should it launch?", options: Object.keys(says.when) },{ id: "more", question: "Anything else we should know?", placeholder: "A sentence is plenty" },]const sentence = ({ make, team, when, more }: Answers) =>[`You’re making ${make ? says.make[make] : "something"}${team ? ` ${says.team[team]}` : ""}${when ? `, to launch ${says.when[when]}` : ""}.`,more ? `You added “${more.trim()}”.` : "","We’ll reply within two days.",].filter(Boolean).join(" ")// The definition's sense, and its usage in quotation marks, as a dictionary gives one.const sense = ({ team, when, more }: Answers) =>[`the one you’re making${team ? ` ${says.team[team]}` : ""}${when ? `, to launch ${says.when[when]}` : ""}.`, more ? `“${more.trim()}”` : ""].filter(Boolean).join(" ")const entry = ({ make }: Answers) => (make ? { word: says.make[make].replace(/^an? /, ""), kind: "noun" } : { word: "something", kind: "pronoun" })const partway: Answers = { make: "A website", team: "Two to ten" }const all: Answers = { ...partway, when: "Next season" }function Caption({ children }: { children: string }) {return <p className="ot-label" style={{ margin: 0 }}>{children}</p>}export default function Example() {return (<div className="grid max-w-[44rem]" style={{ gap: "var(--ot-space-9)" }}><div className="grid gap-(--ot-space-5)"><Caption>Sentence</Caption><Questionnaire questions={questions} sentence={sentence} /></div><div className="grid gap-(--ot-space-5)"><Caption>Interview</Caption><Questionnaire variant="interview" questions={questions} sentence={sentence} /></div><div className="grid gap-(--ot-space-5)"><Caption>Definition</Caption><Questionnaire variant="definition" questions={questions} sentence={sense} entry={entry} /></div></div>)}const wide = { width: "30rem", maxWidth: "100%" }Contract
- Underneath: native form.
- Anatomy:
<form class="ot-quest" data-variant="sentence | interview | definition">holding.ot-quest-head(anot-fractioncount and a filling hairline), in an interview<ol class="ot-quest-log">,.ot-quest-stepsections each with a.ot-quest-qquestion and its controls,.ot-quest-actions(Skip, Next), then.ot-quest-said(aria-live="polite") holding, in a definition,.ot-quest-entry(.ot-quest-word,.ot-quest-kind), and.ot-quest-sentence.defaultAnswersanddefaultStepresume one. - One question at a time, set large. The count rolls and the hairline fills; each question turns in from the side you're heading (
ot-turn). At the end your answers are written into one sentence, arriving word by word, in italic, because every word of it is yours. - Interview: after the printed interview's Q and A. Every question asked stays above the next, small and roman, with your answer under it in italic; Q and A hang in the margin in pencil and step in below 860px. Each answer is a button: pressing it goes back to that question (a hairline sketches under it when pointed at). A skipped one says so in pencil. At the end the whole transcript stands over the sentence.
- Definition: after "the uncreative" (poster VI and its definition). The end is a dictionary entry: the headword (yours, so in the large italic) reversed out of an ink block that draws across behind it from the inline start, the part of speech under it small in roman, then the sense in italic at the lead size, a usage in quotation marks if you wrote one.
entry(answers)gives the word and its part of speech. Forced colours: Canvas on CanvasText. - Keyboard: native form controls; focus moves to each new question (to the chosen answer when you go back to one).
Props
| Prop | Type | Default | What it does |
|---|---|---|---|
variant | "sentence" | "interview" | "definition" | "sentence" | interview: every question asked stays above the next, small, with your answer under it, like a printed interview's Q and A; press an answer to go back to it. definition: the end is a dictionary entry, your headword in italic reversed out of an ink block, then the sentence as its sense. |
questions | { id: string; question: ReactNode; options?: string[]; label?: string; placeholder?: string }[] | In order. With options, the question is a Radio Group and needs a choice to go on. Without, it's an optional free-text line. | |
sentence | (answers: Record<string, string | undefined>) => string | Writes the answers into one sentence. Only questions that were answered are in answers. | |
onComplete | (answers) => void | Called once when the last question is sent, with the answers. | |
submitLabel | string | "Send answers" | The last question's action. |
entry | (answers) => { word: string; kind: string } | Definition only: the headword and its part of speech. | |
defaultAnswers | Record<string, string | undefined> | Answers to start from, to resume a questionnaire. | |
defaultStep | number | 0 | The question to start at; the number of questions is the end. |
error | string | "Choose one to go on, or skip this question." | Said when a choice is needed to go on. |
Craft
Where its move comes from
| Component | Reference | Move |
|---|---|---|
| Questionnaire | Healthy habits → | Your answers written as one sentence |
| Interview questionnaire | The printed interview's Q and A | What's been asked stays as a transcript; press an answer to go back |
| Definition questionnaire | "the uncreative" | The end as a dictionary entry, your headword reversed out of ink |
How it moves
| Component | Articulation | What moves |
|---|---|---|
| Questionnaire | Turn | Questions turn in from where you're heading; the sentence writes in |
| Interview questionnaire | Minutes | The answered pair is written into the transcript as the next question turns in |
| Definition questionnaire | Reversal | The ink block draws across and the headword appears in it where it has reached; the sense writes in after |