Time Slot Picker
Pick a day, then a time on that day. A calendar chooses the date; the times for it come from your getSlots function, sync or async, and show as a group of radio buttons. Times that are taken are disabled and say why. A Confirm button hands back the date and the slot. The app owns which times exist. UI permission is not security: the server must check again before it books anything.
A ready-made piece, built from the library's own parts.
A block puts several components together into something you would otherwise assemble by hand. It never fetches data: you give it the data, or answer its callbacks. It is copied into your project like any component, with the parts it uses, so you can change anything.
Appointment
October 2026
| S | M | T | W | T | F | S |
|---|---|---|---|---|---|---|
27 | 28 | 29 | 30 | 1 | 2 | 3 |
4 | 5 | 6 | 7 | 8 | 9 | 10 |
11 | 12 | 13 | 14 | 15 | 16 | 17 |
18 | 19 | 20 | 21 | 22 | 23 | 24 |
25 | 26 | 27 | 28 | 29 | 30 | 31 |
Thursday, October 8, 2026
Loading times
Times shown in Central European Summer Time (Europe/Berlin)
Choose a day and a time.
Async slots
October 2026
| S | M | T | W | T | F | S |
|---|---|---|---|---|---|---|
27 | 28 | 29 | 30 | 1 | 2 | 3 |
4 | 5 | 6 | 7 | 8 | 9 | 10 |
11 | 12 | 13 | 14 | 15 | 16 | 17 |
18 | 19 | 20 | 21 | 22 | 23 | 24 |
25 | 26 | 27 | 28 | 29 | 30 | 31 |
Thursday, October 8, 2026
Loading times
Unavailable times
October 2026
| S | M | T | W | T | F | S |
|---|---|---|---|---|---|---|
27 | 28 | 29 | 30 | 1 | 2 | 3 |
4 | 5 | 6 | 7 | 8 | 9 | 10 |
11 | 12 | 13 | 14 | 15 | 16 | 17 |
18 | 19 | 20 | 21 | 22 | 23 | 24 |
25 | 26 | 27 | 28 | 29 | 30 | 31 |
Thursday, October 8, 2026
Loading times
Times shown in Eastern Daylight Time (America/New York)
Installation
npx rdloom add time-slot-pickerCopies the source into src/components/rdloom. Edit it freely: rdloom upgrade merges later versions into your changes. It needs @internationalized/date, react-aria-components; add --install to install them.
Use another registry client
npx shadcn@latest add https://rdloom.vimalbhatt.com/r/time-slot-picker.jsonWorks, but without upgrade tracking.
Usage
import { TimeSlotPicker } from "@rdloom/react";
<div className="flex w-full justify-center">
<div className="flex w-full max-w-2xl flex-col gap-4">
<TimeSlotPicker
today="2026-10-08"
timeZone="Europe/Berlin"
getSlots={(date) => times.map((t) => ({ time: `${date}T${t}`, available: true }))}
onSelect={({ date, slot }) => setBooked(`Booked ${date} at ${slot.time.slice(11, 16)}`)}
confirmLabel="Book appointment"
/>
<p role="status" className="text-sm text-[var(--rd-color-text-muted)]">
{booked ?? "Choose a day and a time."}
</p>
</div>
</div>API Reference
Defined by the spec. Components also accept the props of the React Aria component they wrap.
| Prop | Type | Default |
|---|---|---|
getSlotsrequiredReturns the slots for a day. date is YYYY-MM-DD; time is an ISO string such as 2026-10-12T09:30. It is called again when the date changes or after Try again. | (date: string) => Array<{ time: string; available: boolean; reason?: string }> | Promise<Array<{ time: string; available: boolean; reason?: string }>> | none |
onSelectCalled when someone presses Confirm, with the date and the chosen slot. Yours, may be async. | (selection: { date: string; slot: { time: string; available: boolean; reason?: string } }) => void | Promise<unknown> | none |
defaultDateThe date chosen first, as YYYY-MM-DD. Default: today (or the minDate when later). | string | none |
minDateThe earliest date people can pick, as YYYY-MM-DD. Default: today. | string | none |
onDateChangeCalled with YYYY-MM-DD when the date changes. | (date: string) => void | none |
timeZoneAn IANA time zone such as "Europe/Berlin". Slot times with an offset are shown in it, and its name is shown under the times. Times without an offset are read as wall time in that zone. | string | none |
localeA BCP 47 locale for date and time names. Default: en-US on the server and during hydration, then the browser's. | string | none |
todayThe date treated as today, as YYYY-MM-DD. Default: the device's date. Set it for stable screenshots and tests. | string | none |
labelNames the group of times. | string | "Available times" |
confirmLabelThe text of the confirm button. | string | "Confirm" |
permissionsWhat the app allows: confirm. Hidden leaves the button out; disabled keeps it reachable (aria-disabled) with the reason read, and nothing runs. This only changes what people see: the server must check again. | Permissions<"confirm"> | none |
classNamesExtra class names for single parts, so you can restyle one part without editing the file. Keys: root, calendar, slots, heading, slotList, slot, timeZone, footer, confirmButton. | Partial<Record<"root" | "calendar" | "slots" | "heading" | "slotList" | "slot" | "timeZone" | "footer" | "confirmButton", string>> | none |
Accessibility
Role radiogroup, WCAG 2.2 AA. Tested with axe and keyboard tests; screen reader checks are in the audit checklist.
Keyboard
- Tab: moves from the calendar to the times, then to Confirm
- Arrow keys: move between times in the group and choose one
- Enter: presses Confirm
Screen readers announce
- "Available times for Monday 12 October 2026, radio group"
- "9:00 AM, radio button, 1 of 6"
- "10:30 AM, unavailable, Already booked, radio button, dimmed"
What your code must do
- The times are a radio group named by its label and the chosen date
- A taken time is disabled and its reason is part of its name
- A change of date announces how many times there are, or that there are none
- Loading marks the times region busy
- Confirm stays disabled until a time is chosen
- A disabled Confirm because of permission stays reachable and its reason is read
Block contract
- Data
- A date, and a function that returns the slots for it with an available flag and an optional reason.
- Data states
- loading, empty, error, ready
- Permissions
- confirm
- Events
- onSelect, onDateChange
- You can replace
- classNames for each part; getSlots, sync or async; timeZone and locale; confirmLabel
Guidelines
Use it when
- Booking an appointment, a call or a delivery window
- Choosing a start time from a list the app offers for a day
Avoid it when
- Typing any time of day: use TimeField
- Seeing many events across a month: use EventCalendar
- Picking only a date: use DatePicker
Don't
- Treating the permissions prop as protection: UI permission is not security, so the server must check again and refuse a taken time
- Hiding taken times: show them disabled with a reason so people see why a time is missing
- Generating slots in the component: the app owns which times exist
Design tokens
The semantic tokens this component uses. Change them once and every component follows; see Design tokens.
--rd-color-surface-default--rd-color-surface-subtle--rd-color-border-default--rd-color-border-strong--rd-color-text-default--rd-color-text-muted--rd-color-action-primary--rd-color-action-on-primary--rd-color-focus-ring