calendar

A calendar component that allows users to select a date or a range of dates.

PreviousNext
Su
Mo
Tu
We
Th
Fr
Sa
import { ChangeDetectionStrategy, Component, signal } from '@angular/core';

import { ZardCalendarComponent } from '../calendar.component';

@Component({
  selector: 'z-demo-calendar-preview',
  imports: [ZardCalendarComponent],
  template: `
    <z-calendar zMode="single" zCaptionLayout="dropdown" class="rounded-lg border" [(value)]="selectedDate" />
  `,
  changeDetection: ChangeDetectionStrategy.OnPush,
})
export class ZardDemoCalendarPreviewComponent {
  readonly selectedDate = signal<Date | null>(new Date());
}

Installation

Copy
npx zard-cli@latest add calendar

Usage

import { ZardCalendarComponent } from '@/shared/components/calendar/calendar.component';
Copy
<z-calendar zMode="single" class="rounded-lg border"></z-calendar>
Copy

Examples

basic

The calendar renders without a border by default — add class="rounded-lg border" to frame it.
August 2026
Su
Mo
Tu
We
Th
Fr
Sa
import { ChangeDetectionStrategy, Component } from '@angular/core';

import { ZardCalendarComponent } from '../calendar.component';

@Component({
  selector: 'z-demo-calendar-basic',
  imports: [ZardCalendarComponent],
  template: `
    <z-calendar zMode="single" class="rounded-lg border" />
  `,
  changeDetection: ChangeDetectionStrategy.OnPush,
})
export class ZardDemoCalendarBasicComponent {}

range

Use zMode="range" to let users select a start and an end date, and zNumberOfMonths to show more than one month at a time.
January 2026
Su
Mo
Tu
We
Th
Fr
Sa
February 2026
Su
Mo
Tu
We
Th
Fr
Sa
import { ChangeDetectionStrategy, Component, signal } from '@angular/core';

import { ZardCalendarComponent } from '../calendar.component';

const RANGE_LENGTH_IN_DAYS = 30;

function startOfRange(): Date {
  return new Date(new Date().getFullYear(), 0, 12);
}

function addDays(date: Date, days: number): Date {
  const result = new Date(date);
  result.setDate(result.getDate() + days);
  return result;
}

@Component({
  selector: 'z-demo-calendar-range',
  imports: [ZardCalendarComponent],
  template: `
    <z-calendar zMode="range" zNumberOfMonths="2" class="rounded-lg border" [(value)]="dateRange" />
  `,
  changeDetection: ChangeDetectionStrategy.OnPush,
})
export class ZardDemoCalendarRangeComponent {
  readonly dateRange = signal<Date[] | null>([startOfRange(), addDays(startOfRange(), RANGE_LENGTH_IN_DAYS)]);
}

multiple

Use zMode="multiple" to select any number of individual dates.
August 2026
Su
Mo
Tu
We
Th
Fr
Sa

Selected (0) date(s).

import { ChangeDetectionStrategy, Component, signal } from '@angular/core';

import { ZardCalendarComponent } from '../calendar.component';

@Component({
  selector: 'z-demo-calendar-multiple',
  imports: [ZardCalendarComponent],
  template: `
    <div class="flex flex-col gap-4">
      <z-calendar zMode="multiple" class="rounded-lg border" [(value)]="selectedDates" />

      <p class="text-muted-foreground text-sm font-medium">Selected ({{ selectedDates()?.length ?? 0 }}) date(s).</p>
    </div>
  `,
  changeDetection: ChangeDetectionStrategy.OnPush,
})
export class ZardDemoCalendarMultipleComponent {
  readonly selectedDates = signal<Date[] | null>(null);
}

presets

Compose the calendar with a <z-card /> footer to offer quick date presets.
August 2026
Su
Mo
Tu
We
Th
Fr
Sa
import { ChangeDetectionStrategy, Component, signal } from '@angular/core';

import { ZardButtonComponent } from '@/shared/components/button/button.component';
import { ZardCardImports } from '@/shared/components/card/card.imports';

import { ZardCalendarComponent } from '../calendar.component';

interface CalendarPreset {
  label: string;
  days: number;
}

const PRESETS: CalendarPreset[] = [
  { label: 'Today', days: 0 },
  { label: 'Tomorrow', days: 1 },
  { label: 'In 3 days', days: 3 },
  { label: 'In a week', days: 7 },
  { label: 'In 2 weeks', days: 14 },
];

@Component({
  selector: 'z-demo-calendar-presets',
  imports: [ZardCalendarComponent, ZardButtonComponent, ZardCardImports],
  template: `
    <z-card zSize="sm" class="mx-auto w-fit max-w-[300px]">
      <z-card-content>
        <z-calendar zMode="single" class="p-0 [--cell-size:--spacing(9.5)]" [(value)]="selectedDate" />
      </z-card-content>
      <z-card-footer zFooterBorder class="flex flex-wrap gap-2">
        @for (preset of presets; track preset.days) {
          <button z-button type="button" zType="outline" zSize="sm" class="flex-1" (click)="selectPreset(preset)">
            {{ preset.label }}
          </button>
        }
      </z-card-footer>
    </z-card>
  `,
  changeDetection: ChangeDetectionStrategy.OnPush,
})
export class ZardDemoCalendarPresetsComponent {
  readonly presets = PRESETS;
  readonly selectedDate = signal<Date | null>(new Date());

  selectPreset(preset: CalendarPreset): void {
    const date = new Date();
    date.setDate(date.getDate() + preset.days);
    this.selectedDate.set(date);
  }
}

with time

Pair the calendar with time inputs to build a date and time picker.
August 2026
Su
Mo
Tu
We
Th
Fr
Sa
import { ChangeDetectionStrategy, Component, signal } from '@angular/core';

import { NgIcon, provideIcons } from '@ng-icons/core';
import { lucideClock2 } from '@ng-icons/lucide';

import { ZardCardImports } from '@/shared/components/card/card.imports';
import { ZardFieldImports } from '@/shared/components/field/field.imports';
import { ZardInputComponent } from '@/shared/components/input/input.component';
import { ZardInputGroupImports } from '@/shared/components/input-group/input-group.imports';

import { ZardCalendarComponent } from '../calendar.component';

@Component({
  selector: 'z-demo-calendar-with-time',
  imports: [
    ZardCalendarComponent,
    ZardCardImports,
    ZardFieldImports,
    ZardInputGroupImports,
    ZardInputComponent,
    NgIcon,
  ],
  template: `
    <z-card zSize="sm" class="mx-auto w-fit">
      <z-card-content>
        <z-calendar zMode="single" class="p-0" [(value)]="selectedDate" />
      </z-card-content>
      <z-card-footer zFooterBorder>
        <div z-field-group>
          <div z-field>
            <label z-field-label for="calendar-time-from">Start Time</label>
            <z-input-group>
              <input
                z-input
                id="calendar-time-from"
                type="time"
                step="1"
                value="10:30:00"
                class="appearance-none [&::-webkit-calendar-picker-indicator]:hidden"
              />
              <z-input-group-addon>
                <ng-icon name="lucideClock2" class="text-muted-foreground" />
              </z-input-group-addon>
            </z-input-group>
          </div>

          <div z-field>
            <label z-field-label for="calendar-time-to">End Time</label>
            <z-input-group>
              <input
                z-input
                id="calendar-time-to"
                type="time"
                step="1"
                value="12:30:00"
                class="appearance-none [&::-webkit-calendar-picker-indicator]:hidden"
              />
              <z-input-group-addon>
                <ng-icon name="lucideClock2" class="text-muted-foreground" />
              </z-input-group-addon>
            </z-input-group>
          </div>
        </div>
      </z-card-footer>
    </z-card>
  `,
  changeDetection: ChangeDetectionStrategy.OnPush,
  viewProviders: [provideIcons({ lucideClock2 })],
})
export class ZardDemoCalendarWithTimeComponent {
  readonly selectedDate = signal<Date | null>(new Date());
}

booked dates

Use zDisabledDates to block individual days — here they are struck through to read as booked.
August 2026
Su
Mo
Tu
We
Th
Fr
Sa
import { ChangeDetectionStrategy, Component, signal } from '@angular/core';

import { ZardCalendarComponent } from '../calendar.component';

const FIRST_BOOKED_DAY = 8;
const BOOKED_DAYS_COUNT = 12;

function bookedDatesOfCurrentMonth(): Date[] {
  const today = new Date();

  return Array.from(
    { length: BOOKED_DAYS_COUNT },
    (_, index) => new Date(today.getFullYear(), today.getMonth(), FIRST_BOOKED_DAY + index),
  );
}

@Component({
  selector: 'z-demo-calendar-booked-dates',
  imports: [ZardCalendarComponent],
  template: `
    <z-calendar
      zMode="single"
      class="rounded-lg border shadow-sm [&_[data-disabled=true]_button]:line-through [&_[data-disabled=true]_button]:opacity-100"
      [zDisabledDates]="bookedDates"
      [(value)]="selectedDate"
    />
  `,
  changeDetection: ChangeDetectionStrategy.OnPush,
})
export class ZardDemoCalendarBookedDatesComponent {
  readonly bookedDates = bookedDatesOfCurrentMonth();
  readonly selectedDate = signal<Date | null>(null);
}

custom cell size

Override the --cell-size CSS variable to resize the whole calendar.
Su
Mo
Tu
We
Th
Fr
Sa
import { ChangeDetectionStrategy, Component, signal } from '@angular/core';

import { ZardCardImports } from '@/shared/components/card/card.imports';

import { ZardCalendarComponent } from '../calendar.component';

@Component({
  selector: 'z-demo-calendar-custom-cell-size',
  imports: [ZardCalendarComponent, ZardCardImports],
  template: `
    <z-card zSize="sm" class="mx-auto w-fit">
      <z-card-content>
        <z-calendar
          zMode="range"
          zCaptionLayout="dropdown"
          class="p-0 [--cell-size:--spacing(10)] md:[--cell-size:--spacing(12)]"
          [(value)]="dateRange"
        />
      </z-card-content>
    </z-card>
  `,
  changeDetection: ChangeDetectionStrategy.OnPush,
})
export class ZardDemoCalendarCustomCellSizeComponent {
  readonly dateRange = signal<Date[] | null>(null);
}
<!-- Scale every measurement with the Tailwind spacing scale. -->
<z-calendar class="rounded-lg border [--cell-size:--spacing(11)] md:[--cell-size:--spacing(12)]" />
Copy
<!-- Or use fixed values. -->
<z-calendar class="rounded-lg border [--cell-size:2.75rem] md:[--cell-size:3rem]" />
Copy

with constraints

Use minDate and maxDate to limit the selectable range, or disabled to turn the whole calendar off.
August 2026
Su
Mo
Tu
We
Th
Fr
Sa
August 2026
Su
Mo
Tu
We
Th
Fr
Sa
import { ChangeDetectionStrategy, Component } from '@angular/core';

import { ZardCalendarComponent } from '../calendar.component';

const DAYS_IN_FUTURE = 30;
const MILLISECONDS_PER_DAY = 24 * 60 * 60 * 1000;

@Component({
  selector: 'z-demo-calendar-with-constraints',
  imports: [ZardCalendarComponent],
  template: `
    <div class="flex flex-wrap items-start justify-center gap-6">
      <z-calendar zMode="single" class="rounded-lg border" [minDate]="minDate" [maxDate]="maxDate" />

      <z-calendar zMode="single" class="rounded-lg border" [disabled]="true" />
    </div>
  `,
  changeDetection: ChangeDetectionStrategy.OnPush,
})
export class ZardDemoCalendarWithConstraintsComponent {
  readonly minDate = new Date();
  readonly maxDate = new Date(Date.now() + DAYS_IN_FUTURE * MILLISECONDS_PER_DAY);

  constructor() {
    this.minDate.setHours(0, 0, 0, 0);
    this.maxDate.setHours(23, 59, 59, 999);
  }
}

expand year selection range

minDate and maxDate also expand the year dropdown — useful for a date of birth picker.
Su
Mo
Tu
We
Th
Fr
Sa
import { ChangeDetectionStrategy, Component, signal } from '@angular/core';

import { ZardCalendarComponent } from '../calendar.component';

const BIRTH_YEAR_FLOOR = 1950;

@Component({
  selector: 'z-demo-calendar-expand-year-selection-range',
  imports: [ZardCalendarComponent],
  template: `
    <z-calendar
      zMode="single"
      zCaptionLayout="dropdown"
      class="rounded-lg border"
      [minDate]="minDate"
      [maxDate]="maxDate"
      [(value)]="dateOfBirth"
    />
  `,
  changeDetection: ChangeDetectionStrategy.OnPush,
})
export class ZardDemoCalendarExpandYearSelectionRangeComponent {
  readonly minDate = new Date(BIRTH_YEAR_FLOOR, 0, 1);
  readonly maxDate = new Date();
  readonly dateOfBirth = signal<Date | null>(null);
}

API Reference

z-calendarComponent

A calendar component that allows users to select a date or a range of dates, with full keyboard navigation support. Every measurement derives from the --cell-size and --cell-radius CSS variables listed at the end of the table, so overriding them through the class input rescales the whole calendar.

PropertyDescriptionTypeDefault
[class] Additional CSS classes. Also where a border is opted into (`rounded-lg border`) and where the CSS variables below are overridden ClassValue ''
[zMode] Selection mode of the calendar 'single' | 'multiple' | 'range' 'single'
[value] Currently selected date(s) - type depends on mode CalendarValue null
[minDate] Minimum selectable date. Also used to expand the year picker range Date | null null
[maxDate] Maximum selectable date. Also used to expand the year picker range Date | null null
[disabled] Whether the calendar is disabled boolean false
[zCaptionLayout] How the month/year caption is rendered: a plain label, two dropdowns, or a dropdown for only the month or only the year. The dropdowns are native `<select>` elements laid invisible over the label, so the browser owns the popup 'label' | 'dropdown' | 'dropdown-months' | 'dropdown-years' 'label'
[zButtonVariant] Button variant used by the previous/next month arrows ZardButtonTypeVariants 'ghost'
[zShowOutsideDays] Whether the days of the surrounding months are visible. When false they are hidden but keep their grid cell, so the layout never shifts boolean true
[zDisabledDates] Individual days that cannot be selected, on top of the minDate/maxDate range. Each day still keeps its grid cell and is marked with `data-disabled="true"` Date[] []
[zNumberOfMonths] How many months are rendered side by side. They stack vertically below the `md` breakpoint, and only the first and the last month carry the navigation arrows number 1
(dateChange) Emitted when date selection changes EventEmitter<Date | Date[]> -
resetNavigation() Public method that moves the visible month back to the selected value and clears the roving focus () => void -
[--cell-size] CSS variable: width and height of a day cell, e.g. `class="[--cell-size:--spacing(12)]"` length --spacing(7)
[--cell-radius] CSS variable: corner radius of a day cell, e.g. `class="[--cell-radius:var(--radius-lg)]"` length var(--radius-md)
github iconwhatsapp icondiscord iconX icon

Made with in Brazil. Open source and available on GitHub .