# Event Rendering
URL: https://pro.rv-grid.com/guides/event-scheduler/customization/events/
Source: src/content/docs/guides/event-scheduler/customization/events.mdx
Description: Customize event colors, properties, content, badges, resize handles, tooltips, and accessible labels.
## Event branding

Event blocks support a lightweight styling layer before you use the grouped `customization.events` API.

```ts
grid.eventScheduler = {
  view: 'week',
  weekStartDate: '2026-06-08',
  statusColorResolver: (event) => {
    if (event.status === 'confirmed') return '#2563eb';
    if (event.status === 'planned') return '#d97706';
    if (event.status === 'blocked') return '#dc2626';
  },
  eventProperties: ({ event }) => ({
    class: `scheduler-event--${event.type ?? 'default'}`,
    style: {
      '--shift-accent': event.status === 'blocked' ? '#dc2626' : '#2563eb',
    },
    'data-shift-status': event.status,
  }),
  eventTemplate: (h, { event }) =>
    h('span', { class: 'shift-event-title' }, event.title),
};
```

`statusColorResolver` resolves the scheduler event accent color when the event does not already provide `event.color` and the status does not map to a built-in status class. `eventProperties` is merged onto the event button and can return `class`, `style`, attributes, data fields, or event handlers. `eventTemplate` replaces the default event content only; it does not replace the outer interactive event shell.

## Event section templates

Use direct event hooks when a product needs a simple public API for event bars in week and resource-timeline views.

| Config | Purpose |
| --- | --- |
| `eventTemplate` | Legacy top-level event content renderer. |
| `eventContentTemplate` | Replaces the default title, time, badge, and resource content block. |
| `eventProperties` | Adds classes, styles, attributes, data fields, or event handlers to the event button. |
| `eventTooltipTemplate` | Overrides the event `title` tooltip with custom details. |
| `eventBadgeTemplate` | Replaces individual status, type, category, or required-role badges. |
| `eventResizeHandleTemplate` | Replaces the visual content inside start/end resize handles. |

```ts
grid.eventScheduler = {
  view: 'week',
  weekStartDate: '2026-06-08',
  eventProperties: (context) => ({
    class: [
      'event-card',
      context.isSelected ? 'event-card--selected' : '',
      context.isLocked ? 'event-card--locked' : '',
      context.hasConflict ? 'event-card--conflict' : '',
      context.isShortEvent ? 'event-card--short' : '',
    ].filter(Boolean).join(' '),
    style: {
      '--event-accent': context.hasConflict ? '#dc2626' : context.event.color ?? '#2563eb',
    },
    'data-event-duration': context.duration,
  }),
  eventContentTemplate: (h, { event, resource, start, end, isFromPreviousDay, isContinuedToNextDay }) =>
    h('span', { class: 'event-card__content' }, [
      h('strong', { class: 'event-card__title' }, event.title),
      h('span', { class: 'event-card__time' }, `${start.slice(11, 16)}-${end.slice(11, 16)}`),
      resource ? h('span', { class: 'event-card__resource' }, resource.name) : null,
      isFromPreviousDay ? h('span', { class: 'event-card__continuation' }, 'From previous day') : null,
      isContinuedToNextDay ? h('span', { class: 'event-card__continuation' }, 'Continues') : null,
    ]),
  eventBadgeTemplate: (h, { kind, label, isLocked }) =>
    h('span', { class: `event-card__badge event-card__badge--${kind}` }, isLocked ? 'Locked' : label),
  eventResizeHandleTemplate: (h, { edge }) =>
    h('span', { class: `event-card__resize event-card__resize--${edge}` }),
  eventTooltipTemplate: ({ event, resource, duration, hasConflict }) =>
    `${event.title} | ${resource?.name ?? 'Unassigned'} | ${duration} minutes${hasConflict ? ' | Conflict' : ''}`,
};
```

Event render contexts include `event`, `resource`, `segment`, `start`, `end`, `day`, `duration`, `status`, `type`, `isSelected`, `isDragging`, `isResizing`, `isLocked`, `hasConflict`, `isReadonly`, `isFromPreviousDay`, `isContinuedToNextDay`, and `isShortEvent`. Dragging and resizing state are currently reserved and remain `false` until the scheduler exposes public pointer-state rendering.

`customization.events` provides the grouped form of the same customization surface and layers on top of direct event properties where both are supplied. It customizes the event button and its internal sections. It applies to both week and resource timeline render modes.

| Hook | What it customizes |
| --- | --- |
| `className` | Adds one or more classes to the event button. |
| `style` | Adds inline styles or CSS custom properties to the event button. |
| `properties` | Adds attributes, classes, styles, or data attributes to the event button. |
| `content` | Replaces the default title, time, badge, and resource content block. |
| `continuationLabels` | Replaces previous/next-day continuation labels. |
| `badgeTray` | Replaces the tray that holds badges for multi-slot events. |
| `badge` | Replaces individual status, type, category, or required-role badges. |
| `resizeHandle` | Replaces the visual content inside start/end resize handles. |
| `tooltip` | Overrides the event `title` tooltip. |
| `ariaLabel` | Overrides the event button accessible label. |

```ts
grid.eventScheduler = {
  view: 'week',
  weekStartDate: '2026-06-08',
  customization: {
    events: {
      className: ({ event }) => `event-card event-card--${event.status ?? 'default'}`,
      style: ({ event }) => ({
        '--event-border-color': event.status === 'blocked' ? '#dc2626' : '#2563eb',
      }),
      properties: ({ event, segment }) => ({
        'data-event-id': event.id,
        'data-event-start-slot': segment.startSlot,
      }),
      content: (h, { event, resource, segment }) =>
        h('span', { class: 'event-card__content' }, [
          h('strong', { class: 'event-card__title' }, event.title),
          h('span', { class: 'event-card__resource' }, resource?.name ?? 'Unassigned'),
          segment.conflict ? h('span', { class: 'event-card__warning' }, 'Conflict') : null,
        ]),
      badge: (h, { kind, label }) =>
        h('span', { class: `event-card__badge event-card__badge--${kind}` }, label),
      tooltip: ({ event, resource }) => `${event.title} - ${resource?.name ?? 'Unassigned'}`,
      ariaLabel: ({ event }) => `Scheduled event ${event.title}`,
    },
  },
};
```

The outer event element remains a scheduler-managed button. It keeps click, double-click, pointer, selection, disabled move/resize state, conflict state, and accessibility wiring. Custom event content should avoid interactive controls inside the event button unless the product has tested the keyboard and pointer behavior carefully.

## Rendering content on the bottom segment

Multi-slot events (events that span more than one time slot) are rendered as a stack of slice elements. The `content` hook fires only on the **first** slice. Content placed there can grow downward through the event using `height: 100%`, but it is clipped at the bottom of the first row.

To render content pinned to the **bottom of the last slice** — such as a status badge, category chip, or summary — use `badgeTray`. It fires on the final slice of every multi-slot event and its return value is placed directly inside the event button.

Include the `event-scheduler-event__badge-tray` wrapper class in the returned element to get the built-in absolute bottom positioning:

```ts
grid.eventScheduler = {
  view: 'week',
  weekStartDate: '2026-06-08',
  customization: {
    events: {
      content: (h, context) =>
        h('span', { class: 'my-event' }, [
          h('strong', { class: 'my-event__title' }, context.event.title),
          h('span', { class: 'my-event__time' }, `${context.start.slice(11, 16)}-${context.end.slice(11, 16)}`),
          // For single-slot events first === last, so this renders inline
          context.last
            ? h('span', { class: 'my-event__badge' }, String(context.event.status ?? ''))
            : null,
        ]),
      badgeTray: (h, context) => {
        // Fires on the last slice of multi-slot events only.
        // Wrapping with event-scheduler-event__badge-tray pins it to the bottom.
        const status = String(context.event.status ?? '');
        if (!status) return null;
        return h('span', { class: 'event-scheduler-event__badge-tray' }, [
          h('span', { class: 'my-event__badge' }, status),
        ]);
      },
    },
  },
};
```

The pattern above handles both cases consistently:

- **Single-slot event** (`first === last === true`): the `content` hook renders the badge inline via the `context.last` guard.
- **Multi-slot event**: `content` renders on the first slice without the badge; `badgeTray` renders it pinned to the bottom of the last slice.

`badgeTray` does not fire for single-slot events or for the last slice of an event that continues into the next day (`isContinuedToNextDay`). When `badgeTray` returns `null` or `undefined`, the default badge tray — which displays type, category, and required-role chips — is rendered instead.
