A lightweight, framework-agnostic date picker that ships as a single custom element. Keyboard-first navigation, single / range / multiple selection modes, optional time + datetime modes, and OS-aware dark mode out of the box.
⚠️ Security Notice: This component intentionally allows raw HTML in rendering callbacks and message content to give developers full control over content display. If you display user-generated content, you must sanitize it yourself. See HTML Injection (XSS) Notice for the complete list of affected callbacks and methods.
-
Weekday header labels — customizable at last — The picker gained a
weekdayNamesoverride (property andDatePickerOptions), closing a long-standing asymmetry with the already-overridablemonthNames(issue #5). Previously weekday headers were locked to whateverIntlproduced for the locale; now you can supply your own seven short labels. The array is indexed by day-of-week —[0]=Sunday …[6]=Saturday, matchingDate.getDay()— andweekStartDayonly rotates the display, never the mapping, so labels stay glued to the correct days at any start-of-week (Monday, Wednesday-first business weeks, whatever). -
Declarative name overrides in plain HTML — Both
month-namesandweekday-namesare now settable as pipe-delimited attributes (weekday-names="Ne|Po|Út|St|Čt|Pá|So"), not just via JS properties — no script needed for static localization.month-namesis indexed[0]=January …[11]=December (matchesDate.getMonth()). Both are dual-path: the explicitly-set property wins when both an attribute and a property are present, and changes route through the surgicalupdateOptionspath so they re-render without a full picker rebuild. -
Malformed name lists fail loudly, not silently — A pipe list without exactly 12 / 7 non-empty segments is now ignored (locale names used as fallback) and emits a
console.warnnaming the offending attribute and segment count — so a typo'd override surfaces in the console instead of silently doing nothing or crashing on a bad index.
-
Scoped read-only lock —
lock()/unlock()/toggleLock()— A new imperative API freezes user interaction while keeping the selected value fully readable, unlikedisabled(which greys the input out and blocks the whole field). The motivating flow: the user picks a range, a custom confirm button calls the server, and on success you calllock()so the confirmed range can no longer be edited. Locks are enforced at every user-interaction choke point acrossdate-picker.ts,date-picker-navigation.ts,date-picker-interaction.ts, anddate-picker-ui.ts, so day clicks, drag-to-adjust, typed input, keyboard month-crossing, and popover re-opening all respect it. -
Four independent lock aspects —
selection,navigation,actions,open—lock()with no argument freezes everything;lock(aspect | aspect[])freezes a subset via the new exportedLockAspecttype.selectioncovers day/drag/typed/time picks and flips the<input>toreadOnly;navigationcovers</>, PageUp/Down, Ctrl+arrows and the rolling year/month selector;actionscovers Apply and custom buttons;openblocks re-opening the popover but never blocks closing, so a locked picker is never a keyboard trap. Partial locks compose —lock(['selection','actions'])freezes the range and buttons while leaving</>live so a user can still browse other months after confirming. -
Programmatic API stays live — the lock gates the end user, not your code — Selection setters (
selectedRanges = …),clearSelection(), and the navigation methods all keep working while locked, exactly like areadOnly<input>is still settable from JS. This keeps server-driven flows (confirm, amend, reset) fully scriptable against a locked picker. -
Declarative
readonlyattribute + greyed affordance —<web-daterangepicker readonly>orpicker.readonly = trueapplies a full lock and survives the attribute-driven rebuild; it reads backtrueonly when every aspect is locked. Locked regions grey out like disabled controls via the new--drp-opacity-lockedtoken (default0.6, inherited from--drp-opacity-disabled; set to1to disable greying), driven by per-aspect root modifiers.drp__picker--locked-{aspect}.
@keenmate/web-daterangepicker is a date picker that runs as a Web
Component — drop the <web-daterangepicker> element into any HTML
page (or any framework that speaks DOM: React, Vue, Svelte, Angular,
plain HTML) and it works. No JavaScript framework integration to
build. No virtualized rendering. No copy-pasted theme tokens to keep
in sync.
It covers the full date-picker matrix in one component: single date
or date range selection; single / range / multiple modes;
date, time, or datetime picker modes with four time-display
UIs (rolls, Material clock face, iOS wheel, iOS compact pills);
multi-month grids; input masking with progressive auto-format
as the user types; drag-to-adjust for range endpoints; disabled
dates with five different range-traversal strategies; special
dates with badges, tooltips, and dayClass / badgeClass
discriminator hooks; localized week start, month names, and labels;
and async validation via beforeDateSelectCallback with optional
bulk metadata loading per month.
What makes it different from the rest of the date-picker ecosystem:
- No JavaScript dark-mode detection. The picker reacts to five
separate CSS-only signals (OS
prefers-color-scheme, page-levelcolor-scheme, framework class on an ancestor, per-instancedata-themeattribute, explicitlightoverride) vialight-dark()and:host-context(...)selectors. Set whichever your page already uses; it flips automatically. - Theme Designer integration. Optional. If you use the KeenMate
Theme Designer at theme-designer.keenmate.dev,
the picker reads the cross-component
--base-*taxonomy so one theme drives every KeenMate component on the page. - One component, every selection shape. Date / range / multiple / time / datetime in one custom element with one consistent API — switch by changing one attribute, not by installing a different package.
- First-class keyboard. Arrow keys, Home / End, Ctrl+Home / End,
Tab between month columns,
Tto jump to today. Modal mode blurs the input so the mobile soft-keyboard collapses.
For a more honest accessibility audit including current ARIA gaps,
see docs/accessibility.md.
npm install @keenmate/web-daterangepickerrc releases are tagged separately — install with @rc or pin the
exact version:
npm install @keenmate/web-daterangepicker@rc<!-- Single date -->
<web-daterangepicker
selection-mode="single"
date-format-mask="YYYY-MM-DD"
placeholder="Select date"
></web-daterangepicker>
<!-- Date range -->
<web-daterangepicker
selection-mode="range"
date-format-mask="YYYY-MM-DD"
visible-months-count="2"
placeholder="Select date range"
></web-daterangepicker>// Import the component (registers <web-daterangepicker> + injects styles)
import '@keenmate/web-daterangepicker';
const picker = document.querySelector('web-daterangepicker');
// Listen for date selection
picker.addEventListener('date-select', (e) => {
console.log('Selected:', e.detail.formattedValue);
console.log('Date object:', e.detail.date);
console.log('Range:', e.detail.dateRange);
});
// Programmatic API
picker.show();
picker.hide();
picker.toggle();
picker.clearSelection();
picker.setInputValue('2025-11-15');Using the DateRangePicker class directly (without the custom
element) is fully supported — see docs/usage.md
for the CSS-loading caveats.
Modern browsers with Web Components and CSS color-mix() support:
- Chrome / Edge 111+
- Firefox 113+
- Safari 16.2+
Follows the BlissFramework component guidelines
for structure, theming, color-scheme, and accessibility. See
VALIDATION-NOTES.md for the accepted-deviation
register and docs/theming.md for the four-contract
theming model.
The following callbacks and methods allow raw HTML injection and are intentionally not XSS-safe. This gives developers full control over rendering but requires sanitizing untrusted data:
| Callback / Method | Output used in | Risk |
|---|---|---|
showMessage(html) |
Message area (innerHTML) | HTML injection |
renderDayCallback |
Day cells (innerHTML) | HTML injection |
renderDayContentCallback |
Day cells (innerHTML) | HTML injection |
getDateMetadataCallback (badgeText, dayTooltip) |
Badges / tooltips (innerHTML) | HTML injection |
formatSummaryCallback |
Summary display (innerHTML) | HTML injection |
getMonthHeaderCallback |
Month headers (innerHTML) | HTML injection |
getUnifiedHeaderCallback |
Unified header (innerHTML) | HTML injection |
customStylesCallback |
Style tag (textContent) | CSS injection |
actionButtons[].label |
Button labels (innerHTML) | HTML injection |
Safe callbacks (output is escaped or used as data):
beforeDateSelectCallback,beforeMonthChangedCallback(return action objects)onSelect,onChange(event handlers)getDateMetadataCallback(isDisabled,dayClass,badgeClass— CSS class names only)
If displaying user-generated content, sanitize it before passing it to these callbacks or methods.
The shipped CSS includes default styles for a small set of class names
the picker doesn't emit itself — they're applied by your code via the
dayClassMember / badgeClassMember callbacks (or the equivalent
getDateMetadataCallback return shape). The component ships compound
selectors (.drp__day.holiday, .drp__day.event,
.drp__badge-cell.badge-count, .drp__badge-cell.badge-number,
.drp__badge-cell.badge-text) as ready-to-use hooks for these common
conventions so the most frequent decoration cases work without writing
any CSS.
This means a few CSS classes inside the shadow root don't follow
strict BEM (.<prefix>__element--modifier) — they're consumer-data
values used as discriminators next to a BEM block, not
component-emitted modifiers. The BEM block (.drp__day,
.drp__badge-cell) carries the component scope; the discriminator
carries the data convention.
If your data uses different class names, just supply them via the callback and provide your own CSS — the defaults won't fight you. To retheme the existing defaults without rewriting the CSS, override the backing variables:
- Holiday cell:
--drp-holiday-color,--drp-holiday-bg-opacity,--drp-holiday-hover-bg-opacity - Event cell:
--drp-event-color,--drp-event-bg-opacity,--drp-event-hover-bg-opacity - Badge types:
--drp-badge-number-{bg,color},--drp-badge-count-{bg,color},--drp-badge-text-{bg,color}
These variables are part of the public theming surface (added in
v1.6.0; see CHANGELOG.md).
See CHANGELOG.md for version history and migration
guides.
MIT — see LICENSE if present, otherwise the MIT terms
apply by default per package.json.
Extracted from the Pure Admin design system.