Skip to content

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.

Blockv0.1.0experimentalWCAG 2.2 AAView spec

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

Full screen (opens in a new tab)

Choose a date, October 2026

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

Full screen (opens in a new tab)

Choose a date, October 2026

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

Full screen (opens in a new tab)

Choose a date, October 2026

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-picker

Copies 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.json

Works, 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.

PropTypeDefault
getSlotsrequired

Returns 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
onSelect

Called 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
defaultDate

The date chosen first, as YYYY-MM-DD. Default: today (or the minDate when later).

stringnone
minDate

The earliest date people can pick, as YYYY-MM-DD. Default: today.

stringnone
onDateChange

Called with YYYY-MM-DD when the date changes.

(date: string) => voidnone
timeZone

An 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.

stringnone
locale

A BCP 47 locale for date and time names. Default: en-US on the server and during hydration, then the browser's.

stringnone
today

The date treated as today, as YYYY-MM-DD. Default: the device's date. Set it for stable screenshots and tests.

stringnone
label

Names the group of times.

string"Available times"
confirmLabel

The text of the confirm button.

string"Confirm"
permissions

What 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
classNames

Extra 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