Skip to content
Ndop UI

Date Picker

A button that opens a calendar in a popover to pick a date or a range.

  • v1.0.0
  • SSR
  • Zoneless
  • 1.1 kB gzip
import { DatePipe } from '@angular/common';
import { Component, signal } from '@angular/core';
import { NdIcon } from '@ndop/core';
import {
  Calendar,
  CalendarCaption,
  CalendarCell,
  CalendarDay,
  CalendarGrid,
  CalendarHeading,
  CalendarMonth,
  CalendarMonths,
  CalendarNav,
  CalendarNext,
  CalendarPrevious,
  CalendarWeek,
  CalendarWeekday,
  CalendarWeekdays,
} from '@/ui/calendar/calendar';
import { DatePickerTrigger } from '@/ui/date-picker/date-picker';
import {
  Popover,
  PopoverContent,
  PopoverPortal,
  PopoverTrigger,
} from '@/ui/popover/popover';

/**
 * A button that opens a calendar to pick a date. Opening focuses the chosen
 * day; picking one closes the popover and returns focus to the button.
 */
@Component({
  selector: 'example-date-picker-demo',
  imports: [
    Calendar,
    CalendarCaption,
    CalendarCell,
    CalendarDay,
    CalendarGrid,
    CalendarHeading,
    CalendarMonth,
    CalendarMonths,
    CalendarNav,
    CalendarNext,
    CalendarPrevious,
    CalendarWeek,
    CalendarWeekday,
    CalendarWeekdays,
    DatePickerTrigger,
    DatePipe,
    NdIcon,
    Popover,
    PopoverContent,
    PopoverPortal,
    PopoverTrigger,
  ],
  template: `
    <ng-container ndPopover [(open)]="open">
      <button ndPopoverTrigger ndDatePickerTrigger [empty]="!date()">
        <svg ndIcon="calendar"></svg>
        {{ date() ? (date() | date: 'longDate') : 'Pick a date' }}
      </button>
      <ng-template ndPopoverPortal>
        <div
          ndPopoverContent
          align="start"
          aria-label="Choose a date"
          class="w-auto p-0"
        >
          <div
            ndCalendar
            #calendar="ndCalendar"
            [(value)]="date"
            (valueChange)="open.set(false)"
          >
            <div ndCalendarMonths>
              <div ndCalendarNav>
                <button ndCalendarPrevious aria-label="Previous month">
                  <svg ndIcon="chevron-left"></svg>
                </button>
                <button ndCalendarNext aria-label="Next month">
                  <svg ndIcon="chevron-right"></svg>
                </button>
              </div>
              @for (month of calendar.months(); track month.key) {
                <div ndCalendarMonth [month]="month">
                  <div ndCalendarCaption>
                    <div ndCalendarHeading>{{ month.label }}</div>
                  </div>
                  <table ndCalendarGrid>
                    <thead>
                      <tr ndCalendarWeekdays>
                        @for (
                          weekday of calendar.weekdays();
                          track weekday.long
                        ) {
                          <th ndCalendarWeekday [weekday]="weekday">
                            {{ weekday.short }}
                          </th>
                        }
                      </tr>
                    </thead>
                    <tbody>
                      @for (week of month.weeks; track week.key) {
                        <tr ndCalendarWeek>
                          @for (day of week.days; track day.key) {
                            <td ndCalendarCell [day]="day">
                              <button ndCalendarDay [day]="day">
                                {{ day.label }}
                              </button>
                            </td>
                          }
                        </tr>
                      }
                    </tbody>
                  </table>
                </div>
              }
            </div>
          </div>
        </div>
      </ng-template>
    </ng-container>
  `,
})
export class DatePickerDemoExample {
  protected readonly open = signal(false);
  protected readonly date = signal<Date | null>(null);
}

A date picker is a button that opens a calendar in a popover, to pick a date or a range of days. It composes Popover and Calendar: opening focuses the chosen day, the arrow keys move between days, and choosing one can close the popover and return focus to the button, which shows the date formatted for the locale.

Installation

npx @ndop/cli add date-picker

Usage

import {
  Calendar,
  CalendarCaption,
  CalendarCell,
  CalendarDay,
  CalendarGrid,
  CalendarHeading,
  CalendarMonth,
  CalendarMonths,
  CalendarNav,
  CalendarNext,
  CalendarPrevious,
  CalendarWeek,
  CalendarWeekday,
  CalendarWeekdays,
} from '@/ui/calendar/calendar';
import { DatePickerTrigger } from '@/ui/date-picker/date-picker';
import {
  Popover,
  PopoverContent,
  PopoverPortal,
  PopoverTrigger,
} from '@/ui/popover/popover';
<ng-container ndPopover [(open)]="open">
  <button ndPopoverTrigger ndDatePickerTrigger [empty]="!date()">
    <svg ndIcon="calendar"></svg>
    {{ date() ? (date() | date: 'longDate') : 'Pick a date' }}
  </button>
  <ng-template ndPopoverPortal>
    <div
      ndPopoverContent
      align="start"
      aria-label="Choose a date"
      class="w-auto p-0"
    >
      <div
        ndCalendar
        #calendar="ndCalendar"
        [(value)]="date"
        (valueChange)="open.set(false)"
      >
        <div ndCalendarMonths>
          <div ndCalendarNav>
            <button ndCalendarPrevious aria-label="Previous month">
              <svg ndIcon="chevron-left"></svg>
            </button>
            <button ndCalendarNext aria-label="Next month">
              <svg ndIcon="chevron-right"></svg>
            </button>
          </div>
          @for (month of calendar.months(); track month.key) {
            <div ndCalendarMonth [month]="month">
              <div ndCalendarCaption>
                <div ndCalendarHeading>{{ month.label }}</div>
              </div>
              <table ndCalendarGrid>
                <thead>
                  <tr ndCalendarWeekdays>
                    @for (
                      weekday of calendar.weekdays();
                      track weekday.long
                    ) {
                      <th ndCalendarWeekday [weekday]="weekday">
                        {{ weekday.short }}
                      </th>
                    }
                  </tr>
                </thead>
                <tbody>
                  @for (week of month.weeks; track week.key) {
                    <tr ndCalendarWeek>
                      @for (day of week.days; track day.key) {
                        <td ndCalendarCell [day]="day">
                          <button ndCalendarDay [day]="day">
                            {{ day.label }}
                          </button>
                        </td>
                      }
                    </tr>
                  }
                </tbody>
              </table>
            </div>
          }
        </div>
      </div>
    </div>
  </ng-template>
</ng-container>

Examples

Date Picker range

A button that opens two months to pick a range of days.

import { DatePipe } from '@angular/common';
import { Component, signal } from '@angular/core';
import { NdIcon } from '@ndop/core';
import {
  Calendar,
  CalendarCaption,
  CalendarCell,
  CalendarDay,
  CalendarGrid,
  CalendarHeading,
  CalendarMonth,
  CalendarMonths,
  CalendarNav,
  CalendarNext,
  CalendarPrevious,
  CalendarWeek,
  CalendarWeekday,
  CalendarWeekdays,
  type DateRange,
} from '@/ui/calendar/calendar';
import { DatePickerTrigger } from '@/ui/date-picker/date-picker';
import {
  Popover,
  PopoverContent,
  PopoverPortal,
  PopoverTrigger,
} from '@/ui/popover/popover';

/**
 * A button that opens two months to pick a range of days; it shows both
 * ends once the range is complete.
 */
@Component({
  selector: 'example-date-picker-range',
  imports: [
    Calendar,
    CalendarCaption,
    CalendarCell,
    CalendarDay,
    CalendarGrid,
    CalendarHeading,
    CalendarMonth,
    CalendarMonths,
    CalendarNav,
    CalendarNext,
    CalendarPrevious,
    CalendarWeek,
    CalendarWeekday,
    CalendarWeekdays,
    DatePickerTrigger,
    DatePipe,
    NdIcon,
    Popover,
    PopoverContent,
    PopoverPortal,
    PopoverTrigger,
  ],
  template: `
    <ng-container ndPopover [(open)]="open">
      <button
        ndPopoverTrigger
        ndDatePickerTrigger
        [empty]="!range().from"
        class="w-[300px]"
      >
        <svg ndIcon="calendar"></svg>
        @if (range().from; as from) {
          {{ from | date: 'mediumDate' }}
          @if (range().to; as to) {
            – {{ to | date: 'mediumDate' }}
          }
        } @else {
          Pick a date range
        }
      </button>
      <ng-template ndPopoverPortal>
        <div
          ndPopoverContent
          align="start"
          aria-label="Choose a date range"
          class="w-auto p-0"
        >
          <div
            ndCalendar
            #calendar="ndCalendar"
            mode="range"
            [numberOfMonths]="2"
            [(range)]="range"
          >
            <div ndCalendarMonths>
              <div ndCalendarNav>
                <button ndCalendarPrevious aria-label="Previous month">
                  <svg ndIcon="chevron-left"></svg>
                </button>
                <button ndCalendarNext aria-label="Next month">
                  <svg ndIcon="chevron-right"></svg>
                </button>
              </div>
              @for (month of calendar.months(); track month.key) {
                <div ndCalendarMonth [month]="month">
                  <div ndCalendarCaption>
                    <div ndCalendarHeading>{{ month.label }}</div>
                  </div>
                  <table ndCalendarGrid>
                    <thead>
                      <tr ndCalendarWeekdays>
                        @for (
                          weekday of calendar.weekdays();
                          track weekday.long
                        ) {
                          <th ndCalendarWeekday [weekday]="weekday">
                            {{ weekday.short }}
                          </th>
                        }
                      </tr>
                    </thead>
                    <tbody>
                      @for (week of month.weeks; track week.key) {
                        <tr ndCalendarWeek>
                          @for (day of week.days; track day.key) {
                            <td ndCalendarCell [day]="day">
                              <button ndCalendarDay [day]="day">
                                {{ day.label }}
                              </button>
                            </td>
                          }
                        </tr>
                      }
                    </tbody>
                  </table>
                </div>
              }
            </div>
          </div>
        </div>
      </ng-template>
    </ng-container>
  `,
})
export class DatePickerRangeExample {
  protected readonly open = signal(false);
  protected readonly range = signal<DateRange>({ from: null, to: null });
}

Accessibility

Date Picker has no keyboard interaction or focus handling of its own; it keeps the semantics of the element it's on.

API reference

DatePickerTrigger

button[ndDatePickerTrigger]

The button of a date picker: an outline button that shows the chosen date, or a muted placeholder while empty. Put it on the trigger of a Popover whose content holds a Calendar; opening focuses the chosen day.

DatePickerTrigger binding Type Default
[empty]
Whether no date is chosen yet, which mutes the placeholder text.
booleanfalse
[class]
Extra classes, merged last so they win.
string''
Data attribute Values
data-slotdate-picker-trigger
data-empty —

Agent notes

Do

  • Show the chosen date on the button, formatted with Angular's DatePipe, and a placeholder until then.
  • Name the popover's content with aria-label, such as "Choose a date".
  • Use min, max and disabledDates for dates that can't be chosen.

Don't

  • Don't use it for dates people know by heart, such as a birth date; separate fields for day, month and year are quicker to type.
  • Don't hide the calendar's keyboard: keep it in a popover, not on hover.