Appointment Intake Match

Before you book

Three questions decide the appointment, not a drop-down

Every combination lands on a real appointment type with a named clinician and a length, so nobody arrives for twenty minutes that needed an hour.

What brings you in?

The closest one. It only sets the length and who you sit with.

Soft blue gradient backdrop
Portrait of Sara Okonjo

Sara Okonjo

Duty practitioner

Free from 09:30 today

CoveringAll four sites
AnsweringCalls and messages
Until18:00

On duty today

Whoever you end up with, the notes follow you. Nobody has to hear the same history twice.

About this block

Appointment Intake MatchFREE

Three intake questions that land on a real appointment type instead of a drop-down: the answers pick a route with its length, its facts, and the clinician you will sit with, and the image tile beside them swaps its floated availability card to match.

Booking29: Appointment Intake Match

Three intake questions that land on a real appointment type instead of a drop-down. The answers pick a route with its length, its facts, and the clinician you will sit with, and the image tile beside them swaps its floated availability card to match.

Free block

This block is free. No license or account is required: install it with the CLI and use it in unlimited projects.

Installation

Radix flavor

bash
npx shadcn add "https://ui.beste.co/r/booking29"

Base UI flavor

bash
npx shadcn add "https://ui.beste.co/r-base/booking29"

That installs the block file, the badge23 eyebrow and button21 action button it is built from, the card31 piece it floats over the image, and the questionnaire primitive.

Quick start

tsx
import { Booking29, booking29Demo } from "@/components/beste/block/booking29";

export default function Page() {
  return <Booking29 {...booking29Demo} />;
}
tsx
import { Booking29 } from "@/components/beste/block/booking29";
import { Card31 } from "@/components/beste/piece/card31";

export default function Page() {
  return (
    <Booking29
      badge={{ label: "Before you book" }}
      heading="Three questions decide the appointment, not a drop-down"
      description="Every combination lands on a real appointment type."
      shortcuts="numbers"
      questions={[
        {
          name: "reason",
          title: "What brings you in?",
          choices: [
            { value: "assessment", label: "Something new" },
            { value: "review", label: "A follow-up" },
          ],
        },
        {
          name: "history",
          title: "Have you been seen here before?",
          choices: [
            { value: "new", label: "First time" },
            { value: "returning", label: "I am already registered" },
          ],
        },
      ]}
      routes={[
        {
          // both answers have to fit, so this sits above the looser route
          match: { reason: "assessment", history: "new" },
          title: "First assessment, 60 minutes",
          summary: "A full first appointment with the assessment lead.",
          facts: [
            { label: "With", value: "Dr Amelia Frost" },
            { label: "Length", value: "60 minutes" },
          ],
          media: <Card31 name="Dr Amelia Frost" role="Assessment lead" availability="soon" />,
        },
        {
          // no match: the catch-all, and it has to be last
          title: "Advice call, 15 minutes",
          summary: "A short call with whoever is on duty.",
          media: <Card31 name="Sara Okonjo" role="Duty practitioner" availability="free" />,
        },
      ]}
      image={{ src: "https://images.unsplash.com/photo-1750918619871-dc74c9a57394", alt: "Backdrop" }}
      media={<Card31 name="Sara Okonjo" role="Duty practitioner" availability="free" />}
      waitingBody="Whoever you end up with, the notes follow you."
      button={{ label: "Choose a time", href: "https://beste.co" }}
    />
  );
}

Props

PropTypeDefaultDescription
badgeBadgeEyebrow above the rule, rendered with Badge23.
headingstringSection heading.
descriptionstringLead paragraph, set opposite the heading.
questionsIntakeQuestion[][]The intake questions, in the order they are asked.
shortcuts"letters" | "numbers"Prints a shortcut on every choice of the active question.
routesRoute[][]Checked in order; the first one whose match fits wins.
imageTileImageBackdrop behind the floated piece.
mediaReactNodePiece shown on the tile before anything is answered.
waitingBodystringLine under the tile, kept in both states.
buttonActionLinkButton21 beside the matched appointment.
labelsBooking29Labels{}Overrides for the navigation buttons and the two panel headings.
classNamestringMerged onto the section element.
ts
type Badge = { label: string };
type ActionLink = { label: string; href: string };
type TileImage = { src: string; alt: string };
type Fact = { label: string; value: string };

type IntakeQuestion = {
  name: string;
  title: string;
  description?: string;
  choices?: IntakeChoice[];
};

type IntakeChoice = { value: string; label: string; description?: string };

type Route = {
  match?: Record<string, string>;
  title: string;
  summary: string;
  facts?: Fact[];
  media?: ReactNode;
};

type Booking29Labels = {
  previous?: string;
  next?: string;
  submit?: string;
  restart?: string;
  matchTitle?: string;
  waitingTitle?: string;
};

Behavior notes

  • A route's match maps a question's name to the value it needs, and every pair has to fit. Narrow routes belong above loose ones, because the list is read top to bottom and the first fit wins.
  • The last route stands in when nothing matches, so a submit never dead-ends. Give it no match and it reads as a deliberate catch-all.
  • Pieces are passed as media, pre-configured in the demo data rather than assembled by the block. Give every embedded Card31 its full props: a bare one renders the empty defaults and the tile looks broken.
  • The questions sit directly on the page rather than inside a bordered panel. One question is on screen at a time, so a box around it is mostly empty; the bordered choices and the hairline fact rows carry the structure instead.
  • The tile keeps its height across both states, so matching an appointment swaps the card without the section jumping under the cursor.
  • Every question is required, so Submit stays out of reach until all three have an answer.
  • Change my answers clears the match and remounts the questions with nothing filled in, and the tile falls back to the standing media.

More Booking blocks

View all Booking
PRO

booking27

Choose Your Clinician

A clinician picker whose hairline list of practitioners, each showing their next opening, swaps an image tile floating a live availability card alongside their focus, background, and sites.

PRO

booking28

Waiting List Capture

A no-availability state that answers with three hairline figures and a numbered explanation of how offers are made, beside a sticky square tile floating a live panel of the waiting list itself and an urgent fallback.

FREE

booking4

Service Selector

Service selection grid with category filter tabs, service cards showing icon, name, description, duration, price, and selected state. Ideal for spa, salon, clinic, and fitness booking flows.

PRO

booking24

Service And Slot Picker

An interactive appointment picker: a hairline panel pairing a selectable service list with a scrollable day strip and a time slot grid that marks unavailable times, beside a full-height provider photo with a gradient name card, closing on a live summary bar and confirm action.

FREE

booking5

Provider Picker

Staff/provider selection cards with avatar, name, specialty, star rating, review count, availability badge, experience years, and selected state. Grid layout with "Any Provider" option.

PRO

booking33

Session Picker

A booking chooser: a serif heading that settles in word by word and an intro, then a ruled list of session types on the left that swaps, with a soft blur, the muted detail card on the right showing a serif title, a description, a fact row and a sliding-marker pill.