Installation
import { WrDatePicker } from 'ngwr/date-picker';
import { provideWrDateAdapter } from 'ngwr/date';
bootstrapApplication(AppComponent, {
providers: [provideWrDateAdapter()],
});
@Component({ imports: [WrDatePicker] })
export class MyComponent {
protected readonly picked = signal<Date | null>(null);
}Basic
Default shortDate format follows the active locale.
<wr-date-picker [(value)]="picked" placeholder="Pick a date" />Custom format
Pass any token string supported by the adapter: 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, max, and dateFilter are forwarded to the inner calendar. A typed value the calendar itself would refuse — out of bounds, or filtered out — is not emitted either, so the keyboard and the grid cannot disagree about what is selectable. Both bounds are inclusive, so max names the last selectable day rather than the first excluded one. And grey means only that a cell sits outside the displayed month, never that it is unselectable: an out-of-month cell that is in range and passes the filter can be picked, and the calendar then reopens on the month it landed in. Whether this demo has such a cell depends on the day — isWeekday also applies to it, so in a month whose boundary lands on a weekend every trailing cell is refused.
<wr-date-picker
[(value)]="picked"
[min]="today"
[max]="firstOfNextMonth"
[dateFilter]="isWeekday"
/>Time mode
mode="time" replaces the calendar with an HH:MM[:SS] stepper. AM/PM column appears in 12-hour mode. Value is Date | null — the date portion is preserved across edits.
<!-- Time-only: HH:MM stepper with optional AM/PM -->
<wr-date-picker mode="time" [(value)]="picked" />Time — 24-hour
timeFormat='24h' forces the locale-independent 24h layout.
<wr-date-picker mode="time" timeFormat="24h" [(value)]="picked" />Time — seconds + step
step applies to both minutes and seconds.
<wr-date-picker
mode="time"
timeFormat="24h"
[showSeconds]="true"
[step]="5"
[(value)]="picked"
/>Date + Time mode
mode="datetime" stacks the calendar above the time stepper. Picking a date does NOT close the overlay — the user usually wants to set the time next.
<!-- Date + time: calendar above, stepper below. Picking a date keeps the
overlay open so the user can set the time next. -->
<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> is its own component rather than another [mode], because a range value is [start, end] — folding that into the single picker's Date | null would break [formField] inference everywhere it is already used. Everything else matches: same input skeleton, same overlay, same adapter formats. Two inputs share one range calendar; each end is independently typeable, and picking the second date closes the overlay.
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"
/>Date range + time
mode="datetime" adds one time stepper per end below the calendar. Picking dates does NOT close the overlay, and moving a date keeps the hours already set on that end.
<!-- One time stepper per end; picking dates keeps the overlay open. -->
<wr-date-range-picker
mode="datetime"
timeFormat="24h"
format="dd.MM.yyyy HH:mm"
[(value)]="window"
/>Keyboard and focus
Which key opens the popup decides whether focus follows it in — a click that placed a caret in the text field is never overruled.
- The trigger button — opens and moves focus to the calendar's roving cell (the hours field in `time` mode).
- Alt + ArrowDown in the field — the keyboard equivalent, same destination.
- ArrowDown / ArrowUp in the field while the popup is already open — walks focus in. This is the way in after opening by clicking the field, which deliberately leaves the caret where it was.
- Escape, picking a day, or clicking away — closes, and hands focus back to whatever opened it, provided focus was still inside the popup.
Every other key belongs to the text field: typing a date keeps working with the popup open. The popup is not modal and does not trap focus — Tab leaves it, and leaving closes nothing.
wr-date-range-picker follows the same contract across both of its fields, and focus returns to the one that opened the popup — closing from the end field does not throw the caret back to the start. readonly is the one deliberate difference: the range picker refuses to open at all, because two fields feeding one calendar leaves no reading of “untypeable” that still lets the grid rewrite both ends.
Use it in a form
Both pickers are signal-forms native controls, so [formField] binds their value model directly. Reactive forms and [(ngModel)] go through Angular 22's forms bridge, which binds a signal-forms control with no ControlValueAccessor involved — the library ships none, and formControlName still works. Read the guide before relying on updateOn or on a { emitEvent: false } write; neither behaves the way it does for a native <input>. Submit empty to see the required error.
<!-- Signal forms — the native path. -->
<wr-date-picker [formField]="form.due" />
<!-- Reactive forms. No ControlValueAccessor exists in the library and none is
needed: Angular 22 binds the control's `value` model, relays its `touch`
output as markAsTouched(), and pushes `disabled` down from the control.
The control's value is a `Date | null` — the picker never emits a
half-typed date, so an unparseable field leaves the last valid one. -->
<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 is the same control with a `WrDateRange | null` value. -->
<wr-date-range-picker formControlName="period" />
<!-- Template-driven — the same bridge. -->
<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),
});
}API
| Name | Description | Type | Default |
|---|---|---|---|
mode | Picker behavior — see class doc. | 'date' | 'time' | 'datetime' | 'date' |
format | Format used for both display and parsing. When null (default), the format is derived from mode (shortDate / time / shortDateTime). Pass a named key or raw token string to override. | WrDateFormat | string | null | null |
placeholder | Placeholder shown when the input is empty. | string | '' |
min | Min selectable date (forwarded to the calendar). Ignored in time mode. | Date | undefined | undefined |
max | Max selectable date (forwarded to the calendar). Ignored in time mode. | Date | undefined | undefined |
dateFilter | Predicate to disable specific dates (forwarded to the calendar). Ignored in time mode. | ((date: Date) => boolean) | null | null |
timeFormat | Time-panel 12 / 24-hour format. Applies in time + datetime modes. | 'auto' | '12h' | '24h' | 'auto' |
showSeconds | Render the seconds column. Applies in time + datetime modes. | boolean | false |
step | Minute / second step for the time panel. | number | 1 |
disabled | Disable interaction. Bound automatically from the field's disabled state when used with [formField]. | boolean | false |
readonly | Read-only — input not typeable, but the trigger icon still opens the overlay. | boolean | false |
value | The picked Date. Bound by [formField], or two-way via [(value)]. | Date | null | null |
(touch) | Emitted on blur so a bound field can mark itself touched. | void | — |
ariaLabel | Accessible 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 | null | null |
panelAriaLabel | Accessible 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 | null | null |
Range picker API
| Name | Description | Type | Default |
|---|---|---|---|
value | The picked range as [start, end]. Either end may be null while half-picked; out-of-order ends are swapped on commit. | WrDateRange | null | null |
mode | date (default) renders a range calendar; datetime adds a time stepper per end. | 'date' | 'datetime' | 'date' |
format | Display + parse format for both ends. When omitted, derived from mode (shortDate / shortDateTime). | WrDateFormat | string | null | null |
startPlaceholder | Placeholder for the start input. | string | '' |
endPlaceholder | Placeholder for the end input. | string | '' |
separator | Glyph rendered between the two inputs. | string | '–' |
minDate | Earliest selectable date, both ends. Named minDate because signal forms reserve min for the value type — here a range. | Date | null | null |
maxDate | Latest selectable date, both ends. | Date | null | null |
dateFilter | Predicate disabling individual dates. | ((date: Date) => boolean) | null | null |
timeFormat | Time-panel 12 / 24-hour format. Applies in datetime mode. | 'auto' | '12h' | '24h' | 'auto' |
showSeconds | Render the seconds column on both time panels. | boolean | false |
step | Minute / second step for the time panels. | number | 1 |
disabled | Block interaction. | boolean | false |
readonly | Inputs are not typeable; trigger icon still opens the overlay. | boolean | false |
CSS variables
Custom properties ngwr/date-picker publishes. Each default below is declared on the component's own selector, so a :root override is shadowed by it — set them on that selector, on a wrapper you scope yourself, or inline on the element. Unlike the BEM class names, these are the supported way to restyle the component.
| Variable | Default | Declared on |
|---|---|---|
--wr-time-picker-bg | var(--wr-color-surface) | .wr-time-picker |
--wr-time-picker-border | var(--wr-color-outline) | .wr-time-picker |
--wr-time-picker-col-width | 3rem | .wr-time-picker |
--wr-time-picker-color | var(--wr-color-on-surface) | .wr-time-picker |
--wr-time-picker-muted | var(--wr-color-on-surface-muted) | .wr-time-picker |