Booking a visit in three choices: a provider, a day and a time, ending in a confirmation card. Built on Avatar, Button and the shared Indicator, with the availability maths inline.
When to use
- A patient portal or clinic site where people book a fixed-length visit with one of a few clinicians.
- When open and taken times should both be visible, so the grid never changes shape between days.
When not to use
- Free-form calendars with drag to create: build on calendar-core's
timeGrid. - Picking a date with no times: use Date Picker.
Installation
npx shadcn add https://eased.dev/r/appointment-scheduler.jsonThe command adds Appointment Scheduler, everything it builds on and Eased’s tokens to your project.
Usage
import { AppointmentScheduler } from "@/components/eased/appointment-scheduler/appointment-scheduler";export function Book({ providers, busy, days }: Data) { return ( <AppointmentScheduler title="Book a visit" providers={providers} busy={busy} days={days} hours={{ start: "08:30", end: "16:30", lunch: ["12:00", "13:00"], slotMinutes: 30 }} visit={{ label: "Follow-up", minutes: 20 }} onBook={(booking) => api.book(booking)} /> );}API reference
AppointmentScheduler
| Prop | Type | Default | Description |
|---|---|---|---|
providers | { id, name, short, role? }[] | short labels the chip ("Dr. Achebe"). | |
busy | Record<string, { start, end }[]> | Booked time per provider (epoch ms). | |
days | number[] | Bookable days at midnight in timeZone. | |
hours | { start, end, lunch?, slotMinutes } | Clinic hours as "HH:MM". | |
visit | { label, minutes } | A slot is open when the whole visit fits. | |
onBook | (booking) => void | Promise<void> | Book spins until it settles, then shows Booked. | |
defaultProvider | string | first | |
title | ReactNode | Leading side of the toolbar. | |
locale / timeZone | string | "en-US" / "UTC" | |
className | string | "" |
Keyboard
| Key | Action |
|---|---|
Tab | Moves between the provider group, the day group, the open times and Book. |
← / → | Move within the provider and day groups. |
Enter / Space | Pick a time; press Book. |
Accessibility
- Providers and days are radio groups; days are named with their full date and open count.
- Taken times stay in place as disabled buttons named "taken"; your booking is named "booked for you".
- Book stays focusable while disabled, and keeps focus while it changes to Booked.
Motion
Provider and day highlights glide on the snappy spring. Changing either re-tints the time grid in a 12ms ripple. Picking a time flies a copy of the slot into the card on smooth, with a small lift, and the end time fades in once it lands; a new pick mid-flight replaces the flight. Book shows a spinner, then the label morphs to Booked as the check draws. Reduced motion: no flight or ripple.
Notes for AI
Pick this for booking a fixed-length visit against known availability. Pass busy intervals, not open slots; the block derives open times and keeps taken ones visible.
Don't hide taken times or reflow the grid per day; the fixed grid is the point. Don't send personal health details in onBook's URL or query string.
Related
- Date Picker: a date on its own.
- Care Team: who the patient can book or message.