dotUI
dotUI
beta
  1. Components
  2. Dates
  3. Date Range Picker

Date Range Picker

DateRangePicker combine a DateField and a RangeCalendar overlay to allow users to enter or select a date and time range.

<DateRangePicker />

Installation

Install the following dependencies:

npm install react-aria-components

Copy and paste the following code into your project.

"use client";

import * as React from "react";
import {
  DateRangePicker as AriaDateRangePicker,
  type DateRangePickerProps as AriaDateRangePickerProps,
  type DateValue,
} from "react-aria-components";
import { type VariantProps } from "tailwind-variants";
import { CalendarIcon } from "@/lib/icons";
import { Button } from "./button";
import { DateInput, DateSegment } from "./date-input";
import { Dialog } from "./dialog";
import { fieldStyles } from "./field";
import { Field, type FieldProps } from "./field";
import { InputRoot, type inputStyles } from "./input";
import { RangeCalendar } from "./range-calendar";

interface DateRangePickerProps<T extends DateValue>
  extends DateRangePickerRootProps<T>,
    Omit<FieldProps, "children">,
    VariantProps<typeof inputStyles> {
  prefix?: React.ReactNode;
  isLoading?: boolean;
}

const DateRangePicker = <T extends DateValue>({
  className,
  size,
  label,
  description,
  errorMessage,
  prefix,
  isLoading,
  isRequired,
  isDisabled,
  necessityIndicator,
  contextualHelp,
  ...props
}: DateRangePickerProps<T>) => {
  return (
    <DateRangePickerRoot
      className={className}
      isRequired={isRequired}
      isDisabled={isLoading || isDisabled}
      {...props}
    >
      <Field
        label={label}
        description={description}
        errorMessage={errorMessage}
        isRequired={isRequired}
        necessityIndicator={necessityIndicator}
        contextualHelp={contextualHelp}
      >
        <InputRoot
          size={size}
          prefix={prefix}
          isLoading={isLoading}
          loaderPosition="prefix"
          className="pr-1"
        >
          <DateInput slot="start">{(segment) => <DateSegment segment={segment} />}</DateInput>
          <span aria-hidden="true"></span>
          <DateInput slot="end" className="flex-1">
            {(segment) => <DateSegment segment={segment} />}
          </DateInput>
          <Button variant="default" size="sm" shape="square" className="my-1 size-7 rounded-sm">
            <CalendarIcon />
          </Button>
        </InputRoot>
      </Field>
      <Dialog type="popover" mobileType="drawer" className="flex">
        <RangeCalendar className="mx-auto" />
      </Dialog>
    </DateRangePickerRoot>
  );
};

interface DateRangePickerRootProps<T extends DateValue>
  extends Omit<AriaDateRangePickerProps<T>, "className"> {
  className?: string;
}
const DateRangePickerRoot = <T extends DateValue>({
  className,
  ...props
}: DateRangePickerRootProps<T>) => {
  const { root } = fieldStyles();
  return <AriaDateRangePicker className={root({ className })} {...props} />;
};

export type { DateRangePickerProps, DateRangePickerRootProps };
export { DateRangePicker, DateRangePickerRoot };

Update the import paths to match your project setup.

Usage

Use a DateRangePicker to allow users to enter and edit time values.

Options

Label

A visual label can be provided for the DateRangePicker using the label prop or a hidden label using aria-label prop.

<DateRangePicker label="Trip" />
<DateRangePicker aria-label="Trip" />

Size

Use the size prop to control the size of the DateRangePicker.
The default variant is "md".

<DateRangePicker label="small" size="sm" />
<DateRangePicker label="medium" size="md" />
<DateRangePicker label="large" size="lg" />

Prefix

To add additional context for the DateRangePicker, use the prefix prop.

<DateRangePicker prefix={<PlaneIcon />} />

Description

A description can be supplied to DateRangePicker via the description prop. The description is always visible unless the isInvalid prop is true and an error message is provided.

<DateRangePicker label="Trip" description="Please select a date range." />

Contextual help

A ContextualHelp element may be placed next to the label to provide additional information or help about a DateRangePicker.

<DateRangePicker label="Appointment" contextualHelp={<ContextualHelp />} />

Error message

An errorMessage can be supplied to a DateRangePicker, which will be displayed when the isInvalid prop is set to true.

<DateRangePicker
  label="Trip dates"
  isInvalid
  errorMessage="Trip dates can't be scheduled in the past."
/>

Time zones

DateRangePicker is time zone aware when a ZonedDateTime object is provided as the value.

<DateRangePicker
  defaultValue={{
    start: parseAbsoluteToLocal("2021-04-07T18:45:22Z"),
    end: parseAbsoluteToLocal("2021-04-08T20:00:00Z"),
  }}
/>

Granularity

The granularity prop allows you to control the smallest unit that is displayed by DateRangePicker.

<DateRangePicker label="Hour" granularity="hour" defaultValue={dates} />
<DateRangePicker label="Minute" granularity="minute" defaultValue={dates} />
<DateRangePicker label="Second" granularity="second" defaultValue={dates} />

Placeholder value

Use the placeholderValue prop to control the default values of each segment. The default value is midnight.

<DateRangePicker label="Meeting date" placeholderValue={new CalendarDate(1980, 1, 1)} />

Hide time zone

Use the hideTimeZone prop to hide the time zone abbreviation.

<DateRangePicker
  label="Appointment time"
  granularity="minute"
  defaultValue={{
    start: parseAbsoluteToLocal("2021-04-07T18:45:22Z"),
    end: parseAbsoluteToLocal("2021-04-08T20:00:00Z"),
  }}
  hideTimeZone
/>

Hour cycle

Use the hourCycle prop to control the hour cycle of the DateRangePicker.

<DateRangePicker granularity="minute" hourCycle={24} />

Loading

Use the isLoading prop to control the loading state of the DateRangePicker.

<DateRangePicker isLoading />

Disabled

Use the isDisabled prop to disable the DateRangePicker.

<DateRangePicker label="Trip dates" isDisabled />

ReadOnly

The isReadOnly boolean prop makes the DateRangePicker's text content immutable. Unlike isDisabled, the DateRangePicker remains focusable and the contents can still be copied.

<DateRangePicker
  label="Event date"
  value={{
    start: parseAbsoluteToLocal("2021-04-07T18:45:22Z"),
    end: parseAbsoluteToLocal("2021-04-08T20:00:00Z"),
  }}
  isReadOnly
/>

Required

Use the isRequired prop to mark the DateRangePicker as required. Use the necessityIndicator prop to control the visual style of the required state.

<DateRangePicker label="Event date" isRequired />
<DateRangePicker label="Event date" isRequired necessityIndicator="icon" />
<DateRangePicker label="Event date" isRequired necessityIndicator="label" />
<DateRangePicker label="Event date" necessityIndicator="label" />

Uncontrolled

An initial, uncontrolled value can be provided to the DateRangePicker using the defaultValue prop.

<DateRangePicker
  label="Controlled"
  defaultValue={{
    start: parseDate("2020-02-03"),
    end: parseDate("2020-02-08"),
  }}
/>

Controlled

Use the value and onChange props to control the value of the input.

const [value, setValue] = React.useState({
  start: parseDate("2024-02-03"),
  end: parseDate("2024-02-08"),
});
return <DateRangePicker label="Controlled" value={value} onChange={setValue} />

Composition

If you need to customize things further, you can drop down to the composition level.

<DatePickerRoot>
  <Label>Meeting date</Label>
  <InputRoot suffix={<Button><CalendarIcon /></Button>}>
    <DateInput slot="start">{(segment) => <DateSegment segment={segment} />}</DateInput>
    <span></span>
    <DateInput slot="end">{(segment) => <DateSegment segment={segment} />}</DateInput>
  </InputRoot>
  <Description>Please select a date.</Description>
  <FieldError />
  <Overlay type="popover" mobileType="drawer">
    <DialogContent>
      <RangeCalendar />
    </DialogContent>
  </Overlay>
</DatePickerRoot>

API Reference

PropTypeDefaultDescription
pageBehavior
'single' | 'visible'
'visible'
Controls the behavior of paging. Pagination either works by advancing the visible page by visibleDuration (default) or one unit of visibleDuration.
minValue
DateValue
-
The minimum allowed date that a user may select.
maxValue
DateValue
-
The maximum allowed date that a user may select.
isDateUnavailable
(date: DateValue) => boolean
-
Callback that is called for each date of the calendar. If it returns true, then the date is unavailable.
placeholderValue
DateValue
-
A placeholder date that influences the format of the placeholder shown when no value is selected. Defaults to 12:00 AM or 00:00 depending on the hour cycle.
hourCycle
12 | 24
-
Whether to display the time in 12 or 24 hour format. By default, this is determined by the user's locale.
granularity
'hour' | 'minute' | 'second'
-
Determines the smallest unit that is displayed in the date picker. By default, this is "day" for dates, and "minute" for times.
hideTimeZone
boolean
false
Whether to hide the time zone abbreviation.
shouldForceLeadingZeros
boolean
-
Whether to always show leading zeros in the hour field. By default, this is determined by the user's locale.
isDisabled
boolean
-
Whether the input is disabled.
isReadOnly
boolean
-
Whether the input can be selected but not changed by the user.
isRequired
boolean
-
Whether user input is required on the input before form submission.
isInvalid
boolean
-
Whether the input value is invalid.
autoFocus
boolean
-
Whether the element should receive focus on render.
isOpen
boolean
-
Whether the overlay is open by default (controlled).
defaultOpen
boolean
-
Whether the overlay is open by default (uncontrolled).
allowsNonContiguousRanges
boolean
-
When combined with isDateUnavailable, determines whether non-contiguous ranges, i.e. ranges containing unavailable dates, may be selected.
startName
string
-
The name of the start date input element, used when submitting an HTML form.
endName
string
-
The name of the end date input element, used when submitting an HTML form.
validate
(value: RangeValue<MappedDateValue<DateValue>>) => ValidationError | true | null | undefined
-
A function that returns an error message if a given value is invalid. Validation errors are displayed to the user when the form is submitted if validationBehavior="native". For realtime validation, use the isInvalid prop instead.
value
RangeValue<DateValue> | null
-
The current value (controlled).
defaultValue
RangeValue<DateValue> | null
-
The default value (uncontrolled).
shouldCloseOnSelect
boolean | () => boolean
true
Determines whether the date picker popover should close automatically when a date is selected.
validationBehavior
'native' | 'aria'
'aria'
Whether to use native HTML form validation to prevent form submission when the value is missing or invalid, or mark the field as required or invalid via ARIA.
children
ReactNode | (values: DatePickerRenderProps & {defaultChildren: ReactNode | undefined}) => ReactNode
-
The children of the component. A function may be provided to alter the children based on component state.
className
string
-
The CSS className for the element.
style
CSSProperties | (values: DatePickerRenderProps & {defaultStyle: CSSProperties}) => CSSProperties
-
The inline style for the element. A function may be provided to compute the style based on component state.
EventTypeDescription
onFocus
(e: FocusEvent<Target>) => void
Handler that is called when the element receives focus.
onBlur
(e: FocusEvent<Target>) => void
Handler that is called when the element loses focus.
onFocusChange
(isFocused: boolean) => void
Handler that is called when the element's focus status changes.
onKeyDown
(e: KeyboardEvent) => void
Handler that is called when a key is pressed.
onKeyUp
(e: KeyboardEvent) => void
Handler that is called when a key is released.
onOpenChange
(isOpen: boolean) => void
Handler that is called when the overlay's open state changes.
onChange
(value: MappedDateValue<DateValue>) => void
Handler that is called when the value changes.
Data attributeDescription
[data-focus-within]
Whether an element within the date picker is focused, either via a mouse or keyboard.
[data-focus-visible]
Whether an element within the date picker is keyboard focused.
[data-disabled]
Whether the date picker is disabled.
[data-invalid]
Whether the date picker is invalid.
[data-open]
Whether the date picker is open.

Built by mehdibha. The source code is available on GitHub.