DateRangePicker
DateRangePicker combines one trigger with Calendar range selection. Choose it when a start and end date describe one period; choose two DatePickers only when the dates have separate meaning or rules.
Examples
A complete travel period
The trigger displays the selected start and end date as one value. Selecting the second bound in the composed Calendar completes the range and closes the picker.
Translated range text
labels.rangeSeparator and labels.incompleteRange control the text between a start and end date
and the marker shown while only the start exists. labels.rangeAnnouncement(start, end) controls
the accessible description for a complete range, announced after the field label; include both
formatted dates in its result.
When to use
Use DateRangePicker when one person needs to choose a contiguous local-date period, such as travel, availability or a reporting window.
Reach for something else when:
- Only one date matters — use DatePicker.
- The two dates have distinct labels, validation or workflow steps — use separate DatePickers.
- The date grid belongs permanently in the page — use Calendar directly.
Accessibility
- The trigger is a native button associated with
labelthroughhtmlFor. On desktop it is the Popover trigger, with its non-modal dialog semantics, Escape dismissal and focus return. - The composed Calendar runs in range mode. It retains roving day focus and keyboard navigation; the first selection starts a range, the next finishes it, and a completed range closes the picker.
- The trigger's visible value is a single formatted string: start date,
labels.rangeSeparator, then the end date orlabels.incompleteRange. The field label stays the accessible name. With alabel, a complete range is also described as “X to Y” by default through a visually hidden element linked byaria-describedby. Customize it withlabels.rangeAnnouncement(start, end)in React or therangeAnnouncementAlpine option. Empty and incomplete ranges have no description. errorreplaceshint;disableddisables the trigger.- At viewports of 640px or less, the Popover path becomes a BottomSheet containing Calendar. Its
title and close name come from
label/labels.sheetTitleandlabels.close. It is prose only in these docs because the stage cannot resize its viewport.
API and code
| Name | Type | Required | Description |
|---|---|---|---|
label | string | — | Label rendered above the picker trigger. |
hint | string | — | Helper text rendered below the picker. Replaced by `error` when set. |
error | string | — | Error message that enables error styling and replaces `hint`. |
value | DateRange | null | — | Controlled local-date range, or `null` with no range selected. |
defaultValue | DateRange | {
start: Date | string;
end: Date | string;
} | — | Initial local-date range. ISO `YYYY-MM-DD` strings are accepted for convenience. |
onChange | (range: DateRange) => void | — | Called after the user selects either bound of the local-date range. |
placeholder | string | — | Trigger text when no range is selected. Defaults to `labels.placeholder`. |
min | Date | string | — | Inclusive lower selection limit as a local date or ISO `YYYY-MM-DD` string. |
max | Date | string | — | Inclusive upper selection limit as a local date or ISO `YYYY-MM-DD` string. |
locale | string | — | BCP 47 locale used to format selected dates and compose Calendar. Default: `"en-US"`. |
labels | DateRangePickerLabels | — | Translatable visible and accessible labels, merged over the English defaults. |
disabled | boolean | — | Disables the picker trigger. |
x-data="lyraDateRangePicker({ … })"
| Option | Type | Required | Description |
|---|---|---|---|
defaultValue | {
start?: string | null;
end?: string | null;
} | null | — | Initial ISO `YYYY-MM-DD` bounds. |
locale | string | — | BCP 47 locale used for the trigger's selected-date text. Default: `"en-US"`. |
placeholder | string | — | Trigger text when no range has started. Default: `"Select period"`. |
rangeSeparator | string | — | Text between formatted range bounds. Default: `" – "`. |
incompleteRange | string | — | Text after the separator while the range has no end. Default: `"…"`. |
rangeAnnouncement | (start: string, end: string) => string | — | Accessible description of a complete range, referenced by `aria-describedby`. Includes both formatted dates. Default: `"X to Y"`. |
Without React, compose one trigger with Popover and Calendar classes, then implement range state, focus movement and the mobile sheet alternative yourself:
<div class="lyra-field">
<label class="lyra-label" for="travel-dates">Travel dates</label>
<span class="lyra-datepicker lyra-popover-anchor">
<button
class="lyra-input lyra-datepicker__btn"
id="travel-dates"
type="button"
aria-haspopup="dialog"
aria-expanded="true"
aria-describedby="range-announcement"
>
<span>08/11/2026 – 08/15/2026</span>
</button>
<span id="range-announcement" class="lyra-visually-hidden">08/11/2026 to 08/15/2026</span>
<div
class="lyra-popover lyra-popover--bottom lyra-popover--align-start"
role="dialog"
aria-label="Date range picker"
>
<div class="lyra-cal">
<button class="lyra-cal__day lyra-cal__day--selected" type="button" aria-pressed="true">
11
</button>
<button class="lyra-cal__day lyra-cal__day--in-range" type="button">12</button>
<button class="lyra-cal__day lyra-cal__day--selected" type="button" aria-pressed="true">
15
</button>
</div>
</div>
</span>
</div><lyra:date-range-picker> Generated from lyra-ds/blade v0.10.0.
The behavior comes from lyraDateRangePicker() — install @lyra-ds/alpine and see the HTML + Alpine tab.
| Prop | Default | Required | Example values |
|---|---|---|---|
label | null | — | 0 Period |
hint | null | — | — |
error | null | — | — |
defaultValue | null | — | — |
placeholder | null | — | — |
min | null | — | — |
max | null | — | — |
locale | 'en-US' | — | — |
labels | [] | — | — |
disabled | false | — | — |
name | null | — | — |
<lyra:date-range-picker
name="reporting_period"
label="Reporting period"
:default-value="['start' => '2026-03-01', 'end' => '2026-03-31']"
min="2026-01-01"
max="2026-12-31"
locale="en-US"
/>