Skip to content
weftGitHub

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 === 0

Installation

npm install @weft-js/warp

Usage

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

Accessibility

  • Each time is a pressed-or-not button. A typed time is always offered as well.

API Reference

PropTypeDefaultDescription
slotsBookingSlot[] | ((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.
durationnumber—Minutes.
minstring—The lowest allowed value.
maxstring—The highest allowed value.
isDateUnavailable((d: `${number}-${number}-${number}`) => boolean)—Days to close.
loadingbooleanfalseThe times are on their way.
errorunknown—Why the times failed.
todaystring—Pins today's date, for server rendering and tests. The browser's clock otherwise.
localestring—The language and region used to format values.
hourCyclestring—h12 for AM and PM, h23 for a 24-hour clock. The locale's otherwise.
EventTypeDefaultDescription
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

RelatedMeeting plannerInline calendar