Most interfaces ask badly. A support page offers a search box and hopes you know the vocabulary. A product page offers twelve filters and hopes you know your own requirements. A contact form asks fourteen questions at once, including the four that do not apply to you, and the answer arrives three days later from somebody who read none of them.
The alternative is older than the web: ask one question, listen to the answer, then ask the next one. That is what the shadcn questionnaire primitive makes practical in a React app, and it is what these sixteen blocks are built on. Every one of them asks a short sequence, and every one ends with something concrete: a written answer, a matched product, a booked appointment type, a specification with a price on it.
All sixteen are free.
Free block
This block is free. No license or account is required: install it with the CLI and use it in unlimited projects.
What installs
Each block is a single React component, and the CLI resolves its whole tree in one command:
npx shadcn add "https://ui.beste.co/r/faq92"That lands three layers:
- The section at
components/beste/block/faq92.tsx. Plain TSX you own. - The
questionnaireprimitive atcomponents/ui/questionnaire.tsx, plus whatever else the block composes. - Nothing else. No form engine, no flow builder, no runtime dependency on us.
If you are on Base UI rather than Radix, swap /r/ for /r-base/ and you get the Base UI build of the same component.
The primitive underneath
components/ui/questionnaire.tsx is a thin styled layer over @shadcn/react, the headless package shadcn publishes for it. It is worth understanding before you edit a block, because it does more than hide and show fields.
The parts compose like any other shadcn component:
<Questionnaire defaultItem="area" shortcuts="numbers" onSubmit={handleSubmit}>
<QuestionnaireProgress />
<QuestionnaireItem name="area" required>
<QuestionnaireTitle>What is this about?</QuestionnaireTitle>
<QuestionnaireChoices>
<QuestionnaireChoice value="billing">Billing and invoices</QuestionnaireChoice>
<QuestionnaireChoice value="access">Getting into the account</QuestionnaireChoice>
</QuestionnaireChoices>
<QuestionnaireError />
</QuestionnaireItem>
<QuestionnaireActions>
<QuestionnairePrevious />
<QuestionnaireNext />
<QuestionnaireSubmit />
</QuestionnaireActions>
</Questionnaire>Four things it gives you for free are the reason these blocks are short:
- It is a real form. Every question is a
fieldsetwith a name, and the answers are ordinary form values. The blocks read them withnew FormData(event.currentTarget)on submit, so there is no state library and nothing to serialize. - One item is on screen, the rest stay mounted. Inactive questions are hidden and inert rather than unmounted, so answers given earlier are still submitted with the rest, and the browser still owns validation.
- Navigation understands the question.
requiredblocks Next until the question is answered,Skiponly appears when a question is optional, and each item reports a status ofunanswered,answered, orskippedthroughonStatusChange, which is how a block can draw a rail of progress beside the flow. - Keyboard shortcuts are scoped. Set
shortcuts="letters"orshortcuts="numbers"and each choice on the active question gets a key, rebound as you move. Pressing2means something different one question later, which is exactly what you want.
Let the primitive own navigation
Every block here keeps the flow uncontrolled and only watches it, usually with
onItemChange, to draw a progress bar or swap a photograph. Fighting the
primitive for control of the current step is the fastest way to end up with a
flow that disagrees with its own buttons.
Four things a questionnaire can end with
The sixteen blocks are not sixteen variations on a wizard. They are grouped by what happens when the answers land, because that is the decision you are actually making when you pick one.
One: route to a written answer
The cheapest support win is not a better search box. It is admitting that most tickets are a dozen questions wearing different words, and routing to the one that fits.
faq92 matches on a small object per answer, so a narrow route sits above a general one for the same subject and wins when both fit. The last entry is the catch-all, which means a submit never dead-ends. The same shape drives booking29, where the routes are appointment types with a length, a clinician, and a set of facts, and careers56, where they are open roles with a published pay band.
fitness40 is the honest version of the pattern. Its result answers in two columns of equal weight: who the programme is for beside who should not take it. A recommendation that never says no is a sales page, and readers can tell.
Two: score the answers and recommend
When the answer is a ranking rather than a lookup, the blocks score instead of match.
The scoring is deliberately blunt: one point per trait, highest total wins, ties go to the first item. What makes it feel considered is the reasons list, which is not written by hand. It collects the reason attached to every answer the winning product actually satisfies, so the same bag explains itself differently depending on how you reached it.
podcast54 scores the same way but returns an order rather than an item: three episodes to listen to in sequence, with the total runtime summed from the episodes themselves. workflow55 weights each answer with a number, turns the total into a percentage of the ceiling, and places you on a scale between shipping straight away and full change control. education102 grades against an answer key and shows its working, including the explanation for anything you got wrong.
Three: build something while you answer
The most useful thing a questionnaire can do is show its work as it goes, so nothing arrives as a surprise at the end.
product12 never hides the price. The specification sits under the questions the whole time, filling one row per answered step, with the total recomputed from a base plus each option. event101 does the same for a day at a conference: a fixed rail of time slots that fill in as you choose, with the hours you left open still on the page, because an empty afternoon is a decision worth seeing.
travel42 inverts the filter sidebar. Instead of a result count quietly changing somewhere, you watch the grid narrow one answer at a time, which tells you what each question is costing you while you can still change it. It is possible to answer your way down to nothing, so the empty state is written copy rather than a blank panel.
gallery25 takes the idea somewhere else again: its questions are photographs rather than sentences. You pick the frames you would hang, and the result is a distribution rather than a winner, because a split eye should read as split.
Four: collect, then confirm
The last group is the one closest to a traditional form, and the one where a questionnaire earns the most.
A brief asked one question at a time is answered more honestly than the same brief as a wall of fields, and the closing ledger matters as much as the questions: it shows the sender exactly what is about to be sent. onboarding43 applies that to workspace setup, cta92 to a newsletter that asks which subjects you want before it asks for your address, and saas108 to a sales enquiry that branches, so nobody is asked about compliance for a blog.
Every value on the screen is a prop
The rule behind the whole catalog holds here: if you can read it, you can pass it.
Each block renders its full demo with zero props, so <Faq92 /> looks exactly like the embed above. Every prop is optional and falls back to the demo value, so you replace demo copy with real content one field at a time:
import { Faq92 } from "@/components/beste/block/faq92";
export default function HelpPage() {
return (
<Faq92
heading="Two questions instead of a search box"
questions={[
{
name: "area",
title: "What is this about?",
choices: [
{ value: "billing", label: "Billing and invoices" },
{ value: "access", label: "Getting into the account" },
],
},
{
name: "state",
title: "Where did it stop working?",
choices: [
{ value: "before", label: "Before I could start" },
{ value: "after", label: "It finished, but the result is wrong" },
],
},
]}
answers={[
{
match: { area: "billing", state: "after" },
title: "The invoice does not match what you expected",
description: "Mid-cycle plan changes are prorated to the day.",
link: { label: "Read how proration is calculated", href: "/docs/billing" },
},
{
title: "Something else",
description: "Start from the index and narrow down from there.",
link: { label: "Open the help index", href: "/docs" },
},
]}
/>
);
}Every block exports its demo object too (faq92Demo here), which doubles as documentation: the exact shape your data needs, in a file you can open.
Wiring the result to your backend is the part we deliberately left to you. Each block resolves its outcome in one handler, so you add a fetch there and nothing else changes.
The details we had to get right
- Server rendering is safe. No
Date.now(),Math.random(), ortoLocaleString()in render paths. Money and durations are formatted by hand, so a server and a client never disagree about a comma. - The flows are real forms. Answers are form values, validation is the browser's, and a submit is a submit. If JavaScript never arrives, the questions are still there.
- Keyboards work. Choices are labelled inputs, the whole tile is the target, and shortcuts are bound only while their question is active.
- Theming is token-based. Everything reads from your CSS variables, so the blocks adopt your brand and your dark mode on arrival. If you are still shaping those tokens, the anatomy of a shadcn theme covers the variables underneath.
- Three design languages, one mechanic. The sixteen are split across our Sirius, Auralis, and Polaris languages, so you can pick the one that matches the site you already have rather than restyling a stranger.
The full set
Every block links to a live, full-screen preview. They are gathered under one hub at /blocks/tag/questionnaire.
| Block | What it does | Ends with |
|---|---|---|
| faq92 | Support router, two questions | One written answer |
| booking29 | Appointment intake | A matched appointment type |
| careers56 | Role fit finder | One open role and its pay band |
| fitness40 | Programme match | Who it is for, beside who it is not |
| ecommerce46 | Product finder | One product and why it won |
| podcast54 | Listening path builder | Three episodes in order |
| workflow55 | Process sizer | A position on a weighted scale |
| education102 | Graded lesson check | A score and a per-question review |
| product12 | Made-to-order configurator | A live specification and total |
| event101 | Conference day builder | A filled schedule, gaps included |
| travel42 | Narrowing shortlist | Whatever survived the answers |
| gallery25 | Taste profile from photographs | A distribution across styles |
| agency24 | Studio brief | A ledger of the answers |
| onboarding43 | Workspace setup wizard | A review of what will be created |
| cta92 | Tailored subscription | A confirmation that reads back |
| saas108 | Branching sales enquiry | A scoped enquiry, sent |
Start with the question you already ask badly
The quickest way in is not to build a new flow. It is to find the place where your product already asks too much at once: the contact form nobody finishes, the filter sidebar nobody touches, the help page that sends everyone to the same inbox. Install the block that fits, replace the demo copy with the questions you already ask on a call, and wire the outcome to whatever handles it today.
The questions are the product decision. The blocks are so that writing them down takes an afternoon rather than a sprint.