Hapio Calendar

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/button and 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:

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-calendar

This 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
// 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:

ItemWrapperFor
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

ItemFiles
calendar-themeCSS variables and styles only (no files)
calendar-primitivesicons, the link component, the live region, descriptions, class names
calendar-eventthe event element and its parts, background events
calendar-toolbarthe toolbar and the view switcher
calendar-more-popoverthe "+N more" popover
calendar-time-gridthe week and day views, the now indicator
calendar-day-gridthe month view
calendar-listthe list view
hapio-calendarHapioCalendar, the view switch and the loading skeleton
calendar-selectionCalendarSelection, the slot cursor, the selection layer
calendar-event-actionsthe actions menu and the reschedule form
calendar-recurrence-scope-dialogthe scope dialog of recurring events
calendar-editingCalendarEditing, the drag preview
calendar-external-draggableCalendarDraggable, CalendarExternalDrops
event-calendarEventCalendar, the migration component
hapio-calendar-demoa 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

PackageWhat forHow you get it
@hapio/calendar-reactthe hooks, and the whole core re-exportedwith the registry items
@hapio/calendar-coreits own entries: ./locales/sv, and server code without Reactnpm install, the same version
@hapio/calendar-recurrencerecurring series: rrule(), expandRecurrences, describeRulenpm install
@hapio/calendar-compatthe migration component's optionswith the event-calendar item
temporal-polyfillTemporal, 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.