Skip to main content

Date range picker

Let people choose start and end dates with a date range picker and calendar controls.

Examples

Breakpoints

Mobile

320px

Mobile Lg

480px

Tablet

640px

Desktop

1024px
MM/DD/YYYY, September 1 through October 31, 2026
MM/DD/YYYY, September 1 through October 31, 2026; no earlier than the start date
date-range-picker-demo.tsx
import { DatePicker } from '@uswds-tailwind/react'
const minDate = new Date(2026, 8, 1)
const maxDate = new Date(2026, 9, 31)
const initialRange = [new Date(2026, 8, 15), new Date(2026, 8, 20)]
export default function DateRangePickerDemo() {
return (
<DatePicker.Root
selectionMode="range"
ids={{ input: index => index === 0 ? 'event-start' : 'event-end' }}
min={minDate}
max={maxDate}
defaultValue={initialRange}
className="max-w-mobile-lg flex-col gap-6"
>
<div>
<label htmlFor="event-start" className="block">Start date</label>
<div id="event-start-hint" className="text-gray-50">MM/DD/YYYY, September 1 through October 31, 2026</div>
<DatePicker.Control>
<DatePicker.Input index={0} id="event-start" name="eventStart" aria-describedby="event-start-hint" />
<DatePicker.Trigger index={0} />
</DatePicker.Control>
</div>
<div>
<label htmlFor="event-end" className="block">End date</label>
<div id="event-end-hint" className="text-gray-50">MM/DD/YYYY, September 1 through October 31, 2026; no earlier than the start date</div>
<DatePicker.Control>
<DatePicker.Input index={1} id="event-end" name="eventEnd" aria-describedby="event-end-hint" />
<DatePicker.Trigger index={1} />
</DatePicker.Control>
</div>
<DatePicker.Content>
<DatePicker.View view="day">
{({ api }) => (
<>
<DatePicker.ViewControl>
<DatePicker.PrevTrigger unit="year" />
<DatePicker.PrevTrigger />
<div className="flex grow justify-center">
<DatePicker.ViewTrigger view="month" />
<DatePicker.ViewTrigger view="year" />
</div>
<DatePicker.NextTrigger />
<DatePicker.NextTrigger unit="year" />
</DatePicker.ViewControl>
<DatePicker.Table>
<DatePicker.TableHead>
<DatePicker.TableRow>
{api.weekDays.map((day, index) => (
<DatePicker.TableHeader key={day.long} day={day} index={index} />
))}
</DatePicker.TableRow>
</DatePicker.TableHead>
<DatePicker.TableBody>
{api.weeks.map((week, row) => (
<DatePicker.TableRow key={row}>
{week.map(cell => (
<DatePicker.TableCell key={cell.getTime()} value={cell}>
<DatePicker.TableCellTrigger value={cell}>
{cell.getDate()}
</DatePicker.TableCellTrigger>
</DatePicker.TableCell>
))}
</DatePicker.TableRow>
))}
</DatePicker.TableBody>
</DatePicker.Table>
</>
)}
</DatePicker.View>
<DatePicker.View view="month">
{({ api }) => (
<DatePicker.Table>
<DatePicker.TableBody>
{api.monthRows.map((row, rowIndex) => (
<DatePicker.TableRow key={rowIndex}>
{row.map(month => (
<DatePicker.TableCell key={month}>
<DatePicker.TableCellTrigger value={month}>
{api.monthLabels[month]}
</DatePicker.TableCellTrigger>
</DatePicker.TableCell>
))}
</DatePicker.TableRow>
))}
</DatePicker.TableBody>
</DatePicker.Table>
)}
</DatePicker.View>
<DatePicker.View view="year">
{({ api }) => (
<>
<DatePicker.PrevTrigger view="year" />
<DatePicker.Table>
<DatePicker.TableBody>
{api.yearRows.map((row, rowIndex) => (
<DatePicker.TableRow key={rowIndex}>
{row.map(year => (
<DatePicker.TableCell key={year}>
<DatePicker.TableCellTrigger value={year}>
{year}
</DatePicker.TableCellTrigger>
</DatePicker.TableCell>
))}
</DatePicker.TableRow>
))}
</DatePicker.TableBody>
</DatePicker.Table>
<DatePicker.NextTrigger view="year" />
</>
)}
</DatePicker.View>
</DatePicker.Content>
<DatePicker.Status />
</DatePicker.Root>
)
}

Range endpoints

Use one DatePicker.Root with selectionMode="range". Group each input and trigger in a DatePicker.Control. Set index={0} on the start Input and Trigger and index={1} on the end Input and Trigger. There is no separate DateRangePicker export.

Give each endpoint its own visible label, format hint, and form name. The start cannot be after the end, and the end cannot be before the start. Root bounds further limit both fields and the shared calendar.

min and max use Date values; defaultValue is an array of dates. Create calendar dates with local year, month, and day values, such as new Date(2026, 8, 28) for September 28. onValueChange receives the selected Date values and their ISO strings in valueAsString. It preserves start and end slots, using undefined in value and an empty string in valueAsString for an unset endpoint. Selecting a date closes the calendar; use the other endpoint’s trigger to edit it. The visible fields use MM/DD/YYYY.

Component API

Use DatePicker with selectionMode="range". Set index={0} on the start Input and Trigger and index={1} on the end Input and Trigger. Both endpoints share one calendar. The visible fields use MM/DD/YYYY; callback valueAsString uses ISO YYYY-MM-DD.

Root<div>

Owns selected dates, bounds, and calendar state.

PropTypeDescription
selectionModerange (required for this pattern)Enables range selection.
defaultValue(Date | undefined)[]Initial uncontrolled selected dates. In range mode, index 0 is the start and index 1 is the end; either slot may be undefined.
value(Date | undefined)[]Controlled selected dates. In range mode, index 0 is the start and index 1 is the end; either slot may be undefined.
minDateEarliest selectable calendar date.
maxDateLatest selectable calendar date.
onValueChange({ value: (Date | undefined)[]; valueAsString: string[]; view: day | month | year }) => voidReceives proposed dates, ISO YYYY-MM-DD strings, and the view. Unset range endpoints keep their slots as undefined and empty strings. Update value in the callback when controlling the selection.
idsDatepicker ElementIdsOverrides generated part IDs. Input, label, and trigger IDs use callbacks, such as input: index => index === 0 ? "start-date" : "end-date".
defaultDateDateInitial navigation fallback when no date is selected. Does not select or submit a value.
rangeAnchorDateFixed peer date for range markers and preview; independent of the selected value.
readOnlybooleanMakes the visible inputs read-only and prevents calendar editing.
requiredbooleanMarks the visible inputs required.
localestringLocale for month and weekday labels. Defaults to English.

Control<div>

Groups each date input and calendar trigger.

Input<input>

Receives a typed MM/DD/YYYY start or end date. Each field sets aria-invalid for malformed text or a date outside its effective bounds.

PropTypeDescription
index0 | 1Endpoint to edit or open: 0 for start, 1 for end. Use the same index on each input and its trigger.

Trigger<button>

Opens the shared calendar for the indexed input.

PropTypeDescription
index0 | 1Endpoint to edit or open: 0 for start, 1 for end. Use the same index on each input and its trigger.

Content<div>

Place one calendar here for both range inputs.

View<div>

Shows day, month, or year selection.

PropTypeDescription
viewday | month | year (required)Active calendar view.

TableCellTrigger<button>

Selects a date in the range.

PropTypeDescription
valueDate | number (required)Use a Date from api.weeks for a day, a zero-based month from api.months for a month, or a year from api.years for a year.
AttributeValuePurpose
data-range-startPresent at the range startStyles the range start.
data-range-endPresent at the range endStyles the range end.
data-in-rangePresent inside the rangeStyles the active endpoint preview interval.

Accessibility

Give the start and end fields distinct visible labels, names, and format hints. An end date cannot precede the start; a start date cannot follow the end. Root min and max further limit both endpoints.