Skip to content

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

Free

Calendar as Appointment Picker for Ark UI

A calendar beside Available Times: fourteen slots from 9:00 AM to 4:30 PM, with none filled until a slot is clicked.

A month grid sits beside a scrolling list labeled Available Times. Fourteen slots run from 9:00 AM to 4:30 PM, skipping noon. None is filled until a slot is clicked. Choosing another day leaves that slot filled.

Use it on a booking page where the day and the time are chosen together. Keep the same layout off the page until needed, in a Dialog. In React, Ark UI does not reserve the slot.

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

<div className="flex divide-x overflow-hidden rounded-md border bg-background">
  <Calendar onValueChange={handleValueChange} value={value}>
    <CalendarViewControl>
      <CalendarPrevTrigger />
      <CalendarViewDate />
      <CalendarNextTrigger />
    </CalendarViewControl>
    <CalendarTable>
      <CalendarWeekDays />
      <CalendarTableDays />
    </CalendarTable>
  </Calendar>
  <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>
{/* The rest of the file is under Installation. */}

FAQ

How do I read the date the person picked?

value is a list holding the selected date, and onValueChange receives { value }. This demo starts the list from parseDate(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 calendar set beside the times?

A flex row with divide-x, overflow-hidden, rounded-md and border holds the calendar and the times column. The calendar is the first child, and the times column is w-62.

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

Calendar

Sets onValueChange={handleValueChange} and value={value}. The rest is on the Calendar page.

Scroll Area

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

Button

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