A flexible JavaScript date range picker with time, predefined ranges, and locale detection — powered by Luxon.
Install via npm:
npm install @bornova/daterangepicker
Import the picker and its stylesheet from your bundler:
import DateRangePicker from '@bornova/daterangepicker'
import '@bornova/daterangepicker/dist/daterangepicker.css'
Load Luxon 3.x before
the picker. Pin to a specific version (e.g. @bornova/daterangepicker@1.0.0) for production.
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@bornova/daterangepicker/dist/daterangepicker.css" />
<script src="https://cdn.jsdelivr.net/npm/luxon@3/build/global/luxon.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/@bornova/daterangepicker/dist/browser/daterangepicker.min.js"></script>
The constructor accepts a CSS selector or DOM element, an options object, and an optional callback fired on
apply. The callback receives Luxon DateTime objects for start and end, plus the predefined range
label (chosenLabel, or null for a custom range).
<input type="text" id="daterange" />
const picker = new DateRangePicker(
'#daterange',
{
startDate: '01/01/2025',
endDate: '01/31/2025'
},
(start, end, chosenLabel) => {
console.log(start.toISODate(), end.toISODate())
}
)
The picker can attach to any element, not just <input>. When attached to a
<button>, it opens on click and focus. Use the apply event to update the
button label manually, since autoUpdateInput only writes to <input> elements.
const btn = document.querySelector('#btn-trigger')
const picker = new DateRangePicker(btn, { autoUpdateInput: false })
picker.on('apply', (start, end) => {
btn.textContent = `${start.toFormat('MM/dd/yyyy')} - ${end.toFormat('MM/dd/yyyy')}`
})
showVertical is true, number of columns of stacked calendars before
wrapping. Calendars fill each row left-to-right before moving to the next. Capped at
calendarCount; values less than 1 clamp to 1.
true, all calendars move together when navigating months. When false,
each calendar's month can be changed independently.
true, hide the Apply button and apply the selection immediately on pick. When
false, the Apply button is shown and the user must click it to confirm.
<input> on apply.
false, the picker stays open after the user clicks
Apply (or after auto-apply on pick).
false, the picker stays open after the user clicks
Cancel.
true, closing the picker without applying (outside click or Esc) cancels and reverts
the selection. When false, closing keeps the in-progress selection.
drp-today CSS class to today's cell.DateTime and returns true to disable that day.
false to add no class.
locale.format), a JS Date,
or a Luxon DateTime. When omitted the picker opens with no selection and a blank input.
startDate is not provided.true, duration counts both the start and end day (e.g. Apr 7-10 = 4 days). When
false, duration is the elapsed difference (3 days).
"d 'days'". When showTimePicker is enabled
and no value is supplied, the default becomes "d 'days', h 'hrs'". Use
showDuration: false to hide the duration entirely.
{ 'Today': [start, end] }. Grouped:
{ 'Group': { 'Label': [start, end] } }.
startDate/endDate to null,
empties the input, and fires a clear event.
'MM/dd/yyyy'.
['Su','Mo','Tu','We','Th','Fr','Sa'].
0 = Sunday, 1 = Monday, etc.
All scalar options (booleans, numbers, and strings) can be set directly on the element via
data-* attributes using kebab-case — no JavaScript configuration needed. The picker below is
initialised with a single new DateRangePicker('#data-trigger') call; all other options come from
the markup.
<input
id="data-trigger"
data-start-date="01/09/2026"
data-end-date="02/25/2026"
data-show-week-numbers="true"
data-highlight-today="true"
data-open-direction="left"
/>
<script>
new DateRangePicker('#data-trigger')
</script>
Options requiring objects, arrays, or functions — such as ranges, disabledDates,
dayClassFn, and locale — must be passed via the constructor.
All methods operate on the same picker instance.
setStartDate(date)Date, or Luxon
DateTime. Clamps to minDate/maxDate.
setEndDate(date)minDuration distance from start, clamps to
maxDate/maxDuration.
setDateRange(start, end)setStartDate / setEndDate.
updateInput()<input>. Only needed when
autoUpdateInput is false and you want to sync the input manually.
apply()apply event. Hides the picker unless
closeOnApply is false. Equivalent to clicking the Apply button.
clear()startDate and endDate to null, empty the attached input,
and fire the clear event.
cancel()cancel event.
show()hide()toggle()setOptions(options)getDateRange(){ startDate, endDate }.getState(){ isShowing, startDate, endDate, chosenLabel }.
on(event, fn)(startDate, endDate, chosenLabel).
once(event, fn)off(event, fn)on().remove()
All events are dispatched as CustomEvents on the attached element. Listen with
element.addEventListener(name, fn) or use the on() method. For
show/hide, event.detail is the internal picker instance. For
apply/cancel, event.detail is an object containing selection data
(startDate, endDate, and optional chosenLabel). The custom
change event fires only when a committed selection is different from the previously committed
selection; its payload includes old and new values. For clear, event.detail is an
empty object.
showhideapplyevent.detail includes startDate,
endDate, and chosenLabel.
changeevent.detail includes oldStartDate, oldEndDate,
startDate, endDate, chosenLabel, and source (apply
or autoApply).
cancelcancelOnClose is true.
clearstartDate and endDate are set to null and
the input is emptied.