Background layers
Shade business hours, holidays and other rules in the time grid and day-grid views.
backgroundFeature(layers) shades time from rules, such as business hours or holidays, in the time-grid, day-grid and timeline views. Each layer passed to the factory is one kind of shading: fixed ranges, ranges computed for the visible
period, or the time they leave uncovered.
// module scope: layers and their ranges functions are compared by reference
export const backgrounds = [
createBackgroundLayer({
id: 'business-hours',
kind: 'closed',
inverse: true,
ranges: createWeeklyRanges([
{
daysOfWeek: [1, 2, 3, 4, 5],
startTime: Temporal.PlainTime.from('09:00'),
endTime: Temporal.PlainTime.from('17:00'),
},
]),
}),
createBackgroundLayer({
id: 'holidays',
kind: 'holiday',
ranges: [{ allDay: true, start: Temporal.PlainDate.from('2026-12-25') }],
}),
];Register the configured feature, then bind it to the view definitions:
export const features = calendarFeatures({ background: backgroundFeature(backgrounds) });
export const views = calendarViews({
features,
views: {
week: timeGridView({ days: 7 }),
month: dayGridView({ period: 'month', maxEventsPerDay: 3 }),
timeline: timelineView({ days: 7, groupBy: 'resource' }),
},
});
export const calendar = createCalendar({
timeZone,
views,
features,
view: 'week',
date: Temporal.PlainDate.from('2026-03-11'),
events: bookings,
resources: rooms,
onNavigate: next => calendar['~update']({ ...calendar.options, view: next.view, date: next.date }),
});For dated items from your data, such as one holiday with its own id and payload, use background events instead. Both can shade the same view.
Layers
| Field | Type | Description |
|---|---|---|
id | string | Unique; emitted as data-layer. |
kind | string | Emitted as data-kind; style by it. |
ranges | Calendar_BackgroundRange[] or a function | What the layer shades. A function receives the visible range and the timeZone. |
inverse | boolean | Shade what the ranges leave uncovered instead. |
resourceIds | string[] | Shade only these resources' columns or timeline rows. Omitted, it shades every resource. |
A range is timed, { start, end } as Temporal.ZonedDateTimes, or all-day, { allDay: true, start, end? } as
Temporal.PlainDates with an inclusive end.
createBackgroundLayerreturns its argument. It types the layer where you write it and keepskinda literal.createWeeklyRanges(rules)returns a ranges function from weekly opening hours. Each rule takes ISOdaysOfWeek(1Monday to7Sunday), astartTimeand anendTime. AnendTimeat or before thestartTimeends the next day, so22:00to02:00works.- Keep
backgroundsand every ranges function at module scope or memoized. Factory defaults are fixed at calendar creation. To change a view override, replace its view definition and theviewsmap. - An inverse layer with no ranges shades everything, the same rule as a resource with no availability.
Parts
| Part | Model prop | Where |
|---|---|---|
TimeGridViewBackground | background | In TimeGridViewColumn, before the events. |
DayGridViewDayBackground | background | In DayGridViewDay, before its content. |
TimelineViewBackground | background | In TimelineViewLane, beneath foreground events. |
Each renders an aria-hidden div with data-layer and data-kind. A label such as "Closed" is your own text.
// in the time grid: under the column's events
export const Column = ({ column }: { column: Grid['columns'][number] }) => (
<TimeGridViewColumn column={column}>
{column.backgrounds.map(background => (
<TimeGridViewBackground key={background.key} background={background} />
))}
{/* background events, events and the now indicator as usual */}
</TimeGridViewColumn>
);
// in the month view: inside the day, beneath its content
export const Day = ({ day }: { day: Month['sections'][number]['rows'][number]['days'][number] }) => (
<DayGridViewDay day={day}>
{day.backgrounds.map(background => (
<DayGridViewDayBackground key={background.key} background={background} />
))}
{day.date.day}
</DayGridViewDay>
);Model
| View | Field | Contents |
|---|---|---|
| Time grid | column.backgrounds | { key, layer, kind, start, span }, as fractions of the day. |
| Day grid | day.backgrounds | { key, layer, kind } for each layer that shades the day. |
| Timeline | row.backgrounds | { key, layer, kind, start, span }, as fractions of the visible track. |
- In the time grid, an all-day range shades the whole column. Overlapping ranges within a layer merge; layers stack in input order.
- In the day grid, a day takes a layer when one of the layer's all-day ranges covers it. Timed ranges don't shade day-grid cells, except through inverse layers: an inverse layer marks the days none of its ranges touch, so business hours mark the weekends.
- In timelines, all-day ranges cover anchor windows, including overnight portions. Timed ranges use exact source
coverage, preserving DST gaps and combining repeated hours. Coverage merges before inverse gaps are computed.
Hidden weekdays contribute no shading; range callbacks receive
inputRange. - Resource-limited layers skip rows or columns without a matching resource, including ungrouped timelines.
- Every supported view inherits the registered background feature. Per-view arrays replace the default layers. The time grid's all-day row and the list aren't shaded.