Calendar
Compose a month, week, or day calendar with editable events and timezone-aware dates.
Installation
pnpm add @notion-kit/uiImport these components from @notion-kit/ui/calendar. For a date picker,
use Calendar from @notion-kit/ui/primitives.
Anatomy
import {
CalendarContent,
CalendarHeaderToolbar,
CalendarProvider,
} from "@notion-kit/ui/calendar";
<CalendarProvider
events={events}
defaultRange="monthly"
timeZone="America/New_York"
onCreate={createEvent}
onEventClick={openEvent}
onEventChange={changeEvent}
className="h-[600px]"
>
<CalendarHeaderToolbar />
<CalendarContent />
</CalendarProvider>;The provider owns navigation and temporary gesture state. You own events:
apply changes from onCreate and onEventChange to save them. Ignoring a
change leaves the original event in place. Omitting a write callback disables
its operation. readOnly disables creation, moving, resizing, and conversion;
navigation and event opening remain available.
Examples
Week
The week view separates all-day events from timed events. Overlapping events appear in adjacent columns, and events that cross midnight occupy each day they cover. This example uses UTC and saves creation, movement, resizing, and conversion in local React state.
Day with controlled navigation
Control range and anchorDate to keep the selected period and viewed date
in your application. Apply each navigation callback to update the calendar.
Read-only events
Set readOnly to disable event mutations. Users can still change the range,
navigate dates, and open events through onEventClick.
Custom event cards
Pass renderEvent to replace the default card. Compose CalendarEvent.Root,
CalendarEvent.Item, and CalendarEvent.Resize to retain event opening,
dragging, and resizing. The layout positions each segment and shows resize
handles only at the event's actual boundaries.
Event dates
interface CalendarEventData {
id: string;
name: string;
startAt: number;
endAt: number | null;
allDay: boolean;
}Timestamps are milliseconds. endAt is exclusive: an all-day event from
September 16 through September 18 ends at local midnight on September 19.
Use calendar-day arithmetic in the display timezone; a DST day can have 23
or 25 hours. Invalid events are omitted individually. Invalid timezone names
fall back to UTC.
A null end displays one local day for all-day events or one hour for timed events. Moving preserves null; resizing creates a real end. Timed events ending exactly at midnight do not occupy the next day. Existing zero-length or short events keep their values and receive a readable minimum height.
The grid shows local 00:00–24:00. New time slots snap to 15 minutes. A missing DST wall time moves forward to a valid time; a repeated wall time selects the earlier offset. Existing timestamps retain their original offset.
Navigation and editing
range supports monthly, weekly, and daily. Use range with
onRangeChange, or set defaultRange. The same pattern applies to
anchorDate, onAnchorDateChange, and defaultAnchorDate.
Changing range retains the viewed date. Previous and Next move one period; month navigation clamps dates to the last day when needed. Today uses the display timezone. Week and Day initially scroll to 08:00.
Click an empty date or all-day slot to request a one-day event. Click a time slot to request a one-hour event. Enter and Space also activate focused slots and events. Drag any visible event segment to move the whole event; only real start and end boundaries show resize handles. Escape cancels a gesture. Changing period or external event values also cancels it.
Drag between the all-day area and the time grid to convert an event. A three-day all-day event dropped at 10:00 ends at 11:00 on its third day. Timed events converted to all-day retain the dates they cover and discard the hidden time portion. A completed gesture sends one callback.
API reference
CalendarProvider
| Prop | Type | Default | Description |
|---|---|---|---|
events | readonly CalendarEventData[] | Required | Authoritative event values. |
range | CalendarRange | — | Controlled period. |
defaultRange | CalendarRange | monthly | Initial uncontrolled period. |
onRangeChange | (range: CalendarRange) => void | — | Requests a period change. |
anchorDate | number | — | Controlled viewed timestamp. |
defaultAnchorDate | number | Today | Initial viewed timestamp. |
onAnchorDateChange | (date: number) => void | — | Requests a viewed date. |
timeZone | string | Browser zone | IANA display timezone. |
weekStartsOn | 0 through 6 | 1 | First weekday, with Sunday as zero. |
readOnly | boolean | false | Disables event mutations. |
onCreate | (value: CalendarEventValue) => void | — | Receives start, end, and all-day values without an ID. |
onEventClick | (event: CalendarEventData) => void | — | Opens an event. |
onEventChange | (change: CalendarEventChange) => void | — | Receives ID, full date values, and operation reason. |
className | string | — | Sets container size and local styles. |
CalendarEventValue contains startAt, endAt, and allDay.
CalendarEventChange adds id and reason, where reason is move,
resize-start, resize-end, or convert.
Content and controls
| Component | Description |
|---|---|
CalendarContent | Renders CalendarRangeHeader and the layout for the current range. Accepts renderEvent and div props. |
CalendarMonth | Renders the month grid. Accepts renderEvent and div props. |
CalendarTimeGrid | Renders the all-day area and time slots for Week or Day. Accepts renderEvent and div props. |
CalendarRangeHeader | Renders weekday or date labels for the current range. |
CalendarToolbar | Container for custom controls. Accepts div props. |
CalendarJumpTo | Renders Previous, Today, and Next buttons. |
CalendarHeaderToolbar
Combines the period title, range select, and navigation. Also accepts div
props; children appear before the range select.
| Prop | Type | Description |
|---|---|---|
rangeDisabled | boolean | Disables the range select while keeping date navigation available. |
onRangeChange | (range: CalendarRange) => void | Overrides the provider's range-change handler for this toolbar. |
CalendarRangeSelect
| Prop | Type | Description |
|---|---|---|
value | CalendarRange | Selected range. Defaults to the provider's range. |
onChange | (range: CalendarRange) => void | Overrides the provider's range-change handler. |
disabled | boolean | Disables selection. |
CalendarEvent
| Part | Props | Description |
|---|---|---|
Root | event, segment, div props | Provides event and segment data to the card's compound parts. |
Item | Button props except render | Opens or moves the event. children replace the default time and title. |
Resize | edge: "start" | "end", button props except render | Edits one actual event boundary when changes are enabled. |
renderEvent receives CalendarEventRenderProps: the source event and its
visible segment. Pass both to Root; keep Item and Resize inside it.
For events backed by table rows and Date properties, see the Table View calendar layout.