Navigation
Move between views and dates, build a toolbar and sync the URL.
view and date are controlled. The navigation actions never change the calendar: each computes the next view and
date and calls onNavigate with them, and the calendar moves when you pass them back in.
Actions
| Action | Next view and date |
|---|---|
next(), prev() | One period forward or back: the configured calendar period, months, weeks, or days. |
today() | Today in timeZone, from the clock. |
goTo(date) | Same view, date. |
setView(view, date?) | view, keeping the current date unless one is given. |
navigate({ view, date }) | Either or both; omitted fields keep their current value. |
onNavigate receives { view, date, range, period, inputRange }. The next range arrives with the call, so you can update the URL or
start fetching events before the calendar re-renders.
Toolbar
Call the actions on the instance from useCalendar, and select the context for the title and the current view:
export const Toolbar = (props: { calendar: AppCalendar }) => {
const context = useCalendarSelector((state: State) => state.context);
const title = () => context().period.start.toLocaleString('en-US', { month: 'long', year: 'numeric' });
return (
<header>
<button onClick={() => props.calendar.api.prev()}>Previous</button>
<button onClick={() => props.calendar.api.today()}>Today</button>
<button onClick={() => props.calendar.api.next()}>Next</button>
<h2>{title()}</h2>
<select
value={context().view}
onChange={event => props.calendar.api.setView(event.currentTarget.value as ViewKey)}
>
<option value='month'>Month</option>
<option value='week'>Week</option>
<option value='day'>Day</option>
<option value='agenda'>Agenda</option>
</select>
</header>
);
};URL sync
Keep view and date in the URL, so links and the back button work:
const fromUrl = () => {
const params = new URLSearchParams(window.location.search);
const view = params.get('view');
const date = params.get('date');
return {
view: isViewKey(view) ? view : ('week' as ViewKey),
date: date ? Temporal.PlainDate.from(date) : Temporal.Now.plainDateISO(),
};
};
export const useUrlCalendar = () => {
const [navigation, setNavigation] = createSignal(fromUrl());
return useCalendar(() => ({
timeZone,
views,
features,
view: navigation().view,
date: navigation().date,
events: bookings,
onNavigate: next => {
// `next.range` is known here, before rendering: start fetching its events now
window.history.replaceState(null, '', `?view=${next.view}&date=${next.date.toString()}`);
setNavigation({ view: next.view, date: next.date });
},
}));
};Range and period
Every view and date resolve to three inclusive date ranges on state.context:
periodis the nominal span: a calendar period,monthsmonths,weekswhole weeks, ordaysdays. Use it for titles.rangeis what is visible. Month-based day grids extend to whole weeks; other views use the period itself. Hidden dates stay inside this anchor envelope.inputRangecovers the source dates the visible windows need, including the following morning for overnight windows. Fetch events for it.
| View | Period | Range |
|---|---|---|
| Day grid, month-based duration | The calendar period or month count | Padded to full weeks; six weeks per month with fixedWeekCount: true |
Day grid, weeks | weeks whole weeks from the week of date | Same as period |
Time grid or list, days: 7 | The week containing date | Same as period |
| Time grid or list, other | days days starting at date | Same as period |
All four factories support days, weeks, months, and calendar period settings. Calendar quarters begin in January, April, July and October. A months: 3 period starts with the anchor month instead.
export const periodsFeatures = calendarFeatures({});
export const periods = calendarViews({
features: periodsFeatures,
views: {
workweek: timeGridView({ weeks: 1, hiddenDays: [6, 7] }),
threeMonths: listView({ months: 3 }),
quarter: listView({ period: 'quarter' }),
monthTimeline: timelineView({ period: 'month', groupBy: 'resource', slotMinutes: 1440, minSlotWidth: '6rem' }),
yearTimeline: timelineView({ period: 'year', slotMinutes: 1440, minSlotWidth: '6rem' }),
},
});Weeks start on the firstDayOfWeek option, an ISO weekday from 1 (Monday) to 7 (Sunday, the default). The
DayOfWeek constant names them (DayOfWeek.Monday is 1) and also fits hiddenDays and weekly background
daysOfWeek; plain numbers stay valid. The calendar filters input by inputRange, and layouts then clip to visible
dates and hours.
To get the range for any view and date without a calendar, for example to fetch on the server, use
getCalendarNavigation:
import { getCalendarNavigation } from '@calendar-tsx/core';
const { range, period, inputRange } = getCalendarNavigation(views, 'month', Temporal.PlainDate.from('2026-03-11'), {
firstDayOfWeek: 1,
});