The usability-first date range picker.
Classic range pickers give you one calendar and expect two clicks. Many users can't tell which date they're picking, From or To, or how to change one end without starting over.
RangeSelector shows the range as two consecutive inputs, From → To. You always see which date you're editing, you can change either end on its own, and you can type a date instead of hunting for it in a calendar.
dd/mm/yyyy over a __/__/____ guide. When From is complete, the cursor moves to To.min/max are marked red and block form submission. If From is after To, they are swapped.time: true adds an optional hh:mm to each date (no time means the whole day); time: 'only' is a range of hours, with no calendar.The CSS and the JS. No dependencies.
A div with data-range-selector inside your form. It initializes itself.
The form submits desde (from) and hasta (to) as YYYY-MM-DD. Either one may be empty.
Initial values, limits and the text for when there are no dates. The rest is under API.
Below each example is the value that would be submitted.
Initializes itself. Two hidden inputs are submitted: desde and hasta, in ISO format.
One year ago → today. Days outside are disabled and typing them is an error. Empty means “This month”.
PeriodFrom only: “October 1st onwards”. The empty side reads “No limit” and is submitted empty.
CreatedPresets are configurable: here, the quarters of the year.
QuarterNo presets, a single month and custom colors via the CSS variables. In a narrow container the label moves above the date.
Due datetime: true: the time is optional. Typing only the date means the whole day (From 00:00, To 23:59). Pick the days, then adjust each end's time in the list next to the calendar.
The same control with no initial value. Type 011020260830, or open the calendar, pick two days and set their times.
time: 'only': a range of hours. The button opens the time list: From first, then To. Empty means “all day”.
An invalid date (try 31022026) stops the submit and focuses the field. The hidden inputs are submitted with the form.
Change the options and theme to see the result. Events are logged below.
Everything configurable from JS can also be set via data-*. Dates go in as 'YYYY-MM-DD' or Date and come out as ISO. Options have English aliases: from/to, nameFrom/nameTo, empty, noLimit.
| Option | data-attribute | Default | |
|---|---|---|---|
nameDesde / nameHasta | data-name-desde / -hasta | 'desde' / 'hasta' | name of the submitted hidden inputs |
desde / hasta | data-desde / data-hasta | null | Initial value |
min / max | data-min / data-max | null | Limits |
vacio | data-vacio | from locale | What choosing no range means |
sinLimite | data-sin-limite | from locale | Empty side of an open range |
presets | data-presets="false" | true | true, false or [{label, desde, hasta}] |
months | data-months | 2 | Visible months (1 on mobile) |
autoAdvance | data-auto-advance="false" | true | From complete → To |
time | data-time="true" / "only" | false | false date · true date + optional time (YYYY-MM-DDTHH:mm) · 'only' time (HH:mm) |
step | data-step | 30 | Minutes between the times offered in the panel |
onChange | — | null | fn({desde, hasta}) |
locale | data-locale | 'es' | 'es', 'en' or { base, …texts } |
| Method | |
|---|---|
getValue() | {desde, hasta, from, to} as ISO |
setValue(d, h) | ISO, Date or null |
clear() | Clears the range |
setLimits(min, max) | Changes the limits |
validate() | false if there are errors |
open(campo?, modo?) / close() | Panel; modo: 'rango' or 'campo' |
destroy() | Removes listeners and markup |
RangeSelector.init(scope?) | Initializes [data-range-selector] |
| Keyboard | |
|---|---|
| digits | Mask; From complete → To |
| ↓ | Opens that field's calendar |
| ⌫ in empty To | Back to From |
| ← → at the edges | Moves between fields |
| arrows · Home End | Calendar: day / week |
| PgUp PgDn | Previous / next month |
| Enter · Esc | Pick the day · close |
A rangeselector:change event (bubbles) fires with event.detail = {desde, hasta} every time the value changes.