Scales
This guide covers how to tune the time axis on day, week, resources, and timeline views - which hours appear, how tall each slot is, and where dragged events snap.
How the time axis works
Time-based views (day, week, resources, timeline) build their grid from a time scale on one of the section axes. The scale is just configuration: a window between two hours, divided into slots of a fixed length, with an optional snap step for drag-to-create and resize.
Three numbers shape it:
startHour/endHour- the visible window. Defaults are8and18. Events outside this range stay in the data but don't render in this view.step- minutes per slot. Default60. Each slot is one row of header cells and one drop target.snapStep- minutes per snap increment when the user drags or resizes. Defaults tostep. Setfalseto disable snapping; events land exactly where the pointer releases.
The defaults live inside each view's section definition. To change them, pass a ViewConfig object in views and deep-merge overrides under sections.timeGrid.yScale - or xScale for timeline, where the time axis runs horizontally. Only the keys you set are replaced; format, ui, and other defaults stay intact.
Slot pixel size is a separate setting. It lives under the same scale's ui.minUnitHeight (vertical time axes) or ui.minUnitWidth (horizontal ones), not under sections.ui.
Setting visible hours
Restrict the day to working hours by overriding startHour and endHour on the timeGrid section's yScale:
<Calendar
events={data}
view="day"
views={[
{
id: "day",
sections: {
timeGrid: {
yScale: { startHour: 9, endHour: 17 },
},
},
},
"week",
"month",
]}
/>
The same override shape works for week. For timeline, the time axis is xScale instead - replace yScale with xScale in the override.
Adjusting slot size
step controls how many minutes one row covers. Two-hour rows give a sparser grid; 30-minute rows give a denser one. Pair step with a different snap if you want sub-row precision:
<Calendar
events={data}
view="week"
views={[
{
id: "week",
sections: {
timeGrid: {
yScale: {
startHour: 8,
endHour: 20,
step: 120,
snapStep: 30,
},
},
},
},
]}
/>
The user sees one row per two hours, but dragging snaps every 30 minutes - so a 9:30-10:00 event is still creatable without cluttering the grid.
Controlling slot height
Row height is read from yScale.ui.minUnitHeight (in pixels). Increase it for a roomier grid; decrease it to fit more hours on screen:
<Calendar
events={data}
view="day"
views={[
{
id: "day",
sections: {
timeGrid: {
yScale: {
startHour: 0,
endHour: 24,
step: 60,
ui: { minUnitHeight: 40 },
},
},
},
},
]}
/>
A full 24-hour day with 40px rows scrolls comfortably; the default 100 would stretch it across a much longer page. The same key drives row height in resources. For timeline the time axis is horizontal, so use minUnitWidth instead.
Snap granularity
Snap only affects pointer-driven actions: create-by-drag, move, and resize. It doesn't change the rendered slot boundaries. Use it to decouple visual density from edit precision:
<Calendar
events={data}
view="day"
views={[
{
id: "day",
sections: {
timeGrid: {
yScale: { snapStep: 15 },
},
},
},
]}
/>
Set snapStep: false to let users place events at arbitrary minute offsets. Omit snapStep to inherit step.
Switching configurations at runtime
views is a regular prop, so you can derive it from app state. The calendar picks up the new configuration when the array changes:
const [mode, setMode] = useState("work");
const views = useMemo(() => [
{
id: "day",
sections: {
timeGrid: {
yScale: {
startHour: mode === "work" ? 8 : 0,
endHour: mode === "work" ? 18 : 24,
step: mode === "work" ? 60 : 120,
ui: { minUnitHeight: 40 },
},
ui: { nowLine: true },
},
},
},
], [mode]);
<Calendar events={events} view="day" views={views} />
Use the same pattern to toggle full-day vs. working-hour layouts, swap step sizes per user preference, or shrink the grid for compact modes.