calendar-tsx

Resource view

A time grid with a column per resource, and availability shading.

A time grid with one column per resource, such as rooms or people, with the time outside each resource's availability shaded.

Loading the preview…

Live, with sample events. Click an event to see what it reports.
import { timeGridView } from '@calendar-tsx/core';

timeGridView({ days: 1, groupBy: 'resource' });

Day grids (dayGridView({ days: 1, groupBy: 'resource', maxEventsPerDay: 3 }), see grouping), lists (listView({ weeks: 1, groupBy: 'resource' }), see resource grouping) and timelines group by resource too.

Pass the resources to the calendar, in the order their columns should appear:

export const rooms: Calendar_Resource<Room>[] = [
  {
    id: 'room-a',
    title: 'Room A',
    availability: [{ start: at('2026-03-11T09:00'), end: at('2026-03-11T17:00') }],
    data: { capacity: 8 },
  },
  {
    id: 'room-b',
    title: 'Room B',
    availability: [],
    data: { capacity: 4 },
  },
];
Resource fieldDescription
idMatched against events' resourceIds.
parentIdOptional parent resource ID. Every parent must be included.
titleFor your headers.
availabilityAbsolute intervals the resource is available; the gaps are shaded.
dataYour payload, typed as TResourceData.

An event appears in every column whose resource ID is in its resourceIds; events without resourceIds appear in no resource column. With more than one day, groupOrder: 'resource-first' (the default) keeps each resource's days together so all-day bars can span them, and 'date-first' groups by day and splits all-day bars per column.

Resources with parentId form a hierarchy of any depth. Parents appear before descendants; roots and siblings retain their input order. Children may precede their parents in the input. IDs must be unique; missing parents, self-parenting and cycles throw. Replace the resource array when changing the hierarchy.

Time grids, day grids and resource agendas use the full depth-first order. Agendas continue to omit empty groups. Timeline supports expanding and collapsing descendants. A parent is an ordinary resource with its own events and availability. Assignments, availability and backgrounds do not inherit through the hierarchy.

Example

useLayout narrows the layout for <Show>; see narrowing the layout.

resource-view.tsx
export const Rooms = () => {
  const grid = useLayout('timeGrid');

  return (
    <Show when={grid()}>
      {grid => (
        <TimeGridView model={grid()}>
          <TimeGridViewHeader>
            <For each={grid().columns}>
              {column => (
                <TimeGridViewColumnHeader column={column}>
                  {column.resource?.title}
                  {column.isUnavailable && ' (closed)'}
                </TimeGridViewColumnHeader>
              )}
            </For>
          </TimeGridViewHeader>
          <TimeGridViewBody model={grid()}>
            <For each={grid().columns}>
              {column => (
                <TimeGridViewColumn column={column}>
                  <For each={column.unavailable}>{segment => <TimeGridViewUnavailable segment={segment} />}</For>
                  <For each={column.segments}>
                    {segment => <TimeGridViewEvent segment={segment}>{segment.event.data.title}</TimeGridViewEvent>}
                  </For>
                </TimeGridViewColumn>
              )}
            </For>
          </TimeGridViewBody>
        </TimeGridView>
      )}
    </Show>
  );
};

Model

Resource columns are time-grid columns with three more fields:

Column fieldDescription
resourceThe column's resource.
unavailable{ key, start, span } segments between availability intervals, as fractions.
isUnavailableThe resource has no availability on this day; the whole day is shaded.

Parts

The time-grid parts, plus TimeGridViewUnavailable, which takes an unavailable segment, renders an aria-hidden div and shades its span. TimeGridViewColumnHeader adds data-resource-id.