0.1.0

Date Range Picker

Date range pickers collect a start and end date in one control, such as bookings, reporting periods, and availability filters.

Try it

08012026
08142026

Usage

Compose the two inputs, separator, calendar trigger, popup, and calendar as children of RuiDateRangePicker. When no children are supplied, RuiDateRangePicker renders this same composition for you. Pass value as start/end ISO dates and give each input its own accessible name.

The default composition accepts startLabel and endLabel view props for the two segment editors. Use those props when the default structure is sufficient; set aria-label directly on RuiDateRangePickerStartInput and RuiDateRangePickerEndInput when composing the children yourself.

Typing one side does not commit the range. The host value updates when both start and end are valid ISO dates, or when a previously committed range is cleared.

import {
  RuiDateRangePicker,
  RuiDateRangePickerCalendar,
  RuiDateRangePickerControl,
  RuiDateRangePickerEndInput,
  RuiDateRangePickerInputs,
  RuiDateRangePickerPopover,
  RuiDateRangePickerSeparator,
  RuiDateRangePickerStartInput,
  RuiDateRangePickerToggle,
} from '@ecopages/radiant-ui/date-range-picker';
import { RuiField } from '@ecopages/radiant-ui/field';
import { RuiLabel } from '@ecopages/radiant-ui/label';
 
<RuiField name="tripDates">
  <RuiLabel>Trip dates</RuiLabel>
  <RuiDateRangePicker value="2026-08-01/2026-08-14" visibleMonths={2}>
    <RuiDateRangePickerControl>
      <RuiDateRangePickerInputs>
        <RuiDateRangePickerStartInput aria-label="Start date" />
        <RuiDateRangePickerSeparator />
        <RuiDateRangePickerEndInput aria-label="End date" />
      </RuiDateRangePickerInputs>
      <RuiDateRangePickerToggle />
    </RuiDateRangePickerControl>
    <RuiDateRangePickerPopover>
      <RuiDateRangePickerCalendar />
    </RuiDateRangePickerPopover>
  </RuiDateRangePicker>
</RuiField>

Custom markup

import '@ecopages/radiant-ui/date-range-picker';
import '@ecopages/radiant-ui/date-input';
import '@ecopages/radiant-ui/calendar';
 
<rui-date-range-picker value="2026-08-01/2026-08-14">
  <div class="rui-date-range-picker__group">
    <rui-date-input data-range-start aria-label="Start date"></rui-date-input>
    <rui-date-input data-range-end aria-label="End date"></rui-date-input>
    <button type="button" data-range-trigger data-ref="trigger"></button>
  </div>
  <div data-range-popover data-ref="popover" hidden>
    <rui-calendar data-range-calendar />
  </div>
</rui-date-range-picker>

Calendar input

Use the calendar button to select the start and end dates. After the first date is selected, the popup stays open for the second date and closes when the range is complete. Focus moves to the selected start date or the first available date when the popup opens.

mmddyyyy
mmddyyyy

Two-month view

Set visibleMonths to 2 so users can span month boundaries without extra navigation clicks.

Separate form names

Use startName and endName when the backend expects discrete fields instead of a combined range value.

Theming

Date Range Picker surfaces map to semantic surface roles, never Tailwind palette steps:

PartCSS roles
Control group (rui-date-range-picker__group)border, background, radius-control; focus-ring ring on focus-within
Input texton-background, --text-control, --space-control-*
Placeholderon-surface + opacity-muted
Separatoron-surface
Disabledopacity-disabled
Popup (rui-date-range-picker__popover)popover surface + shadow-overlay (via rui-popover)

Geometry shares control tokens (--size-control-*, --radius-control, --space-control-*) with RuiInput and RuiButton so form rows align. Override at the theme layer, not in component CSS.

API

RuiDateRangePicker is a custom element (<rui-date-range-picker>) that coordinates its light-DOM inputs, toggle button, and range calendar popover. It is not form-associated; start-name / end-name land on the nested rui-date-input hosts. RuiField name is the store key for the combined start/end value.

Attributes

AttributeTypeDefaultDescription
valuestring''Canonical YYYY-MM-DD/YYYY-MM-DD range.
minstring''Earliest selectable ISO date.
maxstring''Latest selectable ISO date.
disabledbooleanfalseDisable both inputs and the calendar.
read-onlybooleanfalseDisable editing while keeping values visible.
localestring''BCP 47 locale tag, or comma-separated fallback list.
start-namestring''Form name on the nested start rui-date-input.
end-namestring''Form name on the nested end rui-date-input.
namestring''Reserved; use start-name / end-name for discrete fields.
visible-monthsnumber2Month grids shown in the range calendar popover.

Light-DOM contract

TargetRequiredHost writesAuthor owns
[data-range-start]yesname, locale, min, max, disabled, read-only; value only for a complete rangenested rui-date-input
[data-range-end]yessame as startnested rui-date-input
[data-range-trigger]yesaria-expanded, disabledtoggle (data-ref="trigger")
[data-range-popover]yeshiddenpopup (data-ref="popover")
[data-range-calendar]yesselection-mode="range", value, …nested rui-calendar

Nested host: rui-calendar at [data-range-calendar] — parent queries [data-calendar-day] inside it.

Events

EventDetailDescription
rui-change{ value, start, end }Fired when a valid range is committed, or when a committed range is cleared.

JSX view props

These props configure the default child composition rendered by the RuiDateRangePicker JSX view. They are not custom-element attributes.

PropTypeDefaultDescription
startLabelstringStart dateAccessible name for the default start-date input.
endLabelstringEnd dateAccessible name for the default end-date input.

View helpers

ComponentTarget stampedNotes
RuiDateRangePickerControl—Bordered control row
RuiDateRangePickerInputs—Start / end row
RuiDateRangePickerStartInput[data-range-start]Give it an accessible name
RuiDateRangePickerSeparator—Visual separator
RuiDateRangePickerEndInput[data-range-end]Give it an accessible name
RuiDateRangePickerToggle[data-range-trigger]Opens calendar popup
RuiDateRangePickerPopover[data-range-popover]Popup shell
RuiDateRangePickerCalendar[data-range-calendar]Range-mode rui-calendar

CSS classes

Public BEM classes on the composed light-DOM surface (documented via @cssclass on RuiDateRangePicker):

ClassDescription
.rui-date-range-pickerRoot surface.
.rui-date-range-picker__groupBordered control-height row wrapping inputs and toggle.
.rui-date-range-picker__valuesStart / end input row.
.rui-date-range-picker__inputHost for nested rui-date-input.
.rui-date-range-picker__separatorEm dash between the inputs.
.rui-date-range-picker__popoverRange calendar popup shell (rui-popover / rui-floating).

Theme roles

PartCSS variables consumed
Control group--border, --background, --radius-control, --focus-ring
Input text--on-background, --text-control, --space-control-x, --space-control-y
Placeholder--on-surface, --opacity-muted
Separator--on-surface
Disabled--opacity-disabled
Popup--shadow-overlay (via rui-popover)

Accessibility

  • Give both segment groups distinct accessible names (aria-label or startLabel / endLabel on the default composition).
  • Segments use spinbutton semantics with numeric keypads on touch devices.
  • The calendar trigger opens the popup and moves focus to the selected start date or first available date; arrow keys move between days, Page Up / Page Down change months, and Escape closes the popup.
  • Surface validation errors for incomplete ranges before form submission.