BookingCalendar
Pick a day and a free time, confirm, and get a confirmation.
SourcePackage @weft-js/warp
Preview
tsx
import { state } from '@weft-js/core'import { BookingCalendar, Stack, Text } from '@weft-js/warp'import type { BookingDetails, BookingSlot } from '@weft-js/warp'import { at, clock } from '@weft-js/warp-core'export function Onboarding() { const booked = state<BookingDetails | null>(null) const book = async (b: BookingDetails) => { await pause(800) booked.value = b } return ( <Stack gap="related"> <BookingCalendar slots={slotsFor} defaultDate={TODAY} min={TODAY} today={TODAY} locale="en-GB" duration={30} isDateUnavailable={isWeekend} onBook={book} /> {booked.value && <Text role="caption" subtle>Onboarding call booked for {booked.value.date} at {clock(booked.value.s, 'en-GB')}.</Text>} </Stack> )}const TODAY = '2026-11-03'const min = 60_000const pause = (ms: number) => new Promise(done => { setTimeout(done, ms) })const TIMES = [540, 570, 600, 630, 660, 780, 810, 840]const slotsFor = (date: string): BookingSlot[] => TIMES.map((s, i) => ({ s, disabled: (i + Number(date.slice(8))) % 4 === 0 }))const isWeekend = (date: string) => new Date(date).getUTCDay() % 6 === 0Installation
npm install @weft-js/warpUsage
tsx
import { BookingCalendar } from '@weft-js/warp'Loading
Set loading to show that times are on their way.
tsx
import { BookingCalendar } from '@weft-js/warp'export function Loading() { return ( <BookingCalendar slots={[]} loading defaultDate={TODAY} today={TODAY} locale="en-GB" onBook={() => {}} /> )}const TODAY = '2026-11-03'Times failed to load
Use error to say the times could not be loaded.
tsx
import { BookingCalendar } from '@weft-js/warp'export function Failed() { return ( <BookingCalendar slots={[]} error="We couldn't load the times. Try again in a moment." defaultDate={TODAY} today={TODAY} locale="en-GB" onBook={() => {}} /> )}const TODAY = '2026-11-03'When to use
Use it for
- Someone books time with you or your team
Reach for something else
- MeetingPlanner, when you are finding a time for several people
Accessibility
- Each time is a pressed-or-not button. A typed time is always offered as well.
API Reference
| Prop | Type | Default | Description |
|---|---|---|---|
slots | BookingSlot[] | ((date: string) => BookingSlot[]) | — | The times: a list, or a function of the date. |
date | `${number}-${number}-${number}` | State<`${number}-${number}-${number}` … | — | The picked day: a state it writes, or a plain value with onDateChange. |
defaultDate | `${number}-${number}-${number}` | — | The day picked at the start, when the calendar keeps its own state. |
duration | number | — | Minutes. |
min | string | — | The lowest allowed value. |
max | string | — | The highest allowed value. |
isDateUnavailable | ((d: `${number}-${number}-${number}`) => boolean) | — | Days to close. |
loading | boolean | false | The times are on their way. |
error | unknown | — | Why the times failed. |
today | string | — | Pins today's date, for server rendering and tests. The browser's clock otherwise. |
locale | string | — | The language and region used to format values. |
hourCycle | string | — | h12 for AM and PM, h23 for a 24-hour clock. The locale's otherwise. |
| Event | Type | Default | Description |
|---|---|---|---|
onDateChange | ((d: `${number}-${number}-${number}`) => void) | — | Called with the day picked. Load that day's slots here. |
onBook | (b: BookingDetails) => void | Promise<unknown> | — | Confirmed. Rejecting keeps the confirm step and says why. |
Parts
Every element carries a stable name, so you can style it from your own CSS: [data-scope="booking"] [data-part="slot"]
slotrootslots