Event Rendering
Event branding
Section titled “Event branding”Event blocks support a lightweight styling layer before you use the grouped customization.events API.
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
Section titled “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. |
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, the first real projected segment, the ordered segments represented by the bar, aggregate start, end, and duration, plus day, 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.
The legacy first, last, and sliceSlot fields remain available for source compatibility but are deprecated. Because hooks now run once per continuous bar, first and last are always true, while sliceSlot is the primary segment’s startSlot. New code should use segment.startSlot, segments, and the aggregate range fields instead.
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.
When customization.events.content is configured, it is the authoritative content hook. Returning null or undefined restores the built-in event content rather than invoking eventContentTemplate or legacy eventTemplate. Without the grouped hook, Scheduler tries eventContentTemplate, then eventTemplate, and finally the built-in content.
| 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. |
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 at the bottom of a bar
Section titled “Rendering content at the bottom of a bar”Each event assignment is rendered as one continuous visual bar. In vertical views the bar is anchored from the day’s first slot cell; in resource timeline it is attached to the resource row. Event customization hooks therefore run once per visible bar rather than once per grid cell.
Use context.segment for the first real projected segment and context.segments when you need every day segment represented by a multi-day timeline bar. The aggregate context.start, context.end, and context.duration fields describe the complete visible bar.
To render content pinned to the bottom of a multi-slot bar — such as a status badge, category chip, or summary — use badgeTray. 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:
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)}`), h('span', { class: 'my-event__badge' }, String(context.event.status ?? '')), ]), badgeTray: (h, context) => { // Runs once for a multi-slot bar whose visible end is in this view. 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), ]); }, }, },};badgeTray does not fire for single-slot bars or for a bar whose visible end continues beyond the current view (isContinuedToNextDay). When badgeTray returns null or undefined, the default badge tray — which displays type, category, and required-role chips — is rendered instead.