ng-date-hour-range-selector
May 22, 2026 · View on GitHub
A flexible Angular date / date-time range selector built on Angular CDK Overlay. Supports predefined range shortcuts, time picking, localization, and full CSS customization — with zero third-party date-library dependency.
Features
- Date and time range selection, or date-only mode
- 12-hour (AM/PM) and 24-hour time formats
- Configurable minute step
- Optional manual time inputs for direct hour/minute editing
- Sidebar with predefined range shortcuts (Today, Yesterday, This/Last Week…)
- Configurable calendar icon position (
left,right, or hidden) - Optional reset button in the sidebar
- Works as a
ControlValueAccessor— drop into any reactive or template-driven form - Fully localizable via the
PICKER_LOCALEinjection token - Pre-select a range on load via the
initialRangeinput nextRange()/previousRange()/setRange()public API methods- No third-party date library required
- Built on Angular CDK Overlay
- Standalone components — no NgModule needed
- A directive variant (
drsDateRangePicker) to attach the picker to any<input> - Accessible: keyboard navigation, ARIA attributes, meets WCAG AA
- Dark theme included; fully themeable via CSS custom properties
Requirements
| Dependency | Version |
|---|---|
| Angular | >=19.0.0 |
@angular/cdk | >=19.0.0 |
Installation
npm install ng-date-hour-range-selector @angular/cdk
Import the built-in dark theme once in your global styles:
@use 'ng-date-hour-range-selector/styles/theme';
Add provideAnimationsAsync() to your application config:
import { provideAnimationsAsync } from '@angular/platform-browser/animations/async';
export const appConfig: ApplicationConfig = {
providers: [provideAnimationsAsync()],
};
Quick start
Component (<drs-date-range-picker>)
import { DateRange, DateRangePickerComponent } from 'ng-date-hour-range-selector';
import { FormControl, ReactiveFormsModule } from '@angular/forms';
@Component({
imports: [ReactiveFormsModule, DateRangePickerComponent],
template: `
<drs-date-range-picker
[formControl]="rangeControl"
(rangeChange)="onRangeChange($event)"
ariaLabel="Select date range"
/>
`,
})
export class MyComponent {
readonly rangeControl = new FormControl<DateRange | null>(null);
onRangeChange(range: DateRange | null): void {
console.log(range?.start, range?.end);
}
}
Directive ([drsDateRangePicker])
Attach the picker to any <input> element:
import { DateRangePickerDirective } from 'ng-date-hour-range-selector';
import { FormControl, ReactiveFormsModule } from '@angular/forms';
@Component({
imports: [ReactiveFormsModule, DateRangePickerDirective],
template: `
<input
drsDateRangePicker
[formControl]="rangeControl"
(rangeChange)="onRangeChange($event)"
ariaLabel="Select date range"
/>
`,
})
export class MyComponent {
readonly rangeControl = new FormControl<DateRange | null>(null);
onRangeChange(range: DateRange | null): void {
console.log(range?.start, range?.end);
}
}
Component API — <drs-date-range-picker>
Inputs
| Input | Type | Default | Description |
|---|---|---|---|
showTime | boolean | true | Show the time-picker section |
timeFormat | '12h' | '24h' | '24h' | 12-hour (AM/PM) or 24-hour format |
minuteStep | number | 1 | Minute increment step |
allowManualTimeInput | boolean | false | Enable editable hour/minute text inputs in the time picker |
weekStartsOn | 0 | 1 | 1 | First day of week — 0 Sunday, 1 Monday |
predefinedRanges | PredefinedRange[] | built-in | Sidebar shortcut definitions |
minDate | Date | — | Minimum selectable date (inclusive) |
maxDate | Date | — | Maximum selectable date (inclusive) |
position | ConnectedPosition[] | bottom-start | CDK Overlay connected positions |
showResetButton | boolean | true | Show or hide the reset button in the sidebar |
calendarIcon | 'left' | 'right' | 'hidden' | 'right' | Position of the calendar icon in the trigger button, or hide it |
showApplyButton | boolean | false | Show an Apply button inside the overlay that closes it when clicked |
closeOnSelect | boolean | true | Automatically close the overlay after a complete range is selected or pre-defined |
rangeMatchMode | 'day' | 'exact' | 'day' | How selected ranges are matched to predefined labels — 'day' ignores time, 'exact' requires identical timestamps |
emitOn | 'change' | 'close' | 'change' | Controls when rangeChange is emitted. 'change' — emit immediately on every date/time selection (default). 'close' — defer emission until the overlay is closed or Apply is clicked; reset always emits immediately. |
initialRange | DateRange | PredefinedRange | — | Range or predefined-range factory to pre-select on component load |
ariaLabel | string | 'Select date range' | Accessible label for the trigger button |
Output
| Output | Payload | Description |
|---|---|---|
rangeChange | DateRange | null | Emitted when a complete range is committed or cleared |
Public methods
| Method | Description |
|---|---|
nextRange() | Advance the current range forward by its own duration (e.g. Mon–Sun → next Mon–Sun) |
previousRange() | Rewind the current range backward by its own duration |
setRange(range, emitEvent?) | Programmatically set DateRange | null; pass emitEvent: false to suppress rangeChange and CVA onChange |
ControlValueAccessor
DateRangePickerComponent implements ControlValueAccessor, so it works with both [formControl] and [(ngModel)]:
<!-- Reactive forms -->
<drs-date-range-picker [formControl]="rangeControl" />
<!-- Template-driven -->
<drs-date-range-picker [(ngModel)]="range" />
Directive API — [drsDateRangePicker]
The directive exposes the same inputs and output as the component, except calendarIcon (which is specific to the component's trigger button).
<input drsDateRangePicker [formControl]="ctrl" [showTime]="false" />
Models
interface DateRange {
start: Date;
end: Date;
}
interface PredefinedRange {
/** Label shown in the sidebar */
label: string;
/** Factory function — called on each click to produce a fresh range */
range: () => DateRange;
}
Global configuration — PICKER_CONFIG
Override defaults for every picker in your application (or a specific feature):
import { PICKER_CONFIG } from 'ng-date-hour-range-selector';
// app.config.ts
providers: [
{
provide: PICKER_CONFIG,
useValue: { showTime: false, timeFormat: '12h', weekStartsOn: 0 },
},
];
Individual component/directive inputs always take precedence over the global config.
PickerConfig interface
| Property | Type | Default | Description |
|---|---|---|---|
showTime | boolean | true | Show the time-picker section |
timeFormat | '12h' | '24h' | '24h' | Hour format |
minuteStep | number | 1 | Minute increment step |
allowManualTimeInput | boolean | false | Enable editable hour/minute text inputs in the time picker |
weekStartsOn | 0 | 1 | 1 | First day of week |
predefinedRanges | PredefinedRange[] | built-in | Override all shortcuts globally |
minDate | Date | — | Global minimum date |
maxDate | Date | — | Global maximum date |
position | ConnectedPosition[] | bottom-start | CDK overlay positions |
showResetButton | boolean | true | Show or hide the reset button |
calendarIcon | 'left' | 'right' | 'hidden' | 'right' | Calendar icon position in the trigger button |
showApplyButton | boolean | false | Show an Apply button inside the overlay |
closeOnSelect | boolean | true | Automatically close the overlay after a complete range is selected or pre-defined |
rangeMatchMode | 'day' | 'exact' | 'day' | How selected ranges are matched to predefined labels — 'day' ignores time, 'exact' requires identical timestamps |
emitOn | 'change' | 'close' | 'change' | Controls when rangeChange is emitted. 'change' — emit immediately on every date/time selection. 'close' — defer emission until the overlay is closed or Apply is clicked; reset always emits immediately. |
Localization — PICKER_LOCALE
Provide a PickerLocale object to translate every visible string:
import { PICKER_LOCALE, PickerLocale } from 'ng-date-hour-range-selector';
const ptBrLocale: PickerLocale = {
daysOfWeek: ['Do', 'Se', 'Te', 'Qu', 'Qi', 'Se', 'Sa'],
monthNames: [
'Janeiro',
'Fevereiro',
'Março',
'Abril',
'Maio',
'Junho',
'Julho',
'Agosto',
'Setembro',
'Outubro',
'Novembro',
'Dezembro',
],
am: 'AM',
pm: 'PM',
startTime: 'Início:',
endTime: 'Fim:',
reset: 'Limpar',
apply: 'Aplicar',
placeholder: 'Selecione um período',
formatRange: (start, end) =>
`${start.toLocaleDateString('pt-BR')} – ${end.toLocaleDateString('pt-BR')}`,
formatRangeWithTime: (start, end) => {
const fmt = (d: Date) =>
`${d.toLocaleDateString('pt-BR')} ${String(d.getHours()).padStart(2, '0')}:${String(d.getMinutes()).padStart(2, '0')}`;
return `${fmt(start)} – ${fmt(end)}`;
},
};
providers: [{ provide: PICKER_LOCALE, useValue: ptBrLocale }];
formatRangeWithTimeis optional. WhenshowTimeistrueand it is provided, the trigger will include times in the display value. Falls back toformatRangeif omitted.
PickerLocale interface
| Property | Type | Description |
|---|---|---|
daysOfWeek | [string × 7] | Abbreviated day labels — Sunday first |
monthNames | [string × 12] | Full month names — January first |
am | string | AM toggle label |
pm | string | PM toggle label |
startTime | string | Label above the start time picker |
endTime | string | Label above the end time picker |
reset | string | Reset/clear button label |
apply | string | Apply button label (used when showApplyButton is true) |
placeholder | string? | Trigger placeholder when no range is selected |
formatRange | (start: Date, end: Date) => string | Formats the selected range for display in the trigger |
formatRangeWithTime | (start: Date, end: Date) => string | Formats the range including time; falls back to formatRange if omitted |
Predefined ranges
The sidebar shows these built-in shortcuts by default:
- Today
- Yesterday
- This week / Last week
- This month / Last month
- This quarter / Last quarter
Replace them per-picker via the predefinedRanges input, or globally via PICKER_CONFIG:
import { PredefinedRange } from 'ng-date-hour-range-selector';
const customRanges: PredefinedRange[] = [
{
label: 'Last 7 days',
range: () => {
const end = new Date();
const start = new Date();
start.setDate(start.getDate() - 6);
start.setHours(0, 0, 0, 0);
end.setHours(23, 59, 59, 0);
return { start, end };
},
},
{
label: 'Last 30 days',
range: () => {
const end = new Date();
const start = new Date();
start.setDate(start.getDate() - 29);
start.setHours(0, 0, 0, 0);
end.setHours(23, 59, 59, 0);
return { start, end };
},
},
];
<drs-date-range-picker [predefinedRanges]="customRanges" />
Examples
Date-only picker
<drs-date-range-picker [showTime]="false" />
12-hour format, Sunday start
<drs-date-range-picker timeFormat="12h" [weekStartsOn]="0" />
Calendar icon on the left, no reset button
<drs-date-range-picker calendarIcon="left" [showResetButton]="false" />
Pre-selected range on load
readonly initialRange: PredefinedRange = {
label: 'Last 7 days',
range: () => {
const end = new Date();
const start = new Date();
start.setDate(start.getDate() - 6);
return { start, end };
},
};
<drs-date-range-picker [initialRange]="initialRange" />
Navigate range programmatically
private picker = viewChild(DateRangePickerComponent);
next(): void { this.picker()?.nextRange(); }
prev(): void { this.picker()?.previousRange(); }
Directive on a plain input
<input drsDateRangePicker [formControl]="ctrl" [showTime]="false" />
Theming — CSS custom properties
Import the built-in dark theme and override variables at :root or on specific elements:
@use 'ng-date-hour-range-selector/styles/theme';
// Global accent colour
:root {
--drs-primary: #3b82f6;
}
// Light theme override
drs-date-range-picker.light {
--drs-bg: #ffffff;
--drs-text: #111111;
--drs-border: rgba(0, 0, 0, 0.12);
--drs-hover: rgba(0, 0, 0, 0.06);
--drs-range-bg: rgba(59, 130, 246, 0.12);
}
You can also use the style attribute inline:
<drs-date-range-picker style="--drs-primary: #8b5cf6;" />
Full variable reference
| Variable | Description | Default |
|---|---|---|
--drs-radius | Overlay panel border radius | 10px |
--drs-radius-sm | Button border radius | 5px |
--drs-sidebar-width | Predefined-ranges sidebar width | 148px |
--drs-overlay-z | z-index of the overlay panel | 1000 |
--drs-shadow | Overlay panel box shadow | dark shadow |
--drs-bg | Overlay / modal background | #1e1f22 |
--drs-trigger-bg | Trigger button background | --drs-bg |
--drs-primary | Accent / highlight colour | #f97316 |
--drs-primary-fg | Foreground on accent colour | #ffffff |
--drs-text | Primary text colour | #f1f1f1 |
--drs-text-muted | Dimmed / secondary text | 35% opacity |
--drs-border | Border and divider colour | 8% white |
--drs-hover | Hover background | 7% white |
--drs-range-bg | In-range day background | orange 14% |
--drs-time-bg | Time-picker box background | 5% white |
--drs-font-family | Font family | inherit |
--drs-font-size | Base font size | 0.875rem |
--drs-header-font-size | Month / year header size | 0.9375rem |
--drs-header-font-weight | Month / year header weight | 700 |
--drs-weekday-font-size | Day-of-week label size | 0.6875rem |
--drs-day-font-size | Day number size | 0.875rem |
--drs-sidebar-font-size | Predefined-range label size | 0.875rem |
--drs-time-font-size | Hour / minute number size | 1.375rem |
--drs-ampm-font-size | AM/PM toggle size | 0.9375rem |
--drs-label-font-size | "Start time:" / "End time:" label | 0.8125rem |
--drs-trigger-font-size | Trigger button text size | 0.875rem |
--drs-apply-font-size | Apply button text size | 0.875rem |
Exported API surface
// Components & Directive
export { DateRangePickerComponent } from 'ng-date-hour-range-selector';
export { DateRangePickerDirective } from 'ng-date-hour-range-selector';
export { DateRangePickerPanelComponent } from 'ng-date-hour-range-selector';
export { CalendarComponent } from 'ng-date-hour-range-selector';
export { TimePickerComponent } from 'ng-date-hour-range-selector';
export { PredefinedRangesComponent } from 'ng-date-hour-range-selector';
// Models
export type { DateRange, PredefinedRange } from 'ng-date-hour-range-selector';
export type { PickerConfig } from 'ng-date-hour-range-selector';
export type { PickerLocale } from 'ng-date-hour-range-selector';
export type { TimeValue } from 'ng-date-hour-range-selector';
export type { CalendarCell } from 'ng-date-hour-range-selector';
export type { ResolvedPickerConfig } from 'ng-date-hour-range-selector';
// Tokens & defaults
export { PICKER_CONFIG, DEFAULT_PICKER_CONFIG } from 'ng-date-hour-range-selector';
export { PICKER_LOCALE, DEFAULT_PICKER_LOCALE } from 'ng-date-hour-range-selector';
// Service
export { DateUtilsService } from 'ng-date-hour-range-selector';
Development
# Install dependencies
npm install
# Start the demo app at http://localhost:4200
npm start
# Build the library
npm run build:lib
# Build library + demo
npm run build
# Run unit tests
npm test
License
MIT