React
@hapio/calendar-react: useCalendar, the view hooks with their prop-getters, and the types of the block's UI props.
@hapio/calendar-react renders no DOM: each hook returns data and prop-getters to spread on your
elements. Every hook file carries 'use client'; the entry itself has none, so a Server Component
may import its types and re-exported core functions. It re-exports @hapio/calendar-core.
The calendar
function useCalendar<TData = undefined, TRes = undefined, const V extends ViewMap = DefaultViews, TDrag = never>(options: CalendarOptions<TData, TRes, V, TDrag>, config?: UseCalendarConfig): CalendarInstance<TData, TRes, V>;Creates and binds a calendar. TData is inferred from events (each event's data) or from
eventItems + eventAccessor (the item type); V from views (default defaultViews);
TDrag from acceptExternal. Share the result with CalendarProvider so parts and hooks
below can read it.
const calendar = useCalendar({ events, timeZone: 'Europe/Stockholm', defaultView: 'list' })
return (
<CalendarProvider value={calendar}>
<div {...calendar.getRootProps({ className: 'h-[36rem]' })}>…</div>
</CalendarProvider>
)What useCalendar returns and CalendarProvider shares. model and getRootProps
change per render; commands and store are stable for the calendar's lifetime.
Prop
Type
Second argument of useCalendar: how the caller identifies itself.
Prop
Type
function CalendarProvider(props: CalendarProviderProps): ReactNode;Shares a useCalendar instance with the parts and hooks below. Renders only children.
function useCalendarContext(): CalendarInstance;The calendar of the nearest CalendarProvider, untyped (unknown data, any view map). Use
createCalendarKit for typed access. Throws a TypeError outside a provider.
function useCalendarSelector<T>(selector: (state: CalendarState<string>) => T, isEqual?: (a: T, b: T) => boolean): T;Subscribes to a slice of the calendar store's state (through useSyncExternalStore, with the
server snapshot during hydration) and re-renders only when the selected value changes by
isEqual (default Object.is). selector must be pure; it may be an inline function.
Reads the calendar from CalendarProvider.
const busy = useCalendarSelector((s) => s.live.pending.has(segment.event.key))Typed hooks: createCalendarKit
function createCalendarKit<TData = undefined, TRes = undefined>(): <const V extends ViewMap>(views: V) => CalendarKit<TData, TRes, V>;Typed hooks for one event type and resource type, bound to a view map, so the generics survive React context. Call at module scope.
const kit = createCalendarKit<Booking>()(defineViews({ week: timeGrid(), list: list() }))
function BookingChip({ segment }: { segment: EventSegment<Booking> }) {
const ev = kit.useCalendarEvent(segment)
…
}Hooks bound to one event type, resource type and view map, so the generics survive React
context. Each member behaves like the
standalone hook of the same name. M3 adds useView, useTimeGrid, useDayGrid,
useBackgroundEvent and useMoreLink; M6 useEventActions; M7 useRecurrenceScopePrompt.
(useNowIndicator and useDayCell carry no data type and are used standalone; the
@hapio/calendar-react/interaction hooks are not kit members, so the kit never bundles
them.)
Prop
Type
Demo state: useLocalEvents
function useLocalEvents<TData = undefined>(initial: readonly CalendarEventInput<TData>[] | (() => readonly CalendarEventInput<TData>[])): LocalEvents<TData>;Local event state for demos: returns [events, onEventChange], where onEventChange replaces
the changed event with change.apply() and each related event (same groupId) with its copy
from change.applyRelated(), matched by id; an occurrence of a server-expanded series (an
input with recurrence) by its series and original start, so changing one occurrence leaves
the others. Pass both to the calendar; the change settles on its own echo. Series given as
series (an engine's rules) are not local state here: save change.recurrence.plan.
The toolbar
function useToolbar(): UseToolbarResult;The toolbar of the calendar in CalendarProvider: title, labels, view options and the
getters for the today/prev/next buttons, the title and the view switcher. Uses
model.toolbar, model.views and the store's commands; renders nothing.
What useToolbar returns. Names follow the reference app's expectations: buttons "Previous
Week"/"Next Week" (by unit), "Today" (hint "This week"), a radiogroup "Calendar view" with
radios "Week view", "Day view", "Month view", "List view".
Prop
Type
Views
function useView(): ViewModelOf<ViewMap>;The active view's model (model.view) of the calendar in CalendarProvider, discriminated by
kind, for renderer dispatch and custom views. Throws a TypeError while the model is
pending (render the skeleton then: model.status === 'pending'). Untyped here; the kit's
useView carries the view map and data type.
Time grids
function useTimeGrid(): UseTimeGridResult;The active time grid of the calendar in CalendarProvider: its header tiers, lanes and axis,
and the getters of the time-grid markup (see UseTimeGridResult). Applies scrollTime in a
layout effect and registers the scroller for commands.scrollToTime. Throws a TypeError
when the active view is not a time grid or the model is pending. Events are unknown-typed
here; the kit's useTimeGrid types them.
What useTimeGrid returns. Markup:
div getScrollerProps the view's one 2D scroller (overflow: auto)
div getGridProps role=grid
div getHeaderRowGroupProps role=rowgroup, sticky top: 0
div getHeaderRowProps(tier) role=row
div getAxisHeaderProps role=columnheader, sticky inline-start: 0 ("Time", sr-only)
div getColumnHeaderProps(h) ×N role=columnheader; a nav-link button inside with navLinks
div getAllDayRowGroupProps role=rowgroup (with `allDay`)
div getAllDayRowProps role=row
div getAllDayHeaderProps role=rowheader ("All day"), sticky inline-start: 0
div getAllDayCellProps(lane) ×N role=gridcell: row segments (relative to the row), "+N more"
div getBodyRowGroupProps role=rowgroup
div getBodyRowProps role=row
div getAxisProps role=rowheader ("Time"); labels aria-hidden; sticky inline-start: 0
div getSlotLabelProps(l) ×M
div getLaneProps(lane) ×N role=gridcell, position: relative, no padding-block
bands (getBandProps), background (useBackgroundEvent), events (useCalendarEvent),
more groups (useMoreLink), the now indicator (useNowIndicator)Wrappers between the sticky elements and the scroller use overflow: clip, never hidden;
the scroller has scrollbar-gutter: stable. Read-only time grids have no slot composite: tab
stops are the header nav links (one roving stop) and the events composite. M4: with
interactions.slots (the selection engine and selectable, onDateClick or droppable) each
lane and all-day cell also renders the selection layer (useInteractionMirror, before the
events) and the slot cursor (useSlotCursor, after them), both from
@hapio/calendar-react/interaction and each scoped to its lane, so moving the cursor or
extending a selection re-renders only the lanes involved; the slot composite is the third tab
stop.
Prop
Type
function useNowIndicator(laneKey: string): UseNowIndicatorResult;The now indicator of one time-grid lane (see UseNowIndicatorResult): visible when
model.view.now.laneKey === laneKey. The store's clock ticks every minute while
nowIndicator is on (never read during render). Returns visible: false outside time grids.
What useNowIndicator returns. It reads model.view.now (the store's clock at
minute resolution with nowIndicator; never the render time).
Prop
Type
Day grids
function useDayGrid(): UseDayGridResult;The active day grid of the calendar in CalendarProvider: its weeks and column headers, and
the getters of the day-grid markup (see UseDayGridResult), including the one
ResizeObserver that measures the 'auto' capacity. Throws a TypeError when the active
view is not a day grid or the model is pending. Events are unknown-typed here; the kit's
useDayGrid types them.
What useDayGrid returns. Markup:
div getScrollerProps the view's scroller
div getGridProps role=grid (one ResizeObserver: the 'auto' capacity)
div getHeaderRowGroupProps role=rowgroup, sticky top: 0
div getHeaderRowProps role=row > div getColumnHeaderProps(h) ×N
div getBodyRowGroupProps role=rowgroup
div getWeekRowProps(w) role=row, position: relative (CSS grid)
div useDayCell(d).getCellProps() ×N role=gridcell, roving tabIndex (dates composite)
span day.label visually hidden: the cell's name (from content)
button|span getWeekNumberProps(w) in the row's first cell (with `weekNumbers`)
button|span getDayNumberProps(d)
row segments starting here (useCalendarEvent), background, "+N more" (useMoreLink)Tab stops: the dates composite (one roving cell) and the events composite. M4: each day
cell is rendered through useDayCell(day).getCellProps() (a per-day
subscription, so moves and selection steps re-render only the cells that change); with the
selection engine each day cell also renders the selection layer (useInteractionMirror(day.key)
from @hapio/calendar-react/interaction, row pieces positioned against the week row), and the
dates composite selects (Space, arrows, Enter, Escape).
Prop
Type
function useDayCell(day: DayGridDay): UseDayCellResult;One day cell of the active day grid (see UseDayCellResult): the dates composite's item, with
a per-day subscription (its tab stop, focus and selection state), so a move or a selection
step re-renders only the cells that change, never the grid or its headers. Implements the
dates composite's key table itself (its moves, the nav link,
onDateClick, and with the selection engine Space, Shift+moves, Enter and Escape through
store.interact), so the react barrel never imports @hapio/calendar-react/interaction.
What useDayCell returns: one day-grid cell with its
own subscription, so moving through the dates composite or extending a selection re-renders
only the cells whose state changes (2 per move, at most 7 per week step), never the grid,
its headers or the other cells.
Prop
Type
Lists
function useList(): UseListResult;The active list view of the calendar in CalendarProvider: its days and the getters of the
list markup. Throws a TypeError when the active view is not a list (renderers dispatch by
model.view.kind) or the model is pending. Events are unknown-typed here; the kit's
useList types them.
What useList returns. Markup: ul[aria-label="Events"] > li (day group:
h{headingLevel} with the heading and alt text) > ul[aria-labelledby=heading] > li > the
event (a or button, from useCalendarEvent). The list is rendered only when !isEmpty;
otherwise the empty element shows emptyText. Descriptions are never inside list items (they
live in the per-view hidden container), so an item's text is exactly its visible content.
Prop
Type
Events
function useCalendarEvent(segment: EventSegment, o?: UseCalendarEventOptions): UseCalendarEventResult;One event segment's content, state and root props (see UseCalendarEventResult). One
subscription per element, through the calendar's event hub (one store listener), which tells
it only of changes to its own event (the pending overlay; M5: the edit session's kind and
input, so a keyboard step re-renders nothing, and the touch selection), to its segment (the
tab stop) or to what every event reads from the committed model, so only the affected events
re-render; callbacks (onEventClick) are read from store.getOptions() when an event fires,
never during render. Focus after a keyboard drop and a revert follows in useCalendar
(followEvent).
What useCalendarEvent returns. M2 is the read-only subset, M3 adds the column and row
variants; M5 adds editing (edits, the Space-grab keys, getResizeHandleProps(edge)). M6
adds nothing here: an event's actions trigger is useEventActions(segment).getTriggerProps
of @hapio/calendar-react/interaction (a getter here would put the actions' code in every
bundle).
Prop
Type
Options of useCalendarEvent.
Prop
Type
The visible content of an event.
Prop
Type
An event's state, mirrored by the root's data-* attributes. The editing states arrive with
the editing engine (M5).
Prop
Type
The edits a segment allows now (useCalendarEvent().edits, M5): an engine with the 'edit'
feature, the event's resolved editable flags, and where the segment is. move: Space grabs
it and a drag of its body moves it (time and day grids; not in lists, not in the "+N more"
popover). resizeStart/resizeEnd: the segment shows that resize handle (it holds that end of
the event: isStart/isEnd; timed events in time grids, all-day events in the all-day row
and day grids). All false without the engine, for recurring occurrences (until M7) and
while the event's change is pending.
Prop
Type
"+N more" and background events
function useMoreLink(group: MoreGroup): UseMoreLinkResult;One "+N more" group's trigger and popover (see UseMoreLinkResult). Subscribes per key to
live.moreOpen and the events composite's focus cursor, so only the affected group
re-renders.
What useMoreLink returns. The registry renders a controlled
Popover (open, onOpenChange) with the trigger as PopoverTrigger (no asChild)
and the listed segments through useCalendarEvent(segment, { context: 'popover' }).
Prop
Type
function useBackgroundEvent(segment: BackgroundSegment, o?: UseBackgroundEventOptions): UseBackgroundEventResult;One background segment's root props and name (see UseBackgroundEventResult). Pure: no
subscription (background segments have no live state).
What useBackgroundEvent returns.
Prop
Type
Descriptions and announcements
function useDescriptions(): UseDescriptionsResult;The hidden descriptions container of the active view: one item per segment (segment.description,
id = the event's describedById), so event elements carry only their visible text; in a
selectable grid (M4) first the selection hint ("Press Space to start a selection"), which the
slot cursor and the day cells refer to.
What useDescriptions returns: one hidden container per view.
Prop
Type
function useAnnouncer(): UseAnnouncerResult;The live region of the calendar in CalendarProvider: the latest announcement
(live.announcement) and the region's props. With announcer: 'auto' it speaks through
element.ariaNotify (on the root) where available instead.
What useAnnouncer returns.
Prop
Type
UI props
The types of HapioCalendar's render functions, class names and renderers.
What renderEvent receives.
Prop
Type
What renderBackgroundEvent receives (M3).
Prop
Type
What renderDayHeader receives (M3).
Prop
Type
type CalendarPart = "root" | "toolbar" | "view" | "dayHeader" | "dayCell" | "dayLane" | "event" | "backgroundEvent" | "moreLink" | "nowIndicator" | "listDay" | "empty";The parts classNames can style.
What a classNames function receives for each part. Parts rendered from M3 on (dayHeader,
dayCell, dayLane, backgroundEvent, moreLink, nowIndicator) may gain fields until then.
Prop
Type
Props of a view renderer (renderers[kind], M3): the active view's model; everything else
comes from the calendar context. The registry's UnsupportedView takes the same props and is
rendered for a kind without a renderer (a kind newer than the copied registry files).
Prop
Type