caliper uidocs

Availability scheduler

Block

Pick a day and a time slot from a host's availability.

Installs components/ui/availability-scheduler.tsxDepends on @radix-ui/react-slot, lucide-reactMotion shared motion tokens
pnpm dlx shadcn@latest add https://caliperui.site/r/availability-scheduler.json

Book a time

A 30 minute call with the team. Pick any open time.

30 min · Europe/London

October 2026

Pick a date to see times

Days with open times can be chosen. The rest are dimmed.

Nothing selected yet.

When to use #

Use it for

  • Booking a meeting or demo.

Skip it when

  • Time zones are not handled.

Installation #

terminal
pnpm dlx shadcn@latest add https://caliperui.site/r/availability-scheduler.json

Adds its 8 files to components/ui/, each with its own stylesheet, and installs their packages. No account needed. The first part also adds Caliper UI’s tokens; import them once as in Installation.

Usage #

This is the code behind the preview above.

example.tsx
import { useRef } from "react"
import { AvailabilityScheduler } from "@/components/ui/availability-scheduler"

const wait = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms));

const availability: Record<string, string[]> = {};

export function AvailabilitySchedulerDemo() {
  const tried = useRef(false);
  return (
    <AvailabilityScheduler
            availability={availability}
            timeZone="Europe/London"
            duration={30}
            description="A 30 minute call with the team. Pick any open time."
            onConfirm={async ({ time }) => {
              await wait(900);
              if (time === "14:00" && !tried.current) {
                tried.current = true;
                return { error: "That time was just taken. Pick another." };
              }
            }}
          />
  );
}

API reference #

AvailabilityScheduler

component
PropTypeDefaultDescription
availability *Readonly<Record<string, readonly string[]>>—Open start times ("09:30") by ISO date. Days not listed are unavailable.
timeZone *string—A readable zone name, such as "Europe/London".
durationnumber30Minutes.
titlestring"Book a time"
descriptionstring—
onConfirm *(booking: { date: string; time: string; }) => SchedulerResult | Promise<SchedulerResult>—
classNamestring—

Accessibility #

It exposes status and group semantics, with aria-label and aria-hidden kept in step with state. Changes that matter are announced through a live region.

No custom key handling; Tab and the native activation keys do the work.

Motion #

Transitions use the shared duration and easing tokens. Under prefers-reduced-motion those tokens collapse to instant and travel distances go to zero, so state changes still happen without movement.