Availability pickerPro
Open times from someone's calendar, picked and booked without leaving the thread, in your time zone or theirs.
On this page
Installation
Usage
import { AvailabilityPicker } from "@/components/ui/chatcn/pro/availability-picker"
<AvailabilityPicker
title="Licensing call with Odile Marchetti"
duration={30}
slots={slots}
timeZone="Europe/Lisbon"
otherTimeZone="America/New_York"
otherName="Odile"
value={start}
onValueChange={setStart}
status={status}
onBook={({ start, end }) => book(start, end)}
/>Slots are start times with an offset; every slot lasts duration minutes. The week strip groups them by calendar day in the zone being shown, and three small marks under each date say how open the day is, so the fullest day is found before it is opened. Pass days to keep fully booked days visible, struck through.
With otherTimeZone, a switch flips every time between your zone and theirs, and the readout under the grid always gives the pick in the other zone too. Times outside workingHours in your own zone carry a small sunrise or moon mark, so a 21:00 call is never booked by accident. The book button names the exact day and time.
Move status to booking while the calendar confirms, then to booked, which folds the picker into one line with the time in both zones. A slot taken while someone looked goes in bookError with what to do next. Render it inside the card your host draws for a widget answer. It fills the width it is given, so a comfortable max width (around 28 rem) comes from the card, and it follows the host theme through the kit tokens. Pass onExpand where the host can go full screen.
Examples
States
Props
| Prop | Type | Notes |
|---|---|---|
title, meta | ReactNode | What is booked, and a quiet line after the duration |
duration | number | Minutes per slot |
slots | { start: string | number | Date }[] | Open times, any order |
days | string[] | YYYY-MM-DD days to show even when full |
timeZone, otherTimeZone, otherName | string | IANA zones and a short name |
value, defaultValue, onValueChange | string | null | The picked slot's start |
status | "ready" | "loading" | "error" | "booking" | "booked" | Default ready |
onBook | ({ start, end }) => void | The primary action |
bookError, bookedNote, onChangeTime | Failed booking, booked line, pick again | |
onPrevious, onNext, rangeLabel | Week paging | |
visibleSlots | number | Times before the fold. Default 9 |
workingHours | [number, number] | Your normal hours. Default 8 to 20 |
locale, hour12 | string, boolean | Date and time formatting. Default en-GB |
onRetry, onExpand, disabled | ||
labels | Partial<…Labels> | Every string the widget writes, for translation |
Labels
labels covers the zone switch, week arrows, day and time group names, the fold, the early and late marks, the readout, every button state and the empty and error copy. Strings with values are functions: book(when), booked(when), range(from, to), inOther(time, name). Dates and times come from Intl in locale.
Keyboard
| Key | Action |
|---|---|
| Tab | Week arrows, zone switch, day strip, times, then Book |
| Arrow keys | Move between days, or between times, and pick |
| Enter or Space | Book on the book button |
Accessibility
- Days and times are native radio groups. Each day is named with its full date and how many times it has, and full days are disabled.
- Early and late are said in the time's name, not only drawn as an icon.
- The readout is a polite live region, so the pick and its time in the other zone are announced. Booked is a status message.
- Touch targets grow to 44 px on coarse pointers.
Motion
Times press to 0.97 and change fill in 150 ms. The booked line enters with a 220 ms fade from 0.96. Keyboard moves never animate, and reduced motion keeps only the fade.