Built for booking, not just for showing dates.
Bookings come with rules, time zones and a server behind them. The calendar treats all three as its own concern, so your app passes data and answers callbacks.
Yours to edit
Headless hooks on npm, and every element and class in shadcn registry files you own.
Time zones and DST to the minute
A zone per calendar, changed without remounting. Days with 23 or 25 hours lay out correctly.
Rules that say why
Opening hours, overlap and your own checks. A refused place is announced with its reason.
Saves that settle on your data
A change shows at once, waits striped while your API saves, and reverts if the save fails.
Everything without a drag too
Keyboard moves, an actions menu on every event, live announcements. axe runs on every example with no rule disabled.
Recurring and server-rendered
RFC 5545 series with this, following and all. A complete server render when the server knows the zone and the date.
Bookings from the Hapio API, in three callbacks.
Hapio is the booking and scheduling API: services, resources, schedules and bookable slots live there. Your server talks to it with the hapio-sdk package and the calendar talks to your server, so your API token never reaches the browser.
Load
An event source fetches each visible range. Your route calls getBookings with starts_at bounds and returns plain JSON.
Book
Show getServiceBookableSlots as open time and let customers select inside it. The selection becomes a postBooking.
Reschedule
A drag or a keyboard move calls onEventChange. The booking shows at its new time while your server saves it, and reverts if Hapio refuses.
Hapio Calendar is made by Hapio, the team behind the API. The guides: Events and data and Moving and resizing events.
// Your server keeps the token: app/api/bookings/route.ts answers with
// hapio.getBookings({ query: { 'starts_at[gte]': from, 'starts_at[lte]': to } })
// where hapio = createHapioClient({ token: process.env.HAPIO_TOKEN }).
const SOURCES: EventSource<CalendarEventInput<HapioBooking>>[] = [
{
id: 'hapio',
window: 'week', // whole weeks: moving inside a loaded week fetches nothing
async fetch(range, { signal }) {
const url = `/api/bookings?from=${range.startIso}&to=${range.endIso}`
const response = await fetch(url, { signal })
const bookings: HapioBooking[] = await response.json()
return bookings.map((booking) => ({
id: booking.id,
title: booking.metadata.customer,
start: booking.starts_at,
end: booking.ends_at,
resourceId: booking.resource_id,
data: booking,
}))
},
},
]
export function BookingsCalendar() {
return (
<CalendarEditing>
<HapioCalendar
eventSources={SOURCES}
timeZone="Europe/Stockholm"
editable
onEventChange={(change) => reschedule(change.event.id, change.next)}
selectable
onSelect={({ span }) => book(span)}
className="h-[36rem]"
/>
</CalendarEditing>
)
}
Add it to your shadcn/ui app.
Add the registry namespace
The registry items name each other
@hapio/<item>; the namespace incomponents.jsonresolves them.components.json { "registries": { "@hapio": "https://calendar.hapio.io/r/{style}/{name}.json" } }Add the block
The shadcn CLI copies the files into
components/hapio-calendar/, adds the shadcn components they use and installs@hapio/calendar-reactand the Temporal polyfill.npx shadcn@latest add @hapio/hapio-calendarRender it
Events, a time zone and a height. The toolbar, the four views and the keyboard come with the block.
<HapioCalendar events={events} timeZone="Europe/Stockholm" className="h-[36rem]" />
The installation guide has the full steps.
Three layers. You choose the depth.
Most apps use the block and its props. When the props are not enough, edit the copied files; when you need markup of your own, build it on the hooks.
@hapio/calendar-coreFramework-free TypeScript: dates, time zones, layout, rules, the interaction engines and the store. Safe to import in React Server Components.
@hapio/calendar-reactHooks that return prop-getters with every role, ARIA attribute, id, handler and geometry variable an element needs. No DOM, no CSS.
@hapio registryThe styled components, HapioCalendar included. The shadcn CLI copies them into your app, so every element and Tailwind class is yours to edit.
Free, all of it.
MIT licensed, every view and feature, now and later. Resource timelines, virtualization and print are planned as free features too.
Migrating from another calendar? Change one import.