Calendar

A month grid for picking a date or a date range.

import { โ€ฆ } from '@inlaylab/ivorie/calendar'
November 2026
S M T W T F S
import { ChangeDetectionStrategy, Component, signal } from '@angular/core';
import { IvCalendar } from '@inlaylab/ivorie/calendar';

@Component({
  selector: 'demo-calendar',
  imports: [IvCalendar],
  changeDetection: ChangeDetectionStrategy.OnPush,
  template: `<iv-calendar [(selected)]="date" class="rounded-md border" />`,
})
export default class CalendarDemo {
  protected readonly date = signal<Date | null>(new Date(2026, 0, 15));
}

Installation

CLInpm
npx ivorie add calendar

A date picker grid with previous/next navigation. It picks one date or a range, and works with ngModel and reactive forms. It uses native Date and has no date library dependency.

Usage

import { IvCalendar } from '@inlaylab/ivorie/calendar';
<iv-calendar [(selected)]="date" />

Examples

Range

January 2026
S M T W T F S
import { ChangeDetectionStrategy, Component, signal } from '@angular/core';
import { IvCalendar, IvDateRange } from '@inlaylab/ivorie/calendar';

@Component({
  selector: 'demo-calendar-range',
  imports: [IvCalendar],
  changeDetection: ChangeDetectionStrategy.OnPush,
  template: `
    <iv-calendar mode="range" [(range)]="range" [defaultMonth]="month" class="rounded-md border" />
  `,
})
export default class CalendarRangeDemo {
  protected readonly month = new Date(2026, 0, 1);
  protected readonly range = signal<IvDateRange>({
    from: new Date(2026, 0, 12),
    to: new Date(2026, 0, 18),
  });
}

Min and max

January 2026
S M T W T F S
import { ChangeDetectionStrategy, Component, signal } from '@angular/core';
import { IvCalendar } from '@inlaylab/ivorie/calendar';

@Component({
  selector: 'demo-calendar-min-max',
  imports: [IvCalendar],
  changeDetection: ChangeDetectionStrategy.OnPush,
  template: `
    <iv-calendar
      [(selected)]="date"
      [minDate]="min"
      [maxDate]="max"
      [defaultMonth]="min"
      class="rounded-md border"
    />
  `,
})
export default class CalendarMinMaxDemo {
  protected readonly min = new Date(2026, 0, 10);
  protected readonly max = new Date(2026, 1, 20);
  protected readonly date = signal<Date | null>(new Date(2026, 0, 15));
}

Disabled dates

January 2026
S M T W T F S
import { ChangeDetectionStrategy, Component, signal } from '@angular/core';
import { IvCalendar } from '@inlaylab/ivorie/calendar';

@Component({
  selector: 'demo-calendar-disabled-dates',
  imports: [IvCalendar],
  changeDetection: ChangeDetectionStrategy.OnPush,
  template: `
    <iv-calendar
      [(selected)]="date"
      [disabledDate]="isWeekend"
      [defaultMonth]="month"
      class="rounded-md border"
    />
  `,
})
export default class CalendarDisabledDatesDemo {
  protected readonly month = new Date(2026, 0, 1);
  protected readonly date = signal<Date | null>(new Date(2026, 0, 15));
  protected readonly isWeekend = (d: Date) => d.getDay() === 0 || d.getDay() === 6;
}

Two months

January 2026
S M T W T F S
February 2026
S M T W T F S
import { ChangeDetectionStrategy, Component, signal } from '@angular/core';
import { IvCalendar } from '@inlaylab/ivorie/calendar';

@Component({
  selector: 'demo-calendar-two-months',
  imports: [IvCalendar],
  changeDetection: ChangeDetectionStrategy.OnPush,
  template: `
    <iv-calendar
      [(selected)]="date"
      [numberOfMonths]="2"
      [defaultMonth]="month"
      class="rounded-md border"
    />
  `,
})
export default class CalendarTwoMonthsDemo {
  protected readonly month = new Date(2026, 0, 1);
  protected readonly date = signal<Date | null>(new Date(2026, 0, 15));
}

Default month

February 2026
S M T W T F S
import { ChangeDetectionStrategy, Component, signal } from '@angular/core';
import { IvCalendar } from '@inlaylab/ivorie/calendar';

@Component({
  selector: 'demo-calendar-default-month',
  imports: [IvCalendar],
  changeDetection: ChangeDetectionStrategy.OnPush,
  template: `
    <iv-calendar [(selected)]="date" [defaultMonth]="month" class="rounded-md border" />
  `,
})
export default class CalendarDefaultMonthDemo {
  protected readonly month = new Date(2026, 1, 1);
  protected readonly date = signal<Date | null>(null);
}

Locale

janvier 2026
L M M J V S D
import { ChangeDetectionStrategy, Component, signal } from '@angular/core';
import { IvCalendar } from '@inlaylab/ivorie/calendar';

@Component({
  selector: 'demo-calendar-locale',
  imports: [IvCalendar],
  changeDetection: ChangeDetectionStrategy.OnPush,
  template: `
    <iv-calendar
      [(selected)]="date"
      locale="fr-FR"
      [weekStartsOn]="1"
      [defaultMonth]="month"
      class="rounded-md border"
    />
  `,
})
export default class CalendarLocaleDemo {
  protected readonly month = new Date(2026, 0, 1);
  protected readonly date = signal<Date | null>(new Date(2026, 0, 15));
}

Reactive forms

January 2026
S M T W T F S
import { ChangeDetectionStrategy, Component } from '@angular/core';
import { FormControl, ReactiveFormsModule } from '@angular/forms';
import { IvCalendar } from '@inlaylab/ivorie/calendar';

@Component({
  selector: 'demo-calendar-reactive-forms',
  imports: [ReactiveFormsModule, IvCalendar],
  changeDetection: ChangeDetectionStrategy.OnPush,
  template: `
    <iv-calendar [formControl]="date" [defaultMonth]="month" class="rounded-md border" />
  `,
})
export default class CalendarReactiveFormsDemo {
  protected readonly month = new Date(2026, 0, 1);
  protected readonly date = new FormControl<Date | null>(new Date(2026, 0, 15));
}

API

Every part also accepts class.

iv-calendar

Name Type Default Description
mode 'single' | 'range' 'single' Selection model.
selected Date | null null Selected date in single mode. Bind with [(selected)].
range IvDateRange | null { from: null, to: null } Selected range in range mode. Bind with [(range)].
numberOfMonths 1 | 2 1 Months shown side by side.
minDate Date | null null Earliest selectable day.
maxDate Date | null null Latest selectable day.
disabledDate ((d: Date) => boolean) | null null Returns true for days that cannot be picked.
defaultMonth Date | null current month Visible month. A new value moves the view.
showOutsideDays boolean true Shows leading and trailing days of nearby months.
weekStartsOn 0 | 1 | 2 | 3 | 4 | 5 | 6 from locale First column. 0 is Sunday. Falls back to Sunday where the browser has no week info.
locale string LOCALE_ID BCP 47 tag for month, weekday and day labels.
disabled boolean false Disables every day and the navigation. Also disabled when the form control is disabled.

Outputs:

Name Payload Description
selectedChange Date | null Emits in single mode when a day is picked.
rangeChange IvDateRange Emits in range mode on each pick.

Types: IvCalendarMode is 'single' | 'range'. IvDateRange is { from: Date | null; to: Date | null }. IvWeekday is 0 to 6.

With forms, the value is Date | null in single mode and IvDateRange in range mode.

Accessibility

  • Each month is a role="grid" table labelled by its caption. The caption is aria-live="polite". Column headers carry the long weekday name.
  • Each day is a button labelled with the full date, such as "Monday, March 3, 2025". Picked days have aria-selected.
  • Only one day is in the tab order: the keyboard's day, else the selected day, today, or the first of the month.
  • Arrow keys move by day (left/right) or week (up/down). Home and End go to the start or end of the week. PageUp and PageDown change the month. Shift with PageUp or PageDown changes the year. Disabled days are skipped.
  • Enter and Space pick the focused day.
  • The previous and next buttons are labelled "Previous month" and "Next month". They disable at minDate and maxDate.
  • The form control is marked touched when focus leaves the calendar.

Notes

  • Range picking: the first click sets from and clears to. The second click sets to, or moves from if it is earlier. The third click starts a new range.
  • Day buttons carry data-range-start, data-range-middle, data-range-end and data-selected-single.
  • The grid always shows 6 weeks, so its height does not jump between months.
  • There are no year or month dropdowns. To add them, put a separate control above the calendar and bind it to defaultMonth.
  • The host has bg-background, but is transparent inside card or popover content.