# Layout, Headers, and Cells
URL: https://pro.rv-grid.com/guides/event-scheduler/customization/layout/
Source: src/content/docs/guides/event-scheduler/customization/layout.mdx
Description: Customize scheduler column sizing, generated headers, time rows, day cells, resource rows, and timeline cells.
## Grouped customization API

Use the grouped `customization` API when you need precise control over generated scheduler surfaces.

```ts
grid.eventScheduler = {
  view: 'week',
  weekStartDate: '2026-06-08',
  customization: {
    columnSizes: {
      week: {
        timeColumnSize: 96,
        dayColumnSize: 220,
      },
      resourceTimeline: {
        resourceColumnSize: 260,
        timelineColumnSize: 144,
      },
    },
    headers: {
      dayTemplate: (h, context) =>
        h('span', { class: 'scheduler-day-header' }, context.defaultLabel),
      dayProperties: (context) => ({
        class: context.today ? 'scheduler-day-header--brand-today' : '',
        'data-scheduler-date': context.date,
      }),
      groupTemplate: (h, context) =>
        h('strong', { class: 'scheduler-range-header' }, context.defaultLabel),
    },
    cells: {
      resourceProperties: ({ resource }) => ({
        'data-resource-role': resource?.role,
      }),
      timelineProperties: ({ date, slotIndex }) => ({
        class: 'scheduler-timeline-slot',
        'data-scheduler-slot-key': `${date}:${slotIndex}`,
      }),
    },
    events: {
      properties: ({ event }) => ({
        'data-event-status': event.status,
      }),
      content: (h, { event, resource }) =>
        h('span', { class: 'scheduler-event-title' }, [
          h('strong', {}, event.title),
          h('small', {}, resource?.name ?? 'Unassigned'),
        ]),
      badge: (h, context) =>
        h('span', {
          class: `scheduler-event-chip scheduler-event-chip--${context.kind}`,
        }, context.label),
      resizeHandle: (h, context) =>
        h('span', {
          class: `scheduler-event-resize scheduler-event-resize--${context.edge}`,
        }),
    },
    createPreview: {
      createRangeTemplate: (context) =>
        `${context.title}: ${context.startLabel}-${context.endLabel}`,
      createRangeProperties: () => ({ class: 'scheduler-create-preview' }),
    },
    conflicts: {
      className: ({ severity }) => `scheduler-conflict scheduler-conflict--${severity}`,
      indicator: (h, context) =>
        h('span', { class: 'scheduler-conflict-badge' }, context.severity),
      tooltip: ({ label }) => `Review before saving: ${label}`,
    },
    emptyState: {
      template: ({ kind, label }) => `${kind}: ${label}`,
      properties: ({ kind }) => ({
        class: `scheduler-state scheduler-state--${kind}`,
      }),
    },
  },
};
```

Template hooks receive RevoGrid's `h` helper and a scheduler context object. Return a string, a VNode, `null`, or `undefined`. Returning `null` or `undefined` keeps the built-in section. Property hooks return attributes, `class`, `style`, or data attributes for the target element.

Custom properties are merged with built-in scheduler properties. Built-in interaction handlers and scheduler data attributes remain in place, so custom hooks should not attach drag, resize, selection, or mutation behavior directly.

## Column sizes

`customization.columnSizes` controls scheduler-owned column widths by view. These values are resolved before legacy top-level size props such as `timeColumnSize`, `dayColumnSize`, `resourceColumnSize`, and `timelineColumnSize`.

```ts
grid.eventScheduler = {
  view: 'resourceTimeline',
  weekStartDate: '2026-06-08',
  customization: {
    columnSizes: {
      week: {
        timeColumnSize: 88,
        dayColumnSize: 180,
      },
      resourceTimeline: {
        resourceColumnSize: 280,
        timelineColumnSize: 120,
      },
    },
  },
};
```

Use week sizes for `day`, `week`, and `month` style calendar grids. Use resource timeline sizes when the first column is the pinned resource column and the remaining columns are timeline slots.

## Header templates and properties

Header customization targets the generated scheduler columns. For day headers, use the direct week-scheduler hooks when you only need day-header customization. Use `customization.headers` when you need to customize every generated header type.

Direct day-header hooks:

| Hook | Surface |
| --- | --- |
| `dayHeaderFormatter` | Formats the built-in day header label. |
| `dayHeaderTemplate` | Replaces the day header content. |
| `dayHeaderProperties` | Adds classes, styles, attributes, data fields, or event handlers to day headers. |

```ts
grid.eventScheduler = {
  view: 'week',
  weekStartDate: '2026-06-08',
  dayHeaderFormatter: (date) =>
    date.toLocaleDateString('en-US', {
      weekday: 'short',
      month: 'short',
      day: 'numeric',
      timeZone: 'UTC',
    }),
  dayHeaderTemplate: (h, context) =>
    h('span', { class: 'planner-day-header' }, [
      h('span', { class: 'planner-day-header__label' }, context.defaultLabel),
      context.today ? h('span', { class: 'planner-day-header__badge' }, 'Today') : null,
      context.holiday ? h('span', { class: 'planner-day-header__holiday' }, context.holidayLabel) : null,
      h('span', { class: 'planner-day-header__count' }, `${context.eventCount ?? 0} events`),
    ]),
  dayHeaderProperties: (context) => ({
    class: [
      context.today ? 'planner-day-header--today' : '',
      context.weekend ? 'planner-day-header--weekend' : '',
      context.holiday ? 'planner-day-header--holiday' : '',
    ].filter(Boolean).join(' '),
    style: {
      '--planner-day-event-count': context.eventCount ?? 0,
      ...(context.holiday ? { '--planner-day-state': 'holiday' } : {}),
    },
    'data-planner-day': context.date,
  }),
};
```

Day header context includes `defaultLabel`, `date`, `dayIndex`, `eventCount`, `today`, `weekend`, `holiday`, and `holidayLabel`. Holiday state is date-level: it comes from the primary scheduler calendar or global `kind: 'holiday'` availability entries. Resource-specific holidays stay on the scheduler body cells.

`customization.headers.dayTemplate` takes precedence over `dayHeaderTemplate` when both are provided. This lets applications keep older grouped customization config while adding direct day-header hooks where convenient.

Grouped header customization has one template hook and one matching properties hook per generated header section.

| Hook | Surface |
| --- | --- |
| `timeTemplate` / `timeProperties` | Pinned time header in day/week/month style views. |
| `resourceTemplate` / `resourceProperties` | Pinned resource header in resource timeline views. |
| `dayTemplate` / `dayProperties` | Day header cells. |
| `timelineTemplate` / `timelineProperties` | Resource timeline slot headers. |
| `groupTemplate` / `groupProperties` | Grouped range/day/timeline headers when column grouping is enabled. |

```ts
grid.eventScheduler = {
  view: 'week',
  weekStartDate: '2026-06-08',
  customization: {
    headers: {
      dayTemplate: (h, context) =>
        h('span', { class: 'brand-day-header' }, [
          h('strong', {}, context.defaultLabel),
          context.today ? h('small', {}, 'Today') : null,
          context.holiday ? h('small', {}, context.holidayLabel) : null,
          context.eventCount ? h('small', {}, `${context.eventCount} events`) : null,
        ]),
      dayProperties: (context) => ({
        class: [
          context.today ? 'brand-day-header--today' : '',
          context.weekend ? 'brand-day-header--weekend' : '',
          context.holiday ? 'brand-day-header--holiday' : '',
        ],
        'data-brand-date': context.date,
      }),
    },
  },
};
```

Header contexts include the active view, the default label, date or slot fields where relevant, event counts, today/weekend/holiday flags, holiday labels, and group kind for grouped headers.

## Cell templates and properties

Cell customization targets the scheduler body cells. Use property hooks when you only need classes, styles, or test/data attributes. Use templates when the default cell body should be replaced.

Use the direct time-axis hooks when the product only needs to control the pinned time column in day, week, or month-style views.

| Config | Purpose |
| --- | --- |
| `timeLabelFormatter` | Formats built-in time text such as `06:00`, `6 AM`, or `6:00`. |
| `timeLabelTemplate` | Replaces the pinned time-label cell content. |
| `timeRowProperties` | Adds classes, styles, attributes, data fields, or event handlers to time-label cells. |
| `timeRange` | Sets the visible start/end time and hides time outside that range. |
| `slotMinutes` | Sets row granularity, for example `5`, `10`, `15`, `30`, or `60`. |
| `rowSize` | Controls row density in pixels. |
| `nonWorkingTime` | Styles closed or outside-business-hours cells in the scheduler body. |

```ts
grid.eventScheduler = {
  view: 'week',
  weekStartDate: '2026-06-08',
  timeRange: { start: '06:00', end: '22:00' },
  slotMinutes: 30,
  rowSize: 40,
  timeLabelFormatter: (minutes) => {
    const hour = Math.floor(minutes / 60);
    const minute = minutes % 60;
    return `${hour % 12 || 12}:${String(minute).padStart(2, '0')} ${hour < 12 ? 'AM' : 'PM'}`;
  },
  timeLabelTemplate: (h, context) =>
    h('span', { class: 'planner-time-label' }, context.timeLabel),
  timeRowProperties: (context) => ({
    class: context.currentTimeLabel ? 'planner-time-row--current' : 'planner-time-row',
    style: {
      '--planner-time-start': context.startMinutes,
    },
    'data-planner-time': context.timeLabel,
  }),
  nonWorkingTime: {
    enabled: true,
    workingDays: [1, 2, 3, 4, 5],
    workingHours: { start: '08:00', end: '18:00' },
    className: 'planner-closed-hours',
  },
};
```

`customization.cells.timeTemplate` and `customization.cells.timeProperties` layer over the direct `timeLabelTemplate` and `timeRowProperties` hooks when both are provided. Use `rowSize` for compact, dense, or comfortable density presets, and use `timeRange` when night hours should be hidden from the grid instead of only styled as closed.

| Hook | Surface |
| --- | --- |
| `timeTemplate` / `timeProperties` | Time-label rows in week-style grids. |
| `dayTemplate` / `dayProperties` | Day/time slots in week-style grids. |
| `resourceTemplate` / `resourceProperties` | Resource rows in resource timeline grids. |
| `resourceGroupTemplate` / `resourceGroupProperties` | Group rows when resource grouping is enabled. |
| `timelineTemplate` / `timelineProperties` | Timeline cells in resource timeline grids. |

```ts
grid.eventScheduler = {
  view: 'resourceTimeline',
  weekStartDate: '2026-06-08',
  customization: {
    cells: {
      resourceProperties: ({ resource, unassigned, depth }) => ({
        class: [
          resource?.role ? `resource-row--${resource.role.toLowerCase()}` : '',
          unassigned ? 'resource-row--open-demand' : '',
        ],
        style: { '--resource-depth': depth },
        'data-resource-role': resource?.role,
      }),
      timelineProperties: ({ date, slotIndex, segmentCount }) => ({
        class: segmentCount > 0 ? 'timeline-slot--busy' : 'timeline-slot--open',
        'data-slot-key': `${date}:${slotIndex}`,
      }),
    },
  },
};
```

When a cell template is provided, the scheduler renders that template instead of the default cell content for that slot. For day cells, replacing the template also replaces the built-in visible event slices inside that cell, so prefer `dayProperties` when the goal is only styling or annotation.
