Skip to content

Event Rendering

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.

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

ConfigPurpose
eventTemplateLegacy top-level event content renderer.
eventContentTemplateReplaces the default title, time, badge, and resource content block.
eventPropertiesAdds classes, styles, attributes, data fields, or event handlers to the event button.
eventTooltipTemplateOverrides the event title tooltip with custom details.
eventBadgeTemplateReplaces individual status, type, category, or required-role badges.
eventResizeHandleTemplateReplaces 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.

HookWhat it customizes
classNameAdds one or more classes to the event button.
styleAdds inline styles or CSS custom properties to the event button.
propertiesAdds attributes, classes, styles, or data attributes to the event button.
contentReplaces the default title, time, badge, and resource content block.
continuationLabelsReplaces previous/next-day continuation labels.
badgeTrayReplaces the tray that holds badges for multi-slot events.
badgeReplaces individual status, type, category, or required-role badges.
resizeHandleReplaces the visual content inside start/end resize handles.
tooltipOverrides the event title tooltip.
ariaLabelOverrides 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.

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.