State
Let the calendar own its state, or control it from your app, one value at a time.
Root keeps six values:
| Value | Type | Meaning |
|---|---|---|
view | one of your view names | The active view |
anchorDate | Temporal.PlainDate | The date the visible range is built around |
timeZone | string (IANA name) | The zone timed events are shown in |
selection | your type, or null | What the user selected |
focus | your type, or null | What has keyboard focus inside the calendar |
expandedResourceIds | array of resource IDs | Which resource groups are expanded |
Each value can be uncontrolled (the calendar owns it) or controlled (your app owns it), independently of the others.
Uncontrolled
Pass a default prop and the calendar manages the value from then on. Defaults are read once, when
Root mounts.
<Calendar.Root
events={meetings}
defaultView="day"
defaultAnchorDate={Temporal.PlainDate.from("2026-10-08")}
defaultTimeZone="Europe/Berlin"
locale="en-US"
weekRules={weekRules}
>
<Calendar.ActiveView />
</Calendar.Root>Controlled
Pass the value and a change handler. The calendar never changes a controlled value itself. When the user clicks Next, it calls your handler with the new value, and you decide whether to store it.
This example controls the date so a date input outside the calendar stays in sync. It uses the
Calendar from the Quick start.
export function ControlledCalendar() {
const [anchorDate, setAnchorDate] = useState(() => Temporal.PlainDate.from("2026-10-08"));
return (
<>
<input
type="date"
value={anchorDate.toString()}
onChange={(event) => {
if (event.currentTarget.value) {
setAnchorDate(Temporal.PlainDate.from(event.currentTarget.value));
}
}}
/>
<Calendar.Root
events={meetings}
anchorDate={anchorDate}
onAnchorDateChange={setAnchorDate}
defaultTimeZone="Europe/Berlin"
locale="en-US"
weekRules={weekRules}
>
<Calendar.PreviousTrigger>Previous</Calendar.PreviousTrigger>
<Calendar.NextTrigger>Next</Calendar.NextTrigger>
<NextWeekButton />
<Calendar.ActiveView />
</Calendar.Root>
</>
);
}The props follow one pattern:
| Uncontrolled | Controlled | Handler |
|---|---|---|
defaultView | view | onViewChange |
defaultAnchorDate | anchorDate | onAnchorDateChange |
defaultTimeZone | timeZone | onTimeZoneChange |
defaultSelection | selection | onSelectionChange |
defaultFocus | focus | onFocusChange |
defaultExpandedResourceIds | expandedResourceIds | onExpandedResourceIdsChange |
A few rules:
undefinedmeans uncontrolled. To controlselectionorfocuswith nothing selected, passnull.- Handlers also run for uncontrolled values, after the calendar updates them. The second argument
tells you the
previousvalue and whether the value iscontrolled. - If you haven't applied a change the calendar asked for, the handler also hears a later request for the value you have. For example, Today after a scroll you deferred. Apply the latest value you were given, and you'll drop the earlier change.
Rootneeds a date, a time zone, and a view on its first render. The view can come fromdefaultView,view, or the first registered view. If one is missing,Rootthrowscalendar-root-requires-anchor-date,calendar-root-requires-time-zone, orcalendar-root-requires-view.
Reading and changing state
Any component inside Root can use the hooks on your calendar:
useCalendarState()returns all six values.useCalendarSelector(selector)returns one derived value and re-renders only when it changes.useCalendarActions()returnssetView,setAnchorDate,setTimeZone,setSelection,setFocus,setExpandedResourceIds,navigateNext,navigatePrevious, andnavigateToday.useCalendarMeta()returns everything else: the event snapshot, resources, the active view, diagnostics, and element IDs and refs.useCalendar()returnsstate,actions, andmetatogether.useVisibleRange()anduseViewProjection()return what the active view shows. See Custom views.
const selectAnchorDate = (state: CalendarPublicState) => state.anchorDate;
function NextWeekButton() {
const anchorDate = Calendar.useCalendarSelector(selectAnchorDate);
const actions = Calendar.useCalendarActions();
return (
<button type="button" onClick={() => actions.setAnchorDate(anchorDate.add({ weeks: 1 }))}>
Skip a week
</button>
);
}A selector can live outside the component, as above, or inline. Actions called one after another
build on each other, so navigateNext() twice in one click moves two steps, controlled or not.
Selection and focus types
You choose the shape of selection and focus. Pass their types as the third and fourth type
arguments of createCalendar, after the event and resource types. Time stores the values as they
are.
type Selection = { readonly eventId: string } | { readonly date: Temporal.PlainDate };
const Calendar = createCalendar<Meeting, never, Selection>()({ eventAdapter });
export function SelectStandup() {
const actions = Calendar.useCalendarActions();
return (
<button type="button" onClick={() => actions.setSelection({ eventId: "standup" })}>
Select standup
</button>
);
}Last updated on