Dots

Date Range Picker

A start and end date, typed as segments or picked on a range calendar, with optional preset chips.

Statement period
Type both dates or pick them on the calendar.
"use client"

import {

The DateRangePicker component picks a period with a start and an end date. Use it for statement periods, transaction filters, exports, and trip dates. Both ends can be typed as segments or picked on a range calendar, which opens in a popover on wide screens and a bottom sheet on phones. Pick a Date Picker for a single day, and a Date Field for one date far from today, such as a date of birth.

Installation

pnpm dlx shadcn@latest add @dots/date-range-picker

Usage

import { CalendarDate } from "@internationalized/date"
 
import { DateRangePicker } from "@/components/ui/date-range-picker"
<DateRangePicker
  label="Statement period"
  placeholderValue={new CalendarDate(2026, 11, 1)}
/>

Composition

DateRangePicker is ready to use as is. To change the layout, compose the styled root with the parts from Date Picker:

DateRangePickerRoot
├── DatePickerLabel
├── DatePickerField
│   ├── DatePickerInput slot="start"
│   ├── DateRangePickerSeparator
│   ├── DatePickerInput slot="end"
│   └── DatePickerButton
├── DatePickerDescription
├── DatePickerError
└── DatePickerOverlayContent overlay="auto" title="Statement period"
    ├── DatePickerPresets (optional)
    └── RangeCalendar

DatePickerOverlayContent picks the overlay: a popover (DatePickerContent) on wide screens and a bottom sheet (DatePickerSheetContent) on phones. Use DatePickerContent alone only when you want the popover everywhere.

Features

  • Start and end dates typed as segments in the order and format of the locale
  • A range calendar in a popover on wide screens and a bottom sheet on phones
  • Preset chips such as "Last 7 days" or "This month" above the calendar
  • One or more months side by side in the popover, always one in the sheet
  • Limits with minValue, maxValue, and isDateUnavailable
  • Form submission with startName and endName
  • Label, description, and error message wired up for assistive tech
  • A default field and a small pill for filter rows

Presets

Pass presets to show quick ranges above the calendar. A function value gets today in the user's time zone and runs only when the chip is pressed, so nothing reads the clock while rendering. A fixed value also lights its chip when the same days are picked by hand.

Export period
"use client"

import { CalendarDate, startOfMonth } from "@internationalized/date"
const presets: DateRangePreset[] = [
  {
    id: "month",
    label: "This month",
    value: (today) => ({ start: startOfMonth(today), end: today }),
  },
  {
    id: "q3",
    label: "Q3 2026",
    value: {
      start: new CalendarDate(2026, 7, 1),
      end: new CalendarDate(2026, 9, 30),
    },
  },
]

Months

Use numberOfMonths to show more than one month in the popover. The sheet on phones always shows one.

Trip dates
Two months side by side. Phones show one.
"use client"

import { CalendarDate } from "@internationalized/date"

Sheet

The calendar opens in a popover on wide screens and a bottom sheet on phones. Set overlay to "sheet" or "popover" to use one on every screen. The sheet heading is the label unless you pass sheetTitle.

Spending period
"use client"

import { CalendarDate } from "@internationalized/date"

Size

size="default" matches Input. size="sm" is a pill for filter rows and toolbars. Without a visible label, pass an aria-label.

Statement period
"use client"

import { CalendarDate } from "@internationalized/date"

Limits

Set minValue and maxValue to keep the period inside the account history. Days outside the limits can't be picked, and a typed date outside them makes the field invalid. Use isDateUnavailable for single days that can't be picked.

History
From the day the account opened up to today.
"use client"

import { CalendarDate } from "@internationalized/date"

Controlled

Pass value and onChange to own the range. startName and endName submit each end with a form as an ISO date.

Card transactions

from=2026-11-02 to=2026-11-08

"use client"

import * as React from "react"

Invalid

Set isInvalid and an errorMessage to explain what to change. Built-in validation, such as a date past maxValue, shows the same way.

Statement period
A statement covers up to 12 months. Shorten the period.
"use client"

import { CalendarDate } from "@internationalized/date"

Disabled

Use isDisabled for a period the user can see but not change. Use isReadOnly to keep it focusable and readable.

Billing cycle
Set by your plan.
"use client"

import { CalendarDate } from "@internationalized/date"

Server rendering

With no value, defaultValue, or placeholderValue, the empty segments are built from the current date, and without an I18nProvider the locale comes from the browser. Pass a placeholderValue (or a value) and wrap the page in I18nProvider so the server and client render the same.

<I18nProvider locale="en-US">
  <DateRangePicker
    label="Statement period"
    placeholderValue={new CalendarDate(2026, 11, 1)}
  />
</I18nProvider>

Accessibility

DateRangePicker is built on the React Aria DateRangePicker. It provides:

  • Each date segment as a spin button: arrow keys change it, digits type into it, and focus moves to the next segment when one is full
  • The label, description, and error message linked to both inputs
  • The calendar button named and kept pressed while the calendar is open
  • Focus moved into the calendar on open and back to the button on close
  • Arrow key, Page Up and Page Down, and Home and End navigation in the calendar grid
  • Preset chips as a single-choice group

Label the picker

Give every picker a label. When the label isn't visible, such as the small pill in a filter row, pass an aria-label instead. Name the preset chips with presetsLabel.

<DateRangePicker aria-label="Filter by period" size="sm" />

Explain errors in words

The invalid state uses a destructive color. Always pass an errorMessage that says what to change, so the state is not conveyed by color alone.

API Reference

DateRangePicker

A complete range picker: label, field, help text, error, and the calendar overlay. Accepts all React Aria DateRangePicker props except children, className, and style, such as value, defaultValue, onChange, placeholderValue, minValue, maxValue, isDateUnavailable, allowsNonContiguousRanges, firstDayOfWeek, granularity, isDisabled, isReadOnly, isRequired, isInvalid, startName, and endName.

PropTypeDefaultDescription
labelReact.ReactNode-Visible label. Without it, pass aria-label.
descriptionReact.ReactNode-Help text under the field.
errorMessageReact.ReactNode | ((validation: ValidationResult) => ReactNode)-The reason the value is invalid. Shown only while it is.
size"default" | "sm""default""default" matches Input, "sm" is a pill.
presetsDateRangePreset[]-Quick ranges above the calendar.
presetsLabelstring"Presets"Names the preset chips for assistive tech.
overlay"auto" | "popover" | "sheet""auto"Where the calendar opens. "auto" is a sheet on phones, a popover on wider screens.
sheetTitleReact.ReactNode-Heading of the sheet. Defaults to the label, then aria-label, then "Choose dates".
numberOfMonthsnumber-Months shown side by side in the popover. The sheet shows one.
captionLayout"label" | "dropdown"-"dropdown" puts month and year selects in the calendar header.
classNamestring-Additional classes to apply to the root element.

DateRangePreset

A quick range shown as a chip above the calendar.

FieldTypeDefaultDescription
idKey-The key reported when the chip is chosen.
labelReact.ReactNode-The chip text.
valueDateRange | ((today: CalendarDate) => DateRange)-The range, or a function of today that returns it. Runs only on press.

DateRangePickerRoot

The styled React Aria DateRangePicker root for composing your own layout. Lays out the label, field, help text, and error, and lets the parts follow its disabled state.

PropTypeDefaultDescription
classNamestring-Additional classes to apply to the root element.
...propsDateRangePickerProps<T> (React Aria)-Props spread to the React Aria root.

DateRangePickerSeparator

The dash between the two ends of the range. Hidden from assistive tech.

PropTypeDefaultDescription
childrenReact.ReactNode"–"The separator text.
...propsReact.ComponentProps<"span">-Props spread to the <span>.

See the React Aria documentation for more information.