General
Event Calendar
Usage#
import { WdsEventCalendarComponent } from '@wds/angular/event-calendar';wds-slot-select and wds-event-click and open your own dialog. Load the events of the visible period from wds-range-change. With resources, the timeline-day, timeline-week and timeline-month views show a row per resource with time running across. Put your own content in an event or a resource heading with an element in the event-<id> or resource-<id> slot. Ready-made screens (a shift roster, a clinic schedule, a booking board) are in Event Calendar blocks.
Examples#
Default#
events is a property (in HTML set it from a script). A date (2026-10-05) is an all-day event with an inclusive end; a date-time is a timed event with an exclusive end. The view switcher shows views; drag an event to move it, drag its edge to resize it. The calendar updates events itself and fires wds-event-change with the new list. recurrence repeats an event (an RRULE such as FREQ=WEEKLY;BYDAY=MO,TU,WE,TH,FR): moving one occurrence moves the series.
Last change: none
import { Component, signal } from '@angular/core';
import { WdsEventCalendarComponent } from '@wds/angular/event-calendar';
import type { WdsEventCalendarEventChangeDetail } from '@wds/core/event-calendar';
import { EVENTS } from './calendar-data';
@Component({
selector: 'app-event-calendar-default-demo',
host: { class: 'block w-full' },
imports: [WdsEventCalendarComponent],
template: `
<div class="flex w-full flex-col gap-3">
<wds-event-calendar
class="w-full"
[events]="events()"
(wdsEventClick)="last.set('opened ' + $event.event.title)"
(wdsEventChange)="onChange($event)"
/>
<p class="m-0 text-sm text-muted-foreground">Last change: {{ last() }}</p>
</div>
`,
})
export class EventCalendarDefaultDemo {
protected readonly events = signal(EVENTS);
protected readonly last = signal('none');
// The calendar updates its events itself; keep your copy in sync with detail.events.
protected onChange({ event, events }: WdsEventCalendarEventChangeDetail): void {
this.events.set(events);
this.last.set(`${event.title}: ${event.start} → ${event.end}`);
}
}import type { WdsCalendarEvent, WdsCalendarResource } from '@wds/core/event-calendar';
/** Event colors. */
export const COLORS = {
blue: 'oklch(0.62 0.17 250)',
green: 'oklch(0.65 0.15 150)',
orange: 'oklch(0.72 0.16 60)',
purple: 'oklch(0.6 0.17 300)',
red: 'oklch(0.63 0.2 25)',
};
/** An ISO date `offset` days from today, or a date-time with `time` (`HH:MM`). */
export function at(offset: number, time?: string): string {
const d = new Date();
d.setDate(d.getDate() + offset);
const date = [d.getFullYear(), d.getMonth() + 1, d.getDate()].map((n, i) => String(n).padStart(i ? 2 : 4, '0')).join('-');
return time ? `${date}T${time}` : date;
}
/** Events around today, so every view has something to show. */
export const EVENTS: WdsCalendarEvent[] = [
{ id: 'standup', title: 'Team standup', start: at(-7, '09:00'), end: at(-7, '09:15'), recurrence: 'FREQ=WEEKLY;BYDAY=MO,TU,WE,TH,FR', color: COLORS.blue },
{ id: 'review', title: 'Design review', start: at(0, '11:00'), end: at(0, '12:30'), description: 'Room A', color: COLORS.purple },
{ id: 'lunch', title: 'Lunch with Ana', start: at(0, '12:00'), end: at(0, '13:00'), color: COLORS.green },
{ id: 'offsite', title: 'Team offsite', start: at(1), end: at(2), color: COLORS.orange },
{ id: 'release', title: 'Release 2.4', start: at(-1, '15:00'), end: at(-1, '16:00'), color: COLORS.red },
{ id: 'planning', title: 'Quarterly planning', start: at(2, '13:00'), end: at(2, '16:00'), description: 'Board room', color: COLORS.purple },
{ id: 'dentist', title: 'Dentist', start: at(3, '08:00'), end: at(3, '09:00'), color: COLORS.green },
{ id: 'sprint', title: 'Sprint 14', start: at(4), end: at(13), color: COLORS.blue },
{ id: 'interview', title: 'Interview: frontend', start: at(-2, '14:00'), end: at(-2, '15:00'), color: COLORS.orange },
];
/** Rooms for the resource view. */
export const ROOMS: WdsCalendarResource[] = [
{ id: 'a', title: 'Room A', color: COLORS.blue },
{ id: 'b', title: 'Room B', color: COLORS.green },
{ id: 'c', title: 'Board room', color: COLORS.purple },
];
/** Bookings of the rooms today. */
export const BOOKINGS: WdsCalendarEvent[] = [
{ id: 'b1', title: 'Sprint review', start: at(0, '09:00'), end: at(0, '10:30'), resourceId: 'a' },
{ id: 'b2', title: 'Hiring sync', start: at(0, '10:00'), end: at(0, '11:00'), resourceId: 'b' },
{ id: 'b3', title: 'Customer call', start: at(0, '13:00'), end: at(0, '14:00'), resourceId: 'a' },
{ id: 'b4', title: 'Board meeting', start: at(0, '14:00'), end: at(0, '17:00'), resourceId: 'c' },
{ id: 'b5', title: 'Workshop', start: at(0, '12:00'), end: at(0, '15:30'), resourceId: 'b' },
];Creating and editing events#
drag-create, drawing a range on empty time (or clicking it) fires wds-slot-select, and a click on an event fires wds-event-click; here both open a Dialog built from Field, Input and Select. Save adds or replaces the event in events.
import { Component, signal, viewChild } from '@angular/core';
import { WdsButtonComponent } from '@wds/angular/button';
import { WdsDialogComponent } from '@wds/angular/dialog';
import { WdsEventCalendarComponent } from '@wds/angular/event-calendar';
import { WDS_FIELD } from '@wds/angular/field';
import { WdsInputComponent } from '@wds/angular/input';
import { WDS_SELECT } from '@wds/angular/select';
import type { WdsCalendarEvent, WdsEventCalendarSlotSelectDetail } from '@wds/core/event-calendar';
import { COLORS, EVENTS } from './calendar-data';
type ColorName = keyof typeof COLORS;
@Component({
selector: 'app-event-calendar-create-edit-demo',
host: { class: 'block w-full' },
imports: [WdsEventCalendarComponent, WdsDialogComponent, WdsButtonComponent, WdsInputComponent, ...WDS_FIELD, ...WDS_SELECT],
template: `
<wds-event-calendar
class="w-full"
drag-create
[events]="events()"
(wdsSlotSelect)="create($event)"
(wdsEventClick)="edit($event.event)"
(wdsEventChange)="events.set($event.events)"
(wdsEventRemove)="events.set($event.events)"
/>
<!-- The calendar only shows events: this dialog creates and edits them. -->
<wds-dialog style="--wds-dialog-width: 26rem" [heading]="isNew() ? 'New event' : 'Edit event'" [description]="when()">
<form id="event-form" class="flex flex-col gap-4" (submit)="save($event)">
<wds-field>
<wds-field-label>Title</wds-field-label>
<wds-input required autofocus [value]="title()" (wdsInput)="title.set($event.value)" />
</wds-field>
<wds-field>
<wds-field-label>Color</wds-field-label>
<wds-select [value]="color()" (wdsChange)="color.set($any($event.value))">
@for (name of colorNames; track name) {
<wds-select-item [value]="name">{{ name[0].toUpperCase() + name.slice(1) }}</wds-select-item>
}
</wds-select>
</wds-field>
</form>
@if (!isNew()) {
<wds-button slot="footer" variant="destructive" class="me-auto" (click)="remove()">Delete</wds-button>
}
<wds-button slot="footer" variant="outline" (click)="dialog().close()">Cancel</wds-button>
<wds-button slot="footer" (click)="save($event)">Save</wds-button>
</wds-dialog>
`,
})
export class EventCalendarCreateEditDemo {
protected readonly dialog = viewChild.required(WdsDialogComponent);
protected readonly events = signal<WdsCalendarEvent[]>(EVENTS);
protected readonly colorNames = Object.keys(COLORS) as ColorName[];
/** The event being edited, or a new one from the drawn range. */
private draft: WdsCalendarEvent | null = null;
protected readonly isNew = signal(true);
protected readonly when = signal('');
protected readonly title = signal('');
protected readonly color = signal<ColorName>('blue');
protected create({ start, end }: WdsEventCalendarSlotSelectDetail): void {
this.open({ id: crypto.randomUUID(), title: '', start, end }, true);
}
protected edit(event: WdsCalendarEvent): void {
this.open(event, false);
}
private open(event: WdsCalendarEvent, isNew: boolean): void {
this.draft = event;
this.isNew.set(isNew);
this.when.set(`${event.start.replace('T', ' ')} – ${(event.end ?? event.start).replace('T', ' ')}`);
this.title.set(event.title);
this.color.set(this.colorNames.find((name) => COLORS[name] === event.color) ?? 'blue');
this.dialog().show();
}
protected save(e: Event): void {
e.preventDefault();
const event: WdsCalendarEvent = { ...this.draft!, title: this.title().trim() || 'Untitled', color: COLORS[this.color()] };
this.events.update((list) => (this.isNew() ? [...list, event] : list.map((x) => (x.id === event.id ? event : x))));
this.dialog().close();
}
protected remove(): void {
this.events.update((list) => list.filter((x) => x.id !== this.draft!.id));
this.dialog().close();
}
}import type { WdsCalendarEvent, WdsCalendarResource } from '@wds/core/event-calendar';
/** Event colors. */
export const COLORS = {
blue: 'oklch(0.62 0.17 250)',
green: 'oklch(0.65 0.15 150)',
orange: 'oklch(0.72 0.16 60)',
purple: 'oklch(0.6 0.17 300)',
red: 'oklch(0.63 0.2 25)',
};
/** An ISO date `offset` days from today, or a date-time with `time` (`HH:MM`). */
export function at(offset: number, time?: string): string {
const d = new Date();
d.setDate(d.getDate() + offset);
const date = [d.getFullYear(), d.getMonth() + 1, d.getDate()].map((n, i) => String(n).padStart(i ? 2 : 4, '0')).join('-');
return time ? `${date}T${time}` : date;
}
/** Events around today, so every view has something to show. */
export const EVENTS: WdsCalendarEvent[] = [
{ id: 'standup', title: 'Team standup', start: at(-7, '09:00'), end: at(-7, '09:15'), recurrence: 'FREQ=WEEKLY;BYDAY=MO,TU,WE,TH,FR', color: COLORS.blue },
{ id: 'review', title: 'Design review', start: at(0, '11:00'), end: at(0, '12:30'), description: 'Room A', color: COLORS.purple },
{ id: 'lunch', title: 'Lunch with Ana', start: at(0, '12:00'), end: at(0, '13:00'), color: COLORS.green },
{ id: 'offsite', title: 'Team offsite', start: at(1), end: at(2), color: COLORS.orange },
{ id: 'release', title: 'Release 2.4', start: at(-1, '15:00'), end: at(-1, '16:00'), color: COLORS.red },
{ id: 'planning', title: 'Quarterly planning', start: at(2, '13:00'), end: at(2, '16:00'), description: 'Board room', color: COLORS.purple },
{ id: 'dentist', title: 'Dentist', start: at(3, '08:00'), end: at(3, '09:00'), color: COLORS.green },
{ id: 'sprint', title: 'Sprint 14', start: at(4), end: at(13), color: COLORS.blue },
{ id: 'interview', title: 'Interview: frontend', start: at(-2, '14:00'), end: at(-2, '15:00'), color: COLORS.orange },
];
/** Rooms for the resource view. */
export const ROOMS: WdsCalendarResource[] = [
{ id: 'a', title: 'Room A', color: COLORS.blue },
{ id: 'b', title: 'Room B', color: COLORS.green },
{ id: 'c', title: 'Board room', color: COLORS.purple },
];
/** Bookings of the rooms today. */
export const BOOKINGS: WdsCalendarEvent[] = [
{ id: 'b1', title: 'Sprint review', start: at(0, '09:00'), end: at(0, '10:30'), resourceId: 'a' },
{ id: 'b2', title: 'Hiring sync', start: at(0, '10:00'), end: at(0, '11:00'), resourceId: 'b' },
{ id: 'b3', title: 'Customer call', start: at(0, '13:00'), end: at(0, '14:00'), resourceId: 'a' },
{ id: 'b4', title: 'Board meeting', start: at(0, '14:00'), end: at(0, '17:00'), resourceId: 'c' },
{ id: 'b5', title: 'Workshop', start: at(0, '12:00'), end: at(0, '15:30'), resourceId: 'b' },
];Month#
view="month" shows the weeks of the month. Events that do not fit in max-events rows are behind a “+N more” button that opens a Popover with the whole day. week-numbers adds ISO week numbers; hide-weekends hides Saturdays and Sundays.
import { Component } from '@angular/core';
import { WdsEventCalendarComponent } from '@wds/angular/event-calendar';
import { EVENTS } from './calendar-data';
@Component({
selector: 'app-event-calendar-month-demo',
host: { class: 'block w-full' },
imports: [WdsEventCalendarComponent],
template: `<wds-event-calendar class="w-full" view="month" views="month agenda" week-numbers max-events="2" [events]="events" />`,
})
export class EventCalendarMonthDemo {
protected readonly events = EVENTS;
}import type { WdsCalendarEvent, WdsCalendarResource } from '@wds/core/event-calendar';
/** Event colors. */
export const COLORS = {
blue: 'oklch(0.62 0.17 250)',
green: 'oklch(0.65 0.15 150)',
orange: 'oklch(0.72 0.16 60)',
purple: 'oklch(0.6 0.17 300)',
red: 'oklch(0.63 0.2 25)',
};
/** An ISO date `offset` days from today, or a date-time with `time` (`HH:MM`). */
export function at(offset: number, time?: string): string {
const d = new Date();
d.setDate(d.getDate() + offset);
const date = [d.getFullYear(), d.getMonth() + 1, d.getDate()].map((n, i) => String(n).padStart(i ? 2 : 4, '0')).join('-');
return time ? `${date}T${time}` : date;
}
/** Events around today, so every view has something to show. */
export const EVENTS: WdsCalendarEvent[] = [
{ id: 'standup', title: 'Team standup', start: at(-7, '09:00'), end: at(-7, '09:15'), recurrence: 'FREQ=WEEKLY;BYDAY=MO,TU,WE,TH,FR', color: COLORS.blue },
{ id: 'review', title: 'Design review', start: at(0, '11:00'), end: at(0, '12:30'), description: 'Room A', color: COLORS.purple },
{ id: 'lunch', title: 'Lunch with Ana', start: at(0, '12:00'), end: at(0, '13:00'), color: COLORS.green },
{ id: 'offsite', title: 'Team offsite', start: at(1), end: at(2), color: COLORS.orange },
{ id: 'release', title: 'Release 2.4', start: at(-1, '15:00'), end: at(-1, '16:00'), color: COLORS.red },
{ id: 'planning', title: 'Quarterly planning', start: at(2, '13:00'), end: at(2, '16:00'), description: 'Board room', color: COLORS.purple },
{ id: 'dentist', title: 'Dentist', start: at(3, '08:00'), end: at(3, '09:00'), color: COLORS.green },
{ id: 'sprint', title: 'Sprint 14', start: at(4), end: at(13), color: COLORS.blue },
{ id: 'interview', title: 'Interview: frontend', start: at(-2, '14:00'), end: at(-2, '15:00'), color: COLORS.orange },
];
/** Rooms for the resource view. */
export const ROOMS: WdsCalendarResource[] = [
{ id: 'a', title: 'Room A', color: COLORS.blue },
{ id: 'b', title: 'Room B', color: COLORS.green },
{ id: 'c', title: 'Board room', color: COLORS.purple },
];
/** Bookings of the rooms today. */
export const BOOKINGS: WdsCalendarEvent[] = [
{ id: 'b1', title: 'Sprint review', start: at(0, '09:00'), end: at(0, '10:30'), resourceId: 'a' },
{ id: 'b2', title: 'Hiring sync', start: at(0, '10:00'), end: at(0, '11:00'), resourceId: 'b' },
{ id: 'b3', title: 'Customer call', start: at(0, '13:00'), end: at(0, '14:00'), resourceId: 'a' },
{ id: 'b4', title: 'Board meeting', start: at(0, '14:00'), end: at(0, '17:00'), resourceId: 'c' },
{ id: 'b5', title: 'Workshop', start: at(0, '12:00'), end: at(0, '15:30'), resourceId: 'b' },
];Resources#
resources (such as rooms or people) splits the day of the resource view into columns; resource-views splits other time-grid views too (here day and days). An event goes to the column of its resourceId and can be dragged to another one. day-start and day-end limit the hours, business-hours shades the rest, and days sets the length of the days view. work-week shows Monday to Friday.
import { Component, signal } from '@angular/core';
import { WdsEventCalendarComponent } from '@wds/angular/event-calendar';
import type { WdsCalendarEvent, WdsEventCalendarSlotSelectDetail } from '@wds/core/event-calendar';
import { BOOKINGS, ROOMS } from './calendar-data';
@Component({
selector: 'app-event-calendar-resources-demo',
host: { class: 'block w-full' },
imports: [WdsEventCalendarComponent],
template: `
<wds-event-calendar
class="w-full"
view="day"
views="day days"
days="2"
resource-views="day days"
day-start="8"
day-end="19"
business-hours="09:00-17:00"
drag-create
[resources]="rooms"
[events]="events()"
(wdsEventChange)="events.set($event.events)"
(wdsSlotSelect)="book($event)"
/>
`,
})
export class EventCalendarResourcesDemo {
// Each resource is a column; an event goes to the column of its resourceId and can be dragged to another.
protected readonly rooms = ROOMS;
protected readonly events = signal<WdsCalendarEvent[]>(BOOKINGS);
protected book({ start, end, resourceId }: WdsEventCalendarSlotSelectDetail): void {
this.events.update((list) => [...list, { id: crypto.randomUUID(), title: 'New booking', start, end, resourceId }]);
}
}import type { WdsCalendarEvent, WdsCalendarResource } from '@wds/core/event-calendar';
/** Event colors. */
export const COLORS = {
blue: 'oklch(0.62 0.17 250)',
green: 'oklch(0.65 0.15 150)',
orange: 'oklch(0.72 0.16 60)',
purple: 'oklch(0.6 0.17 300)',
red: 'oklch(0.63 0.2 25)',
};
/** An ISO date `offset` days from today, or a date-time with `time` (`HH:MM`). */
export function at(offset: number, time?: string): string {
const d = new Date();
d.setDate(d.getDate() + offset);
const date = [d.getFullYear(), d.getMonth() + 1, d.getDate()].map((n, i) => String(n).padStart(i ? 2 : 4, '0')).join('-');
return time ? `${date}T${time}` : date;
}
/** Events around today, so every view has something to show. */
export const EVENTS: WdsCalendarEvent[] = [
{ id: 'standup', title: 'Team standup', start: at(-7, '09:00'), end: at(-7, '09:15'), recurrence: 'FREQ=WEEKLY;BYDAY=MO,TU,WE,TH,FR', color: COLORS.blue },
{ id: 'review', title: 'Design review', start: at(0, '11:00'), end: at(0, '12:30'), description: 'Room A', color: COLORS.purple },
{ id: 'lunch', title: 'Lunch with Ana', start: at(0, '12:00'), end: at(0, '13:00'), color: COLORS.green },
{ id: 'offsite', title: 'Team offsite', start: at(1), end: at(2), color: COLORS.orange },
{ id: 'release', title: 'Release 2.4', start: at(-1, '15:00'), end: at(-1, '16:00'), color: COLORS.red },
{ id: 'planning', title: 'Quarterly planning', start: at(2, '13:00'), end: at(2, '16:00'), description: 'Board room', color: COLORS.purple },
{ id: 'dentist', title: 'Dentist', start: at(3, '08:00'), end: at(3, '09:00'), color: COLORS.green },
{ id: 'sprint', title: 'Sprint 14', start: at(4), end: at(13), color: COLORS.blue },
{ id: 'interview', title: 'Interview: frontend', start: at(-2, '14:00'), end: at(-2, '15:00'), color: COLORS.orange },
];
/** Rooms for the resource view. */
export const ROOMS: WdsCalendarResource[] = [
{ id: 'a', title: 'Room A', color: COLORS.blue },
{ id: 'b', title: 'Room B', color: COLORS.green },
{ id: 'c', title: 'Board room', color: COLORS.purple },
];
/** Bookings of the rooms today. */
export const BOOKINGS: WdsCalendarEvent[] = [
{ id: 'b1', title: 'Sprint review', start: at(0, '09:00'), end: at(0, '10:30'), resourceId: 'a' },
{ id: 'b2', title: 'Hiring sync', start: at(0, '10:00'), end: at(0, '11:00'), resourceId: 'b' },
{ id: 'b3', title: 'Customer call', start: at(0, '13:00'), end: at(0, '14:00'), resourceId: 'a' },
{ id: 'b4', title: 'Board meeting', start: at(0, '14:00'), end: at(0, '17:00'), resourceId: 'c' },
{ id: 'b5', title: 'Workshop', start: at(0, '12:00'), end: at(0, '15:30'), resourceId: 'b' },
];Validation and context menu#
canDropEvent runs before a move or resize is applied: returning false rejects it (here: overlapping events and weekends), and a screen reader hears that the change is not allowed. An event with readOnly cannot be moved. eventActions adds a menu on right click, Shift+F10 or the Menu key; the chosen item comes in wds-event-action. snap sets the step of moves in minutes.
Drag an event onto another one or a weekend, or right click it.
import { Component, signal } from '@angular/core';
import { WdsEventCalendarComponent } from '@wds/angular/event-calendar';
import type { WdsCalendarEvent, WdsEventCalendarAction, WdsEventCalendarEventActionDetail, WdsEventCalendarEventChangeDetail } from '@wds/core/event-calendar';
import { EVENTS } from './calendar-data';
const minutes = (iso: string) => new Date(iso).getTime();
@Component({
selector: 'app-event-calendar-rules-demo',
host: { class: 'block w-full' },
imports: [WdsEventCalendarComponent],
template: `
<div class="flex w-full flex-col gap-3">
<wds-event-calendar
class="w-full"
view="week"
views="week day"
day-start="7"
day-end="20"
snap="30"
[events]="events()"
[canDropEvent]="canDrop"
[eventActions]="actions"
(wdsEventChange)="onChange($event)"
(wdsEventAction)="onAction($event)"
/>
<p class="m-0 text-sm text-muted-foreground">{{ message() }}</p>
</div>
`,
})
export class EventCalendarRulesDemo {
protected readonly events = signal<WdsCalendarEvent[]>(EVENTS.map((e) => (e.id === 'release' ? { ...e, readOnly: true, title: 'Release 2.4 (locked)' } : e)));
protected readonly message = signal('Drag an event onto another one or a weekend, or right click it.');
// Right click, Shift+F10 or the Menu key opens the menu of an event.
protected readonly actions: WdsEventCalendarAction[] = [
{ value: 'duplicate', label: 'Duplicate' },
{ separator: true },
{ value: 'delete', label: 'Delete', variant: 'destructive' },
];
// canDropEvent runs before a move or resize is applied: false rejects it (and the screen reader hears why).
protected readonly canDrop = ({ event }: { event: WdsCalendarEvent }): boolean => {
const day = new Date(event.start).getDay();
if (day === 0 || day === 6) return false;
return !this.events().some(
(other) =>
other.id !== event.id && !other.recurrence && !!other.end && other.start.includes('T') &&
minutes(other.start) < minutes(event.end!) && minutes(other.end) > minutes(event.start)
);
};
protected onChange({ event, events }: WdsEventCalendarEventChangeDetail): void {
this.events.set(events);
this.message.set(`${event.title} moved.`);
}
protected onAction({ action, event }: WdsEventCalendarEventActionDetail): void {
if (action === 'delete') this.events.update((list) => list.filter((x) => x.id !== event.id));
if (action === 'duplicate') this.events.update((list) => [...list, { ...event, id: crypto.randomUUID(), title: `${event.title} (copy)` }]);
this.message.set(`${action}: ${event.title}`);
}
}import type { WdsCalendarEvent, WdsCalendarResource } from '@wds/core/event-calendar';
/** Event colors. */
export const COLORS = {
blue: 'oklch(0.62 0.17 250)',
green: 'oklch(0.65 0.15 150)',
orange: 'oklch(0.72 0.16 60)',
purple: 'oklch(0.6 0.17 300)',
red: 'oklch(0.63 0.2 25)',
};
/** An ISO date `offset` days from today, or a date-time with `time` (`HH:MM`). */
export function at(offset: number, time?: string): string {
const d = new Date();
d.setDate(d.getDate() + offset);
const date = [d.getFullYear(), d.getMonth() + 1, d.getDate()].map((n, i) => String(n).padStart(i ? 2 : 4, '0')).join('-');
return time ? `${date}T${time}` : date;
}
/** Events around today, so every view has something to show. */
export const EVENTS: WdsCalendarEvent[] = [
{ id: 'standup', title: 'Team standup', start: at(-7, '09:00'), end: at(-7, '09:15'), recurrence: 'FREQ=WEEKLY;BYDAY=MO,TU,WE,TH,FR', color: COLORS.blue },
{ id: 'review', title: 'Design review', start: at(0, '11:00'), end: at(0, '12:30'), description: 'Room A', color: COLORS.purple },
{ id: 'lunch', title: 'Lunch with Ana', start: at(0, '12:00'), end: at(0, '13:00'), color: COLORS.green },
{ id: 'offsite', title: 'Team offsite', start: at(1), end: at(2), color: COLORS.orange },
{ id: 'release', title: 'Release 2.4', start: at(-1, '15:00'), end: at(-1, '16:00'), color: COLORS.red },
{ id: 'planning', title: 'Quarterly planning', start: at(2, '13:00'), end: at(2, '16:00'), description: 'Board room', color: COLORS.purple },
{ id: 'dentist', title: 'Dentist', start: at(3, '08:00'), end: at(3, '09:00'), color: COLORS.green },
{ id: 'sprint', title: 'Sprint 14', start: at(4), end: at(13), color: COLORS.blue },
{ id: 'interview', title: 'Interview: frontend', start: at(-2, '14:00'), end: at(-2, '15:00'), color: COLORS.orange },
];
/** Rooms for the resource view. */
export const ROOMS: WdsCalendarResource[] = [
{ id: 'a', title: 'Room A', color: COLORS.blue },
{ id: 'b', title: 'Room B', color: COLORS.green },
{ id: 'c', title: 'Board room', color: COLORS.purple },
];
/** Bookings of the rooms today. */
export const BOOKINGS: WdsCalendarEvent[] = [
{ id: 'b1', title: 'Sprint review', start: at(0, '09:00'), end: at(0, '10:30'), resourceId: 'a' },
{ id: 'b2', title: 'Hiring sync', start: at(0, '10:00'), end: at(0, '11:00'), resourceId: 'b' },
{ id: 'b3', title: 'Customer call', start: at(0, '13:00'), end: at(0, '14:00'), resourceId: 'a' },
{ id: 'b4', title: 'Board meeting', start: at(0, '14:00'), end: at(0, '17:00'), resourceId: 'c' },
{ id: 'b5', title: 'Workshop', start: at(0, '12:00'), end: at(0, '15:30'), resourceId: 'b' },
];API References#
wds-event-calendar#
drag-create),
repeated with a rule, and split into columns per resource (such as rooms). The calendar only shows
events: it fires wds-event-click and wds-slot-select, so open your own dialog to create or edit one.
Attributes
| Attribute | Type | Default | Description |
|---|---|---|---|
| resource-views.resourceViews | WdsEventCalendarView[] | resource view (one day). Add day, days, week or work-week to split them too. | |
| view | WdsEventCalendarView | 'week' | |
| views | WdsEventCalendarView[] | ||
| date | string | '' | |
| days | number | 3 | days view. |
| agenda-days.agendaDays | number | 30 | agenda view, from date. |
| day-start.dayStart | number | 0 | |
| day-end.dayEnd | number | 24 | |
| business-hours.businessHours | string | '' | 09:00-17:00: the rest of the time grid is shaded. |
| scroll-time.scrollTime | string | '08:00' | 08:00); for today the current time wins. |
| hour-height.hourHeight | number | 48 | |
| snap | number | 15 | |
| max-events.maxEvents | number | 3 | |
| lane-height.laneHeight | number | 24 | event-<id> content). |
| event-variant.eventVariant | 'default' | 'soft' | 'default' | default (a color bar at the start) or soft (a tinted card with a tinted border all around). |
| hide-weekends.hideWeekends | boolean | false | timeline-week and timeline-month views (the work-week views always hide them). |
| week-numbers.weekNumbers | boolean | false | |
| readonly | boolean | false | |
| drag-create.dragCreate | boolean | false | wds-slot-select, such as to add an event there. |
| time-zone.timeZone | string | '' | Europe/Warsaw). Default: the browser's. |
| locale | string | '' | en-US). Defaults to the nearest lang attribute. |
| first-day.firstDay | number | undefined | ||
| hide-nav.hideNav | boolean | false | next(), prev(), today() and goTo()). |
| fill | boolean | false | --wds-event-calendar-height is then the minimum height of the scrolling grid. |
| loading | boolean | false | aria-busy (such as while events load). |
| empty-text.emptyText | string | 'No events.' | |
| label | string | 'Calendar' |
Properties
| Property | Description |
|---|---|
| eventsWdsCalendarEvent[] | WdsCalendarEvent[]). The calendar updates this array itself after a move, resize or delete. |
| resourcesWdsCalendarResource[] | WdsCalendarResource[]), such as rooms or people: columns of the time grid in resource-views and rows of the timeline; events go to the column or row of their resourceId. |
| eventActionsWdsEventCalendarAction[] | wds-event-action. |
| renderEvent(event: WdsCalendarEvent) => string | Node | undefined | wds-* element: it renders in shadow DOM) instead of the title. |
| canDropEvent(change: Omit<WdsEventCalendarEventChangeDetail, 'events'>) => boolean | undefined | false to reject it. |
| canSelectSlot(slot: WdsEventCalendarSlotSelectDetail) => boolean | undefined | drag-create: return false to reject it (the preview turns red). |
Events
| Event | Description |
|---|---|
| wds-range-changeCustomEvent<WdsEventCalendarRangeChangeDetail> | |
| wds-event-changeCustomEvent<WdsEventCalendarEventChangeDetail> | canDropEvent accepted it). |
| wds-event-removeCustomEvent<WdsEventCalendarEventRemoveDetail> | |
| wds-event-clickCustomEvent<WdsEventCalendarEventClickDetail> | |
| wds-slot-selectCustomEvent<WdsEventCalendarSlotSelectDetail> | drag-create), or Enter was pressed on a day or time. |
| wds-event-actionCustomEvent<WdsEventCalendarEventActionDetail> | eventActions) was chosen. |
Methods
| Method | Description |
|---|---|
| next() | |
| prev() | |
| today() | |
| goTo(date: string) |
Slots
| Slot | Description |
|---|---|
| toolbar | |
| empty | empty-text). |
| actions | |
| event-<id> | id instead of its title (page styles apply). An event shown more than once (a repeated or long event) gets it only in the first place; the others show the title. |
| resource-<id> | id instead of its title. |
CSS parts (::part)
| Part | Description |
|---|---|
| nav | |
| title | |
| views | |
| grid | role="grid"). |
| agenda | |
| body | |
| background-event | display: 'background'). |
CSS custom properties
| Property | Description |
|---|---|
| --wds-event-calendar-height | fill its minimum height. |
| --wds-event-calendar-hour-height | hour-height sets it). |
| --wds-event-calendar-event | --wds-chart-2). |
| --wds-event-calendar-now | --wds-destructive). |
| --wds-event-calendar-resource-width | |
| --wds-event-calendar-slot-width | timeline-day, a day in timeline-week (default 4rem / 8rem / 3rem in timeline-month). |
Accessibility#
Screen reader#
region named by label. The month and the time grid are a grid named by the period: the month has a columnheader per weekday and a gridcell per day (named by its date and the number of events); the time grid has a gridcell per day (or day and resource) named by the date and the time of the keyboard cursor. The timeline has a row per resource with a rowheader and one gridcell named by the resource and the cursor time; group headings are rowheaders too.
display: 'background') are decoration only (aria-hidden): describe holds in your own text if they matter.
Today, All day, +2 more, Previous period).
Keyboard support#
| Key | Function |
|---|---|
snap minutes. In the timeline: by snap minutes (timeline-day) or a day, and to the previous or next resource. On an event: to the previous or next event. | |
drag-create: fires wds-slot-select for that day or hour. On an event: fires wds-event-click. | |
snap minutes earlier or later in the time grid, a week in the month, or to the previous or next resource in the timeline. | |
snap minutes shorter or longer. | |
snap minutes (timeline-day) or a day. | |
wds-event-remove). | |
eventActions menu (also the Menu key). | |