Date Picker

A text field with a popup for picking a date, a time or both.

Installation

Import the class, and @use the entry point in your global styles: without it the component renders unstyled.

import { Component } from '@angular/core';
import { WrDatePicker, WrDateRangePicker } from 'ngwr/date-picker';

@Component({ imports: [WrDatePicker, WrDateRangePicker] })
export class MyComponent {}

Basic

Bind [(value)]. The default format follows the locale.

<wr-date-picker [(value)]="picked" placeholder="Pick a date" />

Custom format

format takes these tokens: yyyy yy MMMM MMM MM M dd d HH H hh h mm ss a.

<wr-date-picker [(value)]="picked" format="dd.MM.yyyy" />

Bounds + filter

min and max are inclusive and dateFilter turns single dates off, for typed dates too.

<wr-date-picker
  [(value)]="picked"
  [min]="today"
  [max]="firstOfNextMonth"
  [dateFilter]="isWeekday"
/>

Refused input

Unreadable text leaves the value unchanged and shows as invalid on Enter or blur; try 07.09.1994.

<wr-date-picker #dueField [(value)]="due" format="dd.MM.yyyy" [min]="today" />

@if (dueField.inputError(); as error) {
  <small>{{ error.message }}</small>
}
inputError: — · value: —
  • The message comes from validation.dateFormat, minDate, maxDate or dateFilter in your i18n catalog. ngwr ships no text for them.
  • Without a form, read inputError() through a template reference.
  • In a form, the control is invalid while the field holds refused text.

Time mode

mode="time" shows a time stepper instead of the calendar. The date part of the value is kept.

<wr-date-picker mode="time" [(value)]="picked" />

Time — 24-hour

timeFormat="24h" forces the 24-hour clock, in the panel and in the field.

<wr-date-picker mode="time" timeFormat="24h" [(value)]="picked" />

Time — seconds + step

showSeconds adds seconds to the panel and the field. step applies to minutes and seconds.

<wr-date-picker
  mode="time"
  timeFormat="24h"
  [showSeconds]="true"
  [step]="5"
  [(value)]="picked"
/>

Date + Time mode

mode="datetime" adds the time stepper under the calendar. Picking a date keeps the popup open.

<wr-date-picker mode="datetime" [(value)]="when" />

Date + Time — custom format, seconds, step

<wr-date-picker
  mode="datetime"
  format="dd.MM.yyyy HH:mm:ss"
  timeFormat="24h"
  [showSeconds]="true"
  [step]="5"
  [(value)]="when"
/>

Date range

<wr-date-range-picker> binds a [start, end] pair. Picking the second date closes the popup.

import { type WrDateRange, WrDateRangePicker } from 'ngwr/date-picker';

@Component({ imports: [WrDateRangePicker] })
export class MyComponent {
  protected readonly period = signal<WrDateRange | null>(null);
}
<wr-date-range-picker
  [(value)]="period"
  startPlaceholder="From"
  endPlaceholder="To"
/>
Value: — → —

Date range + time

mode="datetime" adds a time stepper for each end. Picking dates keeps the popup open.

<wr-date-range-picker
  mode="datetime"
  timeFormat="24h"
  format="dd.MM.yyyy HH:mm"
  [(value)]="window"
/>
Value: — → —

Keyboard

The popup is not modal: Tab leaves it.

KeyDoes
Alt + ArrowDown or the trigger buttonOpens the popup and moves focus into it.
ArrowDownArrowUpMoves focus into a popup that is already open.
TabShift + TabPast the popup's last or first control: closes it and moves on from the trigger.
EscapeCloses the popup and returns focus to what opened it.
EnterAccepts the typed text and closes the popup. A surrounding form still submits.

Use it in a form

Bind [formField], formControlName or [(ngModel)]; see Forms.

<!-- Signal Forms -->
<wr-date-picker [formField]="form.due" />

<!-- Reactive forms -->
<form [formGroup]="form">
  <wr-form-field label="Due date" required>
    <wr-date-picker formControlName="due" placeholder="Pick a date" />
    <wr-form-error key="required">Pick a due date.</wr-form-error>
  </wr-form-field>
</form>

<!-- The range picker's value is WrDateRange | null. -->
<wr-date-range-picker formControlName="period" />

<!-- ngModel -->
<wr-date-picker [(ngModel)]="due" name="due" />
import { FormControl, FormGroup, ReactiveFormsModule, Validators } from '@angular/forms';

import { type WrDateRange, WrDatePicker, WrDateRangePicker } from 'ngwr/date-picker';
import { WrFormError, WrFormField } from 'ngwr/form';

@Component({
  imports: [ReactiveFormsModule, WrFormField, WrFormError, WrDatePicker, WrDateRangePicker],
  templateUrl: './my.html',
})
export class MyComponent {
  protected readonly form = new FormGroup({
    due: new FormControl<Date | null>(null, Validators.required),
    period: new FormControl<WrDateRange | null>(null),
  });
}
due touched: false · form valid: false

API

NameDescriptionTypeDefault
modePicker behavior — see class doc.'date' | 'time' | 'datetime''date'
formatFormat used for both display and parsing. When null (default), the format is derived from mode (shortDate / time / shortDateTime), with the hour cycle of timeFormat and the seconds of showSeconds (time24hSeconds, …). Pass a named key or raw token string to override.WrDateFormat | string | nullnull
placeholderPlaceholder shown when the input is empty.string''
minMin selectable date (forwarded to the calendar). Ignored in time mode.Date | undefinedundefined
maxMax selectable date (forwarded to the calendar). Ignored in time mode.Date | undefinedundefined
dateFilterPredicate to disable specific dates (forwarded to the calendar). Ignored in time mode.((date: Date) => boolean) | nullnull
timeFormat12 / 24-hour clock, in the time panel and in the field, unless format is set. 'auto' follows the locale. Applies in time + datetime modes.'auto' | '12h' | '24h''auto'
showSecondsShow seconds, in the time panel and in the field, unless format is set. Applies in time + datetime modes.booleanfalse
stepMinute / second step for the time panel.number1
disabledDisable interaction. Bound automatically from the field's disabled state when used with [formField].booleanfalse
readonlyRead-only — input not typeable, but the trigger icon still opens the overlay.booleanfalse
valueThe picked Date. Bound by [formField], or two-way via [(value)].Date | nullnull
(touch)Emitted on blur so a bound field can mark itself touched.void—
ariaLabelAccessible name of the text field. Falls back to the placeholder, then to the same catalog string the calendar button uses — the field is a role="combobox", and with an empty placeholder it had no name at all.string | nullnull
panelAriaLabelAccessible name of the popup. The trigger advertises aria-haspopup="dialog", so the panel is a role="dialog" — and an unnamed dialog announces as a bare "dialog". Defaults to the catalog's datePicker.panel* string for the current mode.string | nullnull
panelClassExtra CSS classes for the calendar panel's overlay pane. The pane is appended to the overlay container, not to this component, so nothing in the consumer's own template encloses it and no descendant rule written around the trigger can reach it. This input is the only per-instance handle on it. A space-separated string works as well as an array.WrClassInputnull

Range picker API

NameDescriptionTypeDefault
valueThe range as [start, end]. An end is null until picked; ends in the wrong order are swapped.WrDateRange | nullnull
modedate (default) renders a range calendar; datetime adds a time stepper per end.'date' | 'datetime''date'
formatFormat for both ends. Defaults to shortDate, or shortDateTime with the clock of timeFormat and showSeconds.WrDateFormat | string | nullnull
startPlaceholderPlaceholder for the start input.string''
endPlaceholderPlaceholder for the end input.string''
separatorGlyph rendered between the two inputs.string'–'
minDateEarliest selectable date, for both ends. Not min, unlike <wr-date-picker>.Date | nullnull
maxDateLatest selectable date, for both ends.Date | nullnull
dateFilterPredicate disabling individual dates.((date: Date) => boolean) | nullnull
timeFormat12 / 24-hour clock, in the time panels and both fields. Applies in datetime mode.'auto' | '12h' | '24h''auto'
showSecondsShow seconds on both time panels and in both fields.booleanfalse
stepMinute / second step for the time panels.number1
disabledBlock interaction.booleanfalse
panelAriaLabelAccessible name of the popup. Defaults to the catalog’s datePicker.panelRange* string for the mode.string | nullnull
panelClassExtra classes for the popup’s overlay pane, as a string or an array.WrClassInputnull
(touch)Emitted on blur so a bound field can mark itself touched.void—
readonlyThe value cannot change and the popup does not open. <wr-date-picker> still opens its popup.booleanfalse

CSS variables

Set these on the component's selector or a wrapper, not on :root: the defaults are declared on ngwr/date-picker's own element.

VariableDefaultDeclared on
--wr-date-range-panel-time-gapvar(--wr-space-xs).wr-date-range-panel__time
--wr-date-range-panel-time-label-font-sizevar(--wr-text-xs).wr-date-range-panel__time-label
--wr-date-range-panel-time-label-font-weightvar(--wr-font-weight-semibold).wr-date-range-panel__time-label
--wr-date-range-panel-times-bordervar(--wr-border-base).wr-date-range-panel__times
--wr-date-range-panel-times-gap-xvar(--wr-space-xl).wr-date-range-panel__times
--wr-date-range-panel-times-gap-yvar(--wr-space-md).wr-date-range-panel__times
--wr-date-range-panel-times-paddingvar(--wr-space-md).wr-date-range-panel__times
--wr-date-range-panel-times-radiusvar(--wr-border-radius-base).wr-date-range-panel__times
--wr-date-range-picker-separator-padding-xvar(--wr-space-xs).wr-date-range-picker__separator
--wr-date-time-panel-time-bordervar(--wr-border-base).wr-date-time-panel__time
--wr-date-time-panel-time-paddingvar(--wr-space-md).wr-date-time-panel__time
--wr-date-time-panel-time-radiusvar(--wr-border-radius-base).wr-date-time-panel__time
--wr-time-picker-bgvar(--wr-color-surface).wr-time-picker
--wr-time-picker-bordervar(--wr-border-base).wr-time-picker
--wr-time-picker-col-gapvar(--wr-space-xs).wr-time-picker__col
--wr-time-picker-col-width3rem.wr-time-picker
--wr-time-picker-colorvar(--wr-color-on-surface).wr-time-picker
--wr-time-picker-font-familyvar(--wr-font-family-base).wr-time-picker
--wr-time-picker-gapvar(--wr-space-xs).wr-time-picker
--wr-time-picker-input-font-sizevar(--wr-text-sm).wr-time-picker__input
--wr-time-picker-input-font-weightvar(--wr-font-weight-semibold).wr-time-picker__input
--wr-time-picker-input-line-heightvar(--wr-text-sm-leading).wr-time-picker__input
--wr-time-picker-input-paddingvar(--wr-space-xs).wr-time-picker__input
--wr-time-picker-input-radiusvar(--wr-border-radius-sm).wr-time-picker__input
--wr-time-picker-label-font-sizevar(--wr-text-sm).wr-time-picker
--wr-time-picker-label-font-weightvar(--wr-font-weight-semibold).wr-time-picker
--wr-time-picker-label-radiusvar(--wr-border-radius-sm).wr-time-picker
--wr-time-picker-mutedvar(--wr-color-on-surface-muted).wr-time-picker
--wr-time-picker-overlay-padding-xvar(--wr-space-md).wr-date-picker-overlay > .wr-time-picker
--wr-time-picker-overlay-padding-yvar(--wr-space-sm).wr-date-picker-overlay > .wr-time-picker
--wr-time-picker-overlay-radiusvar(--wr-border-radius-base).wr-date-picker-overlay > .wr-time-picker
--wr-time-picker-sep-font-sizevar(--wr-text-lg).wr-time-picker__sep
--wr-time-picker-sep-font-weightvar(--wr-font-weight-semibold).wr-time-picker__sep
--wr-time-picker-sep-line-heightvar(--wr-leading-none).wr-time-picker__sep
--wr-time-picker-sep-padding-topvar(--wr-space-sm).wr-time-picker__sep
--wr-time-picker-step-line-heightvar(--wr-leading-none).wr-time-picker__step
--wr-time-picker-step-radiusvar(--wr-border-radius-sm).wr-time-picker__step

Dependencies

Angular CDKoverlay, portal
Built onCalendar, ngwr/date, Form, Input, Overlay
Used bySchema Form

See also