BookingCalendar is a shadcn/ui component that implements Cal.com booking functionality within your Next.js applications.
It provides a production-ready booking component with timezone detection, time format preferences, and full booking lifecycle management.
Features
🔌 Cal.com Integration: Direct API connection for slot retrieval, booking creation, rescheduling, and cancellation.
🔄 In-Session Booking Management: Users can reschedule or cancel appointments directly in the success view without page navigation.
💾 Local Slot Filtering: Caches prefetched availability data to minimize API calls when users switch between days.
🎨 Modern UI Components: Accessible, responsive UI built with shadcn/ui primitives and Tailwind CSS.
🌍 Timezone Management: Automatic timezone detection with manual selection dropdown for user control.
⏰ Time Format Toggle: User-selectable 12-hour or 24-hour time display in the interface.
Use Cases
- Service Websites: Implement booking flows for consultants, therapists, or service providers.
- Product Demos: Allow potential customers to schedule live demonstrations.
- Team Scheduling: Provide external users with availability to book meetings with team members.
- Appointment Systems: Manage reservations for healthcare, beauty, or professional services.
How to Use It
1. Install the required dependencies in your Next.js project.
pnpm add @radix-ui/react-select @radix-ui/react-slot @radix-ui/react-label
pnpm add class-variance-authority clsx tailwind-merge
pnpm add react-day-picker react-hook-form @hookform/resolvers zod
pnpm add lucide-react2. Copy these directories from the repository into your Next.js application:
src/components/booking-calendarcontains the calendar widget componentssrc/lib/booking-calendarcontains React hooks and utility functionssrc/components/uiincludes these shadcn/ui primitives:button.tsx,input.tsx,textarea.tsx,select.tsx,label.tsx,alert-dialog.tsx,alert.tsx
3. Create a .env.local file in your project root with these values:
CALCOM_API_KEY=your_cal_com_api_key_here
CALCOM_API_URL=https://api.cal.com/v2
NEXT_PUBLIC_CALCOM_EVENT_TYPE_ID=your_event_type_id_here4. Create four API route handlers in your app/api/booking-calendar/ directory:
Slots Route (slots/route.ts)
This endpoint proxies Cal.com’s availability API. It accepts start and end ISO date parameters and returns slots keyed by date.
export async function GET(request: Request) {
const { searchParams } = new URL(request.url);
const start = searchParams.get('start');
const end = searchParams.get('end');
const response = await fetch(
`${process.env.CALCOM_API_URL}/slots?startTime=${start}&endTime=${end}`,
{
headers: {
'Authorization': `Bearer ${process.env.CALCOM_API_KEY}`,
'cal-api-version': '2024-08-13',
},
}
);
const data = await response.json();
return Response.json(data);
}Booking Route (book/route.ts)
Handles booking creation by validating form input and forwarding to Cal.com.
export async function POST(request: Request) {
const body = await request.json();
const response = await fetch(
`${process.env.CALCOM_API_URL}/bookings`,
{
method: 'POST',
headers: {
'Authorization': `Bearer ${process.env.CALCOM_API_KEY}`,
'Content-Type': 'application/json',
'cal-api-version': '2024-08-13',
},
body: JSON.stringify({
eventTypeId: parseInt(body.eventTypeId),
start: body.start,
bookingFieldsResponses: body.bookingFieldsResponses,
}),
}
);
return Response.json(await response.json());
}Reschedule Route (reschedule/route.ts)
Modifies existing bookings using the booking UID.
export async function POST(request: Request) {
const { uid, start } = await request.json();
const response = await fetch(
`${process.env.CALCOM_API_URL}/bookings/${uid}/reschedule`,
{
method: 'POST',
headers: {
'Authorization': `Bearer ${process.env.CALCOM_API_KEY}`,
'Content-Type': 'application/json',
'cal-api-version': '2024-08-13',
},
body: JSON.stringify({ start }),
}
);
return Response.json(await response.json());
}Cancel Route (cancel/route.ts)
Cancels bookings with an optional reason parameter.
export async function POST(request: Request) {
const { uid, reason } = await request.json();
const response = await fetch(
`${process.env.CALCOM_API_URL}/bookings/${uid}/cancel`,
{
method: 'POST',
headers: {
'Authorization': `Bearer ${process.env.CALCOM_API_KEY}`,
'Content-Type': 'application/json',
'cal-api-version': '2024-08-13',
},
body: JSON.stringify({ reason }),
}
);
return Response.json(await response.json());
}5. Import and use the BookingWidget component in your page:
import BookingWidget from '@/components/booking-calendar/booking-widget';
export default function ContactPage() {
return (
<main className="max-w-3xl mx-auto p-4">
<BookingWidget
eventTypeId={process.env.NEXT_PUBLIC_CALCOM_EVENT_TYPE_ID || ''}
eventLength={30}
title="Schedule a meeting"
description="Choose a time that works for you."
showHeader
/>
</main>
);
}Related Resources
- Cal.com v2 API Documentation – Official API reference with endpoint specifications and authentication details.
- shadcn/ui Components – Component library providing the UI primitives used in the calendar interface.
FAQs
Q: Is a Cal.com account required to use this component?
A: Yes, you need a Cal.com account to obtain an API key and an event type ID, which are necessary for the component to fetch availability and manage bookings.
Q: Can I customize the appearance of the calendar?
A: Yes. Since you copy the source code directly into your project, you have full control over the styles. You can modify the Tailwind CSS classes in the component files to match your site’s design.
Q: Does this work with Cal.com’s self-hosted version?
A: Yes. Change the CALCOM_API_URL environment variable to point at your self-hosted instance URL instead of the cloud API endpoint.
Q: How does the prefetching reduce API calls?
A: The intersection observer loads all slots for the visible month when the widget appears in the viewport. Switching between days filters this cached data locally unless the date falls outside the prefetched range.
Q: Can users add multiple guests to a booking?
A: Yes. The booking form includes a guest email input field that accepts multiple comma-separated email addresses. Cal.com sends invitations to all listed guests.
Q: What happens if Cal.com’s API is down?
A: The widget displays error states in the UI. You should implement your own error boundaries and fallback content based on your application’s requirements.






