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
npx ivorie add calendarimport { โฆ } from '@inlaylab/ivorie/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 isaria-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).
HomeandEndgo to the start or end of the week.PageUpandPageDownchange the month.ShiftwithPageUporPageDownchanges the year. Disabled days are skipped. EnterandSpacepick the focused day.- The previous and next buttons are labelled "Previous month" and "Next month". They disable at
minDateandmaxDate. - The form control is marked touched when focus leaves the calendar.
Notes
- Range picking: the first click sets
fromand clearsto. The second click setsto, or movesfromif it is earlier. The third click starts a new range. - Day buttons carry
data-range-start,data-range-middle,data-range-endanddata-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.