ComponentUpdated in v15

Event calendar

Month, week and day views of events the user can move and resize.

Installation

Import the class, and @use the entry point in your global styles. Nothing warns you when the @use is missing.

import { Component } from '@angular/core';
import { WrEventCalendar } from 'ngwr/event-calendar';

@Component({ imports: [WrEventCalendar] })
export class MyComponent {}

Date adapter

Uses LOCALE_ID with no provider; for another date library, pass a WrDateAdapter subclass (Configuration).

// Only for another date library:
import { provideWr } from 'ngwr';
import { withDates } from 'ngwr/date';

bootstrapApplication(App, { providers: [provideWr(withDates({ adapter: AppDateAdapter }))] });

Views

view and date are two-way. To use your own toolbar, bind them and set hideHeader.

<wr-event-calendar [events]="events()" [(view)]="view" [(date)]="anchor" />

November 2026

Sun
Mon
Tue
Wed
Thu
Fri
Sat
November 1, 2026
November 2, 2026
November 3, 2026
November 4, 2026
November 5, 2026
November 6, 2026
November 7, 2026
November 8, 2026
November 9, 2026
November 10, 2026
November 11, 2026
November 12, 2026
November 13, 2026
November 14, 2026
November 15, 2026
November 16, 2026
November 17, 2026
November 18, 2026
November 19, 2026
November 20, 2026
November 21, 2026
November 22, 2026
November 23, 2026
November 24, 2026
November 25, 2026
November 26, 2026
November 27, 2026
November 28, 2026
November 29, 2026
November 30, 2026
December 1, 2026
December 2, 2026
December 3, 2026
December 4, 2026
December 5, 2026
December 6, 2026
December 7, 2026
December 8, 2026
December 9, 2026
December 10, 2026
December 11, 2026
December 12, 2026

Events

end is exclusive. color takes any intent; editable: false locks one event.

protected readonly events = signal<readonly WrCalendarEvent[]>([
  { id: 1, title: 'Design review', start: at(0, 10), end: at(0, 11, 30), color: 'primary' },
  { id: 5, title: 'Conference', start: at(2, 0), end: at(5, 0), allDay: true, color: 'info' },
  { id: 7, title: 'Frozen — no deploys', start: at(6, 0), end: at(7, 0), allDay: true, editable: false },
]);

Drag to move and resize

Set editable. The calendar never changes events: apply eventChange to your own state, or ignore it to cancel the drag.

<wr-event-calendar
  editable
  [events]="events()"
  [(view)]="view"
  [(date)]="anchor"
  (eventChange)="apply($event)"
/>
protected apply(change: WrCalendarEventChange): void {
  this.events.update(events =>
    events.map(event =>
      event.id === change.event.id ? { ...event, start: change.start, end: change.end } : event
    )
  );
}

Nov 1, 2026 – Nov 7, 2026

Sun1
Mon2
Tue3
Wed4
Thu5
Fri6
Sat7
08:00 AM
08:00 AM — November 1, 2026
08:00 AM — November 2, 2026
08:00 AM — November 3, 2026
08:00 AM — November 4, 2026
08:00 AM — November 5, 2026
08:00 AM — November 6, 2026
08:00 AM — November 7, 2026
08:30 AM — November 1, 2026
08:30 AM — November 2, 2026
08:30 AM — November 3, 2026
08:30 AM — November 4, 2026
08:30 AM — November 5, 2026
08:30 AM — November 6, 2026
08:30 AM — November 7, 2026
09:00 AM
09:00 AM — November 1, 2026
09:00 AM — November 2, 2026
09:00 AM — November 3, 2026
09:00 AM — November 4, 2026
09:00 AM — November 5, 2026
09:00 AM — November 6, 2026
09:00 AM — November 7, 2026
09:30 AM — November 1, 2026
09:30 AM — November 2, 2026
09:30 AM — November 3, 2026
09:30 AM — November 4, 2026
09:30 AM — November 5, 2026
09:30 AM — November 6, 2026
09:30 AM — November 7, 2026
10:00 AM
10:00 AM — November 1, 2026
10:00 AM — November 2, 2026
10:00 AM — November 3, 2026
10:00 AM — November 4, 2026
10:00 AM — November 5, 2026
10:00 AM — November 6, 2026
10:00 AM — November 7, 2026
10:30 AM — November 1, 2026
10:30 AM — November 2, 2026
10:30 AM — November 3, 2026
10:30 AM — November 4, 2026
10:30 AM — November 5, 2026
10:30 AM — November 6, 2026
10:30 AM — November 7, 2026
11:00 AM
11:00 AM — November 1, 2026
11:00 AM — November 2, 2026
11:00 AM — November 3, 2026
11:00 AM — November 4, 2026
11:00 AM — November 5, 2026
11:00 AM — November 6, 2026
11:00 AM — November 7, 2026
11:30 AM — November 1, 2026
11:30 AM — November 2, 2026
11:30 AM — November 3, 2026
11:30 AM — November 4, 2026
11:30 AM — November 5, 2026
11:30 AM — November 6, 2026
11:30 AM — November 7, 2026
12:00 PM
12:00 PM — November 1, 2026
12:00 PM — November 2, 2026
12:00 PM — November 3, 2026
12:00 PM — November 4, 2026
12:00 PM — November 5, 2026
12:00 PM — November 6, 2026
12:00 PM — November 7, 2026
12:30 PM — November 1, 2026
12:30 PM — November 2, 2026
12:30 PM — November 3, 2026
12:30 PM — November 4, 2026
12:30 PM — November 5, 2026
12:30 PM — November 6, 2026
12:30 PM — November 7, 2026
01:00 PM
01:00 PM — November 1, 2026
01:00 PM — November 2, 2026
01:00 PM — November 3, 2026
01:00 PM — November 4, 2026
01:00 PM — November 5, 2026
01:00 PM — November 6, 2026
01:00 PM — November 7, 2026
01:30 PM — November 1, 2026
01:30 PM — November 2, 2026
01:30 PM — November 3, 2026
01:30 PM — November 4, 2026
01:30 PM — November 5, 2026
01:30 PM — November 6, 2026
01:30 PM — November 7, 2026
02:00 PM
02:00 PM — November 1, 2026
02:00 PM — November 2, 2026
02:00 PM — November 3, 2026
02:00 PM — November 4, 2026
02:00 PM — November 5, 2026
02:00 PM — November 6, 2026
02:00 PM — November 7, 2026
02:30 PM — November 1, 2026
02:30 PM — November 2, 2026
02:30 PM — November 3, 2026
02:30 PM — November 4, 2026
02:30 PM — November 5, 2026
02:30 PM — November 6, 2026
02:30 PM — November 7, 2026
03:00 PM
03:00 PM — November 1, 2026
03:00 PM — November 2, 2026
03:00 PM — November 3, 2026
03:00 PM — November 4, 2026
03:00 PM — November 5, 2026
03:00 PM — November 6, 2026
03:00 PM — November 7, 2026
03:30 PM — November 1, 2026
03:30 PM — November 2, 2026
03:30 PM — November 3, 2026
03:30 PM — November 4, 2026
03:30 PM — November 5, 2026
03:30 PM — November 6, 2026
03:30 PM — November 7, 2026
04:00 PM
04:00 PM — November 1, 2026
04:00 PM — November 2, 2026
04:00 PM — November 3, 2026
04:00 PM — November 4, 2026
04:00 PM — November 5, 2026
04:00 PM — November 6, 2026
04:00 PM — November 7, 2026
04:30 PM — November 1, 2026
04:30 PM — November 2, 2026
04:30 PM — November 3, 2026
04:30 PM — November 4, 2026
04:30 PM — November 5, 2026
04:30 PM — November 6, 2026
04:30 PM — November 7, 2026
05:00 PM
05:00 PM — November 1, 2026
05:00 PM — November 2, 2026
05:00 PM — November 3, 2026
05:00 PM — November 4, 2026
05:00 PM — November 5, 2026
05:00 PM — November 6, 2026
05:00 PM — November 7, 2026
05:30 PM — November 1, 2026
05:30 PM — November 2, 2026
05:30 PM — November 3, 2026
05:30 PM — November 4, 2026
05:30 PM — November 5, 2026
05:30 PM — November 6, 2026
05:30 PM — November 7, 2026
06:00 PM
06:00 PM — November 1, 2026
06:00 PM — November 2, 2026
06:00 PM — November 3, 2026
06:00 PM — November 4, 2026
06:00 PM — November 5, 2026
06:00 PM — November 6, 2026
06:00 PM — November 7, 2026
06:30 PM — November 1, 2026
06:30 PM — November 2, 2026
06:30 PM — November 3, 2026
06:30 PM — November 4, 2026
06:30 PM — November 5, 2026
06:30 PM — November 6, 2026
06:30 PM — November 7, 2026

Last output: —

The time grid

dayStartHour and dayEndHour trim the visible band. slotMinutes sets both the row height and the drag snap.

<wr-event-calendar
  view="week"
  [slotMinutes]="15"
  [dayStartHour]="9"
  [dayEndHour]="14"
  [events]="events()"
/>

Nov 1, 2026 – Nov 7, 2026

Sun1
Mon2
Tue3
Wed4
Thu5
Fri6
Sat7
09:00 AM
09:00 AM — November 1, 2026
09:00 AM — November 2, 2026
09:00 AM — November 3, 2026
09:00 AM — November 4, 2026
09:00 AM — November 5, 2026
09:00 AM — November 6, 2026
09:00 AM — November 7, 2026
09:15 AM — November 1, 2026
09:15 AM — November 2, 2026
09:15 AM — November 3, 2026
09:15 AM — November 4, 2026
09:15 AM — November 5, 2026
09:15 AM — November 6, 2026
09:15 AM — November 7, 2026
09:30 AM — November 1, 2026
09:30 AM — November 2, 2026
09:30 AM — November 3, 2026
09:30 AM — November 4, 2026
09:30 AM — November 5, 2026
09:30 AM — November 6, 2026
09:30 AM — November 7, 2026
09:45 AM — November 1, 2026
09:45 AM — November 2, 2026
09:45 AM — November 3, 2026
09:45 AM — November 4, 2026
09:45 AM — November 5, 2026
09:45 AM — November 6, 2026
09:45 AM — November 7, 2026
10:00 AM
10:00 AM — November 1, 2026
10:00 AM — November 2, 2026
10:00 AM — November 3, 2026
10:00 AM — November 4, 2026
10:00 AM — November 5, 2026
10:00 AM — November 6, 2026
10:00 AM — November 7, 2026
10:15 AM — November 1, 2026
10:15 AM — November 2, 2026
10:15 AM — November 3, 2026
10:15 AM — November 4, 2026
10:15 AM — November 5, 2026
10:15 AM — November 6, 2026
10:15 AM — November 7, 2026
10:30 AM — November 1, 2026
10:30 AM — November 2, 2026
10:30 AM — November 3, 2026
10:30 AM — November 4, 2026
10:30 AM — November 5, 2026
10:30 AM — November 6, 2026
10:30 AM — November 7, 2026
10:45 AM — November 1, 2026
10:45 AM — November 2, 2026
10:45 AM — November 3, 2026
10:45 AM — November 4, 2026
10:45 AM — November 5, 2026
10:45 AM — November 6, 2026
10:45 AM — November 7, 2026
11:00 AM
11:00 AM — November 1, 2026
11:00 AM — November 2, 2026
11:00 AM — November 3, 2026
11:00 AM — November 4, 2026
11:00 AM — November 5, 2026
11:00 AM — November 6, 2026
11:00 AM — November 7, 2026
11:15 AM — November 1, 2026
11:15 AM — November 2, 2026
11:15 AM — November 3, 2026
11:15 AM — November 4, 2026
11:15 AM — November 5, 2026
11:15 AM — November 6, 2026
11:15 AM — November 7, 2026
11:30 AM — November 1, 2026
11:30 AM — November 2, 2026
11:30 AM — November 3, 2026
11:30 AM — November 4, 2026
11:30 AM — November 5, 2026
11:30 AM — November 6, 2026
11:30 AM — November 7, 2026
11:45 AM — November 1, 2026
11:45 AM — November 2, 2026
11:45 AM — November 3, 2026
11:45 AM — November 4, 2026
11:45 AM — November 5, 2026
11:45 AM — November 6, 2026
11:45 AM — November 7, 2026
12:00 PM
12:00 PM — November 1, 2026
12:00 PM — November 2, 2026
12:00 PM — November 3, 2026
12:00 PM — November 4, 2026
12:00 PM — November 5, 2026
12:00 PM — November 6, 2026
12:00 PM — November 7, 2026
12:15 PM — November 1, 2026
12:15 PM — November 2, 2026
12:15 PM — November 3, 2026
12:15 PM — November 4, 2026
12:15 PM — November 5, 2026
12:15 PM — November 6, 2026
12:15 PM — November 7, 2026
12:30 PM — November 1, 2026
12:30 PM — November 2, 2026
12:30 PM — November 3, 2026
12:30 PM — November 4, 2026
12:30 PM — November 5, 2026
12:30 PM — November 6, 2026
12:30 PM — November 7, 2026
12:45 PM — November 1, 2026
12:45 PM — November 2, 2026
12:45 PM — November 3, 2026
12:45 PM — November 4, 2026
12:45 PM — November 5, 2026
12:45 PM — November 6, 2026
12:45 PM — November 7, 2026
01:00 PM
01:00 PM — November 1, 2026
01:00 PM — November 2, 2026
01:00 PM — November 3, 2026
01:00 PM — November 4, 2026
01:00 PM — November 5, 2026
01:00 PM — November 6, 2026
01:00 PM — November 7, 2026
01:15 PM — November 1, 2026
01:15 PM — November 2, 2026
01:15 PM — November 3, 2026
01:15 PM — November 4, 2026
01:15 PM — November 5, 2026
01:15 PM — November 6, 2026
01:15 PM — November 7, 2026
01:30 PM — November 1, 2026
01:30 PM — November 2, 2026
01:30 PM — November 3, 2026
01:30 PM — November 4, 2026
01:30 PM — November 5, 2026
01:30 PM — November 6, 2026
01:30 PM — November 7, 2026
01:45 PM — November 1, 2026
01:45 PM — November 2, 2026
01:45 PM — November 3, 2026
01:45 PM — November 4, 2026
01:45 PM — November 5, 2026
01:45 PM — November 6, 2026
01:45 PM — November 7, 2026

Creating from empty space

A click on an empty slot, or Enter on an empty cell, emits slotClick.

<wr-event-calendar [events]="events()" (slotClick)="compose($event)" />

Custom chip contents

<ng-template wrCalendarEvent> replaces a chip's content. Add WrCalendarEventTemplate to imports, or the template is ignored.

<!-- imports: [WrEventCalendar, WrCalendarEventTemplate] -->
<wr-event-calendar [events]="events()">
  <ng-template wrCalendarEvent let-event>
    <strong>{{ event.title }}</strong>
    <small>{{ event.data.room }}</small>
  </ng-template>
</wr-event-calendar>

Keyboard

The grid is one tab stop; each chip is its own.

On a cell
KeyDoes
ArrowLeftArrowRightPrevious or next day.
ArrowUpArrowDownOne week in month view, one slot in week and day view.
HomeEndFirst / last day of the current week row.
EnterSpaceEmits slotClick on an empty cell, or focuses the first chip.
On a chip
KeyDoes
EscapeBack to the cell the chip lives in.
Alt + arrowsMoves the event. Emits eventChange with kind: 'move'.
Shift + Alt + arrowsMoves the end only. Emits eventChange with kind: 'resize'.

Chip keys need editable and skip events with editable: false.

API

NameDescriptionTypeDefault
eventsEverything to render. Never mutated — see eventChange.readonly WrCalendarEvent[][]
viewWhich span is shown. Two-way bindable.WrCalendarView'month'
dateAny date inside the shown span. Two-way bindable.Datenew Date()
viewsWhich buttons the view switcher offers. Empty hides it.readonly WrCalendarView[]['month', 'week', 'day']
editableAllow dragging chips to move and resize them.booleanfalse
slotMinutesMinutes per row in the time views — also the drag snap.number30
dayStartHourFirst hour the time views show.number0
dayEndHourFirst hour they do NOT show, exclusive.number24
maxLanesLanes a month cell shows before collapsing the rest into “+N more”.number3
hideHeaderHide the built-in header — supply your own navigation instead.booleanfalse
(eventClick)A chip was activated.WrCalendarEvent—
(slotClick)Empty space was activated — the slot the user pointed at.WrCalendarSlot—
(eventChange)A drag or an Alt + arrow finished. Apply it yourself; nothing moves until you do.WrCalendarEventChange—
ariaLabelAccessible name of the grid; overridable for a page with several.string | nullnull

CSS variables

Set these on the element itself, with a rule more specific than its Declared on selector. In a global stylesheet, put a class of yours on an ancestor it really has: <body> for an overlay panel, which renders outside your markup. In a component stylesheet the selector alone works, but only for an element in that component's own template. A value on a wrapper or :root is shadowed, because ngwr/event-calendar declares each default on its own element.

VariableDefaultDeclared on
--wr-event-calendar-band-height1.25rem.wr-event-calendar
--wr-event-calendar-cell-paddingvar(--wr-space-xs).wr-event-calendar__cell
--wr-event-calendar-chip-font-sizevar(--wr-text-xs).wr-event-calendar__chip
--wr-event-calendar-chip-gapvar(--wr-space-xs).wr-event-calendar
--wr-event-calendar-chip-inline-gapvar(--wr-space-xs).wr-event-calendar__chip
--wr-event-calendar-chip-inset0.125rem.wr-event-calendar
--wr-event-calendar-chip-padding-xvar(--wr-space-sm).wr-event-calendar__chip +1 variant override
--wr-event-calendar-chip-padding-y0.wr-event-calendar__chip +1 variant override
--wr-event-calendar-chip-radiuscalc(var(--wr-border-radius-sm) / 2).wr-event-calendar
--wr-event-calendar-chip-time-font-weightvar(--wr-font-weight-medium).wr-event-calendar__chip-time
--wr-event-calendar-colhead-day-font-sizevar(--wr-text-base).wr-event-calendar__colhead-day
--wr-event-calendar-colhead-day-font-weightvar(--wr-font-weight-semibold).wr-event-calendar__colhead-day
--wr-event-calendar-colhead-font-sizevar(--wr-text-xs).wr-event-calendar__colhead
--wr-event-calendar-colhead-font-weightvar(--wr-font-weight-medium).wr-event-calendar__colhead
--wr-event-calendar-colhead-gapvar(--wr-space-xs).wr-event-calendar__colhead
--wr-event-calendar-colhead-paddingvar(--wr-space-sm).wr-event-calendar__colhead
--wr-event-calendar-cols7.wr-event-calendar
--wr-event-calendar-daynum-font-sizevar(--wr-text-sm).wr-event-calendar__daynum
--wr-event-calendar-daynum-radiusvar(--wr-border-radius-pill).wr-event-calendar__daynum
--wr-event-calendar-fillvar(--wr-color-fill).wr-event-calendar
--wr-event-calendar-gapvar(--wr-space-md).wr-event-calendar
--wr-event-calendar-grid-radiusvar(--wr-border-radius-base).wr-event-calendar__grid
--wr-event-calendar-gutter3.75rem.wr-event-calendar
--wr-event-calendar-gutter-font-sizevar(--wr-text-xs).wr-event-calendar__gutter
--wr-event-calendar-gutter-padding-xvar(--wr-space-sm).wr-event-calendar__gutter
--wr-event-calendar-header-column-gapvar(--wr-space-md).wr-event-calendar__header
--wr-event-calendar-header-row-gapvar(--wr-space-sm).wr-event-calendar__header
--wr-event-calendar-linevar(--wr-border-base).wr-event-calendar
--wr-event-calendar-more-font-sizevar(--wr-text-xs).wr-event-calendar__more
--wr-event-calendar-more-padding-xvar(--wr-space-sm).wr-event-calendar__more
--wr-event-calendar-more-radiusvar(--wr-border-radius-sm).wr-event-calendar__more
--wr-event-calendar-nav-gapvar(--wr-space-xs).wr-event-calendar__nav
--wr-event-calendar-slot-height1.5rem.wr-event-calendar
--wr-event-calendar-title-font-sizevar(--wr-text-lg).wr-event-calendar__title
--wr-event-calendar-title-font-weightvar(--wr-font-weight-semibold).wr-event-calendar__title

Dependencies

Built onButton, ngwr/date

See also