Skip to content

shadcn.io is an independent project built on shadcn/ui

Free

Calendar as Appointment Picker in Dialog for React

A Book Appointment button that opens a calendar beside fourteen time slots, from 9:00 AM to 4:30 PM.

Book Appointment opens a dialog with the same title. A month grid sits beside Available Times, fourteen slots from 9:00 AM to 4:30 PM. None is filled until a slot is clicked.

Use it when booking should stay hidden until someone asks. On a booking page, show the same grid without a Dialog. In React, Base UI does not hold the appointment.

Preview

Preview width

Installation

Sign in to get your install command
Free with an account. It carries your token, so the CLI installs everything in one go.
Use email insteadNo credit card

Anatomy

Calendar anatomy: prev trigger, month select, year select, next trigger, table, table cell

Usage

<Dialog>
  <DialogTrigger render={<Button variant="outline" />}>
    Book Appointment
  </DialogTrigger>
  <DialogContent className="sm:max-w-fit">
    <DialogHeader>
      <DialogTitle>Book Appointment</DialogTitle>
    </DialogHeader>
    <div className="flex divide-x overflow-hidden rounded-md border bg-background">
      <Calendar mode="single" onSelect={setDate} selected={date} />
      <div className="relative w-62 overflow-hidden">
        <div className="absolute inset-0 flex flex-col gap-4">
          <p className="px-4 pt-4 text-center font-medium text-sm">
            Available Times
          </p>
          <ScrollArea className="min-h-0 flex-1">
            <div className="flex flex-col gap-2 px-4 pb-4">
              {availableTimes.map((time) => (
                <Button
                  key={time}
                  onClick={handleTimeClick}
                  size="sm"
                  value={time}
                  variant={selectedTime === time ? "default" : "outline"}
                >
                  {time}
                </Button>
              ))}
            </div>
          </ScrollArea>
        </div>
{/* The rest of the file is under Installation. */}

FAQ

How do I read the date the person picked?

mode="single" makes selected one date and onSelect the setter. This demo stores that date in state, starting from new Date().

How does a time slot get selected?

Each time is a Button with that string as value. The click handler stores event.currentTarget.value in selectedTime, and that button uses variant="default" while the others stay outline.

Where does the list of times come from?

The times are a fixed availableTimes array: 9:00 AM through 11:30 AM, then 1:00 PM through 4:30 PM, in 30 minute steps. The date handler only updates the date, so the highlighted time stays until a time button is clicked.

How does the time list scroll?

The times column is w-62, filled by an absolute inset-0 wrapper. ScrollArea has min-h-0 flex-1 under the heading Available Times, so the buttons scroll inside the row.

How is the dialog opened?

DialogTrigger is a button reading Book Appointment. It opens DialogContent with sm:max-w-fit, and DialogTitle reads Book Appointment.

Does picking a new date clear the time?

The date state and selectedTime are separate. Choosing another day leaves the highlighted time in place until a time button is clicked.

API Reference

Dialog

No extra props in this demo. The rest is on the Dialog page.

Button

Sets variant="outline", onClick={handleTimeClick}, size="sm", value={time} and variant={selectedTime === time ? "default" : "outline"}. The rest is on the Button page.

Calendar

Sets mode="single", onSelect={setDate} and selected={date}. The rest is on the Calendar page.

Scroll Area

No extra props in this demo. The rest is on the Scroll Area page.