Installation
Add the calendar to a shadcn/ui app with the registry, load the Temporal polyfill, and know which npm packages you get.
Hapio Calendar comes in two parts: npm packages with the logic (no markup, no CSS), and registry items with the markup and the Tailwind classes, which the shadcn CLI copies into your app. Most apps install the registry block and get the packages with it.
Requirements
- React and React DOM 19.2 or later.
- A shadcn/ui app with Tailwind CSS 4: either base works, Radix (
radix-nova) or Base UI (base-nova), because the registry files import only your own shadcn components (@/components/ui/buttonand the like), never a primitive library directly. - TypeScript 5.9 or later, if you use TypeScript (the types are checked against 5.9, 6.0 and 7.0).
- For server rendering, Node 22.12 or later.
1. Add the registry namespace
The registry items name each other @hapio/<item>. Add the namespace to your app's
components.json:
{
"registries": {
"@hapio": "https://calendar.hapio.io/r/{style}/{name}.json"
}
}
{style} lets the registry serve a per-base file where one is ever needed; today every item is the
same for both bases. https://calendar.hapio.io/r/{name}.json serves the base-agnostic files only.
2. Add the calendar
npx shadcn@latest add @hapio/hapio-calendarThis copies the hapio-calendar block into components/hapio-calendar/ together with the items
it is built from, adds the shadcn components they use (button, popover, scroll-area) and
installs the npm dependencies @hapio/calendar-react, cn and temporal-polyfill. The
calendar-theme item adds the calendar's CSS variables and styles to your global CSS.
3. Load the Temporal polyfill
The calendar's dates are Temporal objects, which not
every browser and runtime has yet. Load temporal-polyfill once per app, before the calendar
renders; it leaves a native Temporal alone.
In the browser, add one line to instrumentation-client.ts at the root of your app (Next.js
runs it before any other client code): import 'temporal-polyfill/global'.
When pages render on the server on Node before 26, load it in instrumentation.ts too:
// instrumentation.ts: runs once when the Next.js server starts, before any page renders.
export async function register(): Promise<void> {
// Node before 26 has no Temporal; the polyfill leaves a native one alone.
if (process.env.NEXT_RUNTIME === 'nodejs') {
await import('temporal-polyfill/global')
}
}4. Give it a height and a time zone
<HapioCalendar events={events} timeZone="Europe/Stockholm" className="h-[36rem]" />The calendar fills the height of its root (toolbar included) and scrolls inside. For a complete
server render, give it a date as well: with an IANA timeZone and a date (defaultDate or
date) or now, the server renders the whole calendar, events included
(SSR and RSC). Continue with your first calendar.
Interactions are separate items
The block on its own is read-only, and a read-only page ships none of the interaction code. Add the items you need and wrap the calendar in their component:
| Item | Wrapper | For |
|---|---|---|
calendar-selection | <CalendarSelection> | selecting times and days |
calendar-editing | <CalendarEditing> | moving and resizing, the actions menu |
calendar-external-draggable | <CalendarExternalDrops> (inside either) | dropping items from outside |
event-calendar | (a component, EventCalendar) | migrating from another calendar |
calendar-editing brings calendar-event-actions (the actions menu and the "Change time…"
form) and calendar-recurrence-scope-dialog (the "which events?" dialog of recurring events).
The registry items
| Item | Files |
|---|---|
calendar-theme | CSS variables and styles only (no files) |
calendar-primitives | icons, the link component, the live region, descriptions, class names |
calendar-event | the event element and its parts, background events |
calendar-toolbar | the toolbar and the view switcher |
calendar-more-popover | the "+N more" popover |
calendar-time-grid | the week and day views, the now indicator |
calendar-day-grid | the month view |
calendar-list | the list view |
hapio-calendar | HapioCalendar, the view switch and the loading skeleton |
calendar-selection | CalendarSelection, the slot cursor, the selection layer |
calendar-event-actions | the actions menu and the reschedule form |
calendar-recurrence-scope-dialog | the scope dialog of recurring events |
calendar-editing | CalendarEditing, the drag preview |
calendar-external-draggable | CalendarDraggable, CalendarExternalDrops |
event-calendar | EventCalendar, the migration component |
hapio-calendar-demo | a demo page for Next.js, app/hapio-calendar-demo/page.tsx |
Every item can be added on its own; its dependencies come with it. Each copied file starts with a
stamp, // @hapio/calendar registry X.Y.Z · contract 1: the hooks warn in development when the
files implement another contract than the installed @hapio/calendar-react. See
Upgrading.
Apps with "rsc": false (Vite and others without Server Components)
With "rsc": false in components.json, the shadcn CLI removes 'use client' from the files it
copies, and in about half of them the comment lines below it go too: the license lines and the
stamp. The files work the same, and the hooks' check does not need the stamp (the block passes
its version in code: const REGISTRY = { version: …, contract: … } in
hapio-calendar/hapio-calendar.tsx is where to read it). To keep every file whole, set
"rsc": true before adding the items: the directive does nothing in an app without Server
Components.
The npm packages
| Package | What for | How you get it |
|---|---|---|
@hapio/calendar-react | the hooks, and the whole core re-exported | with the registry items |
@hapio/calendar-core | its own entries: ./locales/sv, and server code without React | npm install, the same version |
@hapio/calendar-recurrence | recurring series: rrule(), expandRecurrences, describeRule | npm install |
@hapio/calendar-compat | the migration component's options | with the event-calendar item |
temporal-polyfill | Temporal, where the runtime has none (a peer dependency, ^1.0.5) | with the hapio-calendar item |
The four @hapio packages are released together under one version: install the same version of
each. They are ES modules only, with no CSS and no side effects, and every hook file carries its
own 'use client', so the packages work in React Server Components where the
SSR guide says.
Without the registry
The hooks work with markup of your own, in any styling system:
npm install @hapio/calendar-react temporal-polyfill, then follow
Customization and check your markup with
runConformance.