calendar-tsx

Navigation

Move between views and dates with controlled view and date.

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

ActionNext 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.

Applying navigation

Keep view and date where your app keeps state, and pass them back in through ~update:

let navigation: { view: ViewKey; date: Temporal.PlainDate } = { view: 'week', date: Temporal.Now.plainDateISO() };

const calendar = createCalendar({
  timeZone,
  views,
  features,
  ...navigation,
  events: bookings,
  onNavigate: next => {
    navigation = { view: next.view, date: next.date };
    // `next.range` is known here, before the calendar changes: start fetching its events now
    calendar['~update']({ ...calendar.options, ...navigation });
  },
});

calendar.api.next(); // calls onNavigate, which applies the next week

Read the title and the current view from the state's context:

const { context } = calendar.store.state;
export const title = context.period.start.toLocaleString('en-US', { month: 'long', year: 'numeric' });

Range and period

Every view and date resolve to three inclusive date ranges on state.context:

  • period is the nominal span: a calendar period, months months, weeks whole weeks, or days days. Use it for titles.
  • range is what is visible. Month-based day grids extend to whole weeks; other views use the period itself. Hidden dates stay inside this anchor envelope.
  • inputRange covers the source dates the visible windows need, including the following morning for overnight windows. Fetch events for it.
ViewPeriodRange
Day grid, month-based durationThe calendar period or month countPadded to full weeks; six weeks per month with fixedWeekCount: true
Day grid, weeksweeks whole weeks from the week of dateSame as period
Time grid or list, days: 7The week containing dateSame as period
Time grid or list, otherdays days starting at dateSame 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,
});