DateRange enables users to preview and select a range of days by picking dates from a calendar or adding a text input.
DateRange is distributed in its own package and must be installed separately.

also known as Calendar, Date time picker, Date picker range

DateRange is an pilot component. Expect development and design iteration and breaking API changes.
Figma:

Responsive:

Adaptive:

Props

Component props
Name
Type
Default
dateValue
Required
{ startDate: Date | null; endDate: Date | null }
-

DateRange is a controlled component. dateValue sets the value of the start date and end date. See the controlled component variant to learn more.

onCancel
Required
() => void
-

Callback triggered when the user clicks the Cancel button to not persist the selected dates. It should be used to close DateRange. See the controlled component variant to learn more.

onDateChange
Required
(startDate: { value: Date | null }, endDate: { value: Date | null }) => void
-

DateField is a controlled component. onDateChange is the callback triggered when the start date value changes. Should be used to modify the controlled value. See the controlled component variant to learn more.

onSubmit
Required
() => void
-

Callback triggered when the user clicks the Apply button to persist the selected dates. It should be used to persist the dates selected and close the DateRange. See the controlled component variant to learn more.

dateErrorMessage
{ startDate: string | null; endDate: string | null }
-

Customize your error message for the cases the user enters invalid start dates. See the error messaging variant to learn more.

localeData
Locale
-

DateRange accepts imported locales from the open source date utility library date-fns. See the locales variant to learn more.

maxDate
Date
-

Maximal selectable date. Disables any date values after the provided date. See the disable future and past variant to learn more.

minDate
Date
-

Minimal selectable date. Disables any date values before the provided date. See the disable future and past variant to learn more.

onDateBlur
{
  startDate: (args: { event: React.FocusEvent<HTMLInputElement>; value: string }) => void;
  endDate: (args: { event: React.FocusEvent<HTMLInputElement>; value: string }) => void;
}
-

Callback triggered when the end date input loses focus. See the error messaging variant to learn more.

onDateError
{
  startDate: (args: { errorMessage: string; value: Date | null }) => void;
  endDate: (args: { errorMessage: string; value: Date | null }) => void;
}
-

Callback triggered when the start date value entered is invalid. See the error messaging variant to learn more.

onDateFocus
{
  startDate: (args: { event: React.FocusEvent<HTMLInputElement>; value: string }) => void;
  endDate: (args: { event: React.FocusEvent<HTMLInputElement>; value: string }) => void;
}
-

Callback triggered when the user focus on the input of the start date DateField. See the error messaging variant to learn more.

onSecondaryDateBlur
{
  startDate: (args: { event: React.FocusEvent<HTMLInputElement>; value: string }) => void;
  endDate: (args: { event: React.FocusEvent<HTMLInputElement>; value: string }) => void;
}
-

Callback triggered when the end date input loses focus. See the error messaging variant to learn more.

onSecondaryDateChange
(
  startDate: { value: Date | null },
  endDate: { value: Date | null },
) => void
-

DateField is a controlled component. onSecondaryDateChange is the callback triggered when the start date value changes. Should be used to modify the controlled value. See the controlled component variant to learn more.

onSecondaryDateError
{
  startDate: (args: { errorMessage: string; value: Date | null }) => void;
  endDate: (args: { errorMessage: string; value: Date | null }) => void;
}
-

Callback triggered when the start date value entered is invalid. See the error messaging variant to learn more.

onSecondaryDateFocus
{
  startDate: (args: { event: React.FocusEvent<HTMLInputElement>; value: string }) => void;
  endDate: (args: { event: React.FocusEvent<HTMLInputElement>; value: string }) => void;
}
-

Callback triggered when the user focus on the input of the start date DateField. See the error messaging variant to learn more.

radioGroup
ReactElement
-

An optional RadioGroup to provide preestablished date range options. See the with RadioGroup variant to learn more.

secondaryDateErrorMessage
{ startDate: string | null; endDate: string | null }
-

Customize your error message for the cases the user enters invalid start dates. See the error messaging variant to learn more.

secondaryDateValue
{ startDate: Date | null; endDate: Date | null }
-

DateRange is a controlled component. dateValue sets the value of the start date and end date. See the secondary date range variant to learn more.

Usage guidelines

When to use
  • When asking users to select past, present or future date ranges
  • When users can pick between pre-selected date ranges or input a custom date range
When not to use
  • When users need to select one specific day. Use DatePicker instead
  • When users need to input a date value with a numeric keyboard, for example when adding a birthday date. Use DateField instead

Best practices

Do
  • Disable future or past dates according to the use case. If the user would like to see predictions, for example, disable the past.
  • When possible, provide a list of applicable date ranges to facilitate user selection
Don't
  • Enable users to select dates in the future or past, if those dates are not a valid input
  • Provide a long of a list of applicable date ranges with confusing labels, to avoid confusing the user. Display concise options and in a logical order.

Localization

Be sure to localize all text strings. Note that localization can lengthen text by 20 to 30 percent.

DateRange depends on DefaultLabelProvider for internal text strings. Localize the texts via DefaultLabelProvider. Learn more
import { useState } from 'react';
import { DefaultLabelProvider, Flex } from 'gestalt';
import { DateRange } from 'gestalt-datepicker';

export default function Example() {
  const [startDate, setStartDate] = useState(null);
  const [endDate, setEndDate] = useState(null);

  return (
    <DefaultLabelProvider
      labels={{
        DateRange: {
          cancelText: 'Abbrechen',
          applyText: 'Anwenden',
        },
      }}
    >
      <Flex
        alignItems="center"
        height="100%"
        justifyContent="center"
        width="100%"
      >
        <DateRange
          dateValue={{ startDate, endDate }}
          onCancel={() => {}}
          onDateChange={(newStartDate, newEndDate) => {
            setStartDate(newStartDate.value);
            setEndDate(newEndDate.value);
          }}
          onDateError={{ startDate: () => {}, endDate: () => {} }}
          onSubmit={() => {}}
        />
      </Flex>
    </DefaultLabelProvider>
  );
}

Date format locales

DateRange supports multiple locales. Adjust the date format to each date-fns locale. The following locale examples show the different locale format variants.

Note that locale data from date-fns is external to gestalt-datepicker, it's not an internal dependency. Add date-fns to your app's dependencies.

import { DateRange } from 'gestalt-datepicker';
import { it } from 'date-fns/locale';
<DateRange localeData={it}/>

Use the SelectList to try out different locales by passing in the localeData prop.

Variants

Controlled component

DateRange is a controlled component.
Use endDateValue, startDateValue, onEndDateChange, onStartDateChange, onEndDateError, onStartDateError, onSubmit and onCancel to implement it correctly.

Follow the implementation in the example to implement a controlled DateRange correctly.

When there’s not a date range selected, the call-to-action is disabled to prevent user errors.

import { useRef, useState } from 'react';
import { Box, Flex, IconButton, Layer, Popover, Status } from 'gestalt';
import { DateRange } from 'gestalt-datepicker';

export default function Example() {
  const [selectedDates, setSelectedDates] = useState(null);
  const [startDate, setStartDate] = useState(null);
  const [endDate, setEndDate] = useState(null);
  const [showComponent, setShowComponent] = useState(true);
  const anchorRef = useRef(null);

  return (
    <Flex
      alignContent="start"
      height="100%"
      justifyContent="start"
      width="100%"
    >
      <Box padding={8}>
        <Flex alignItems="center" gap={2}>
          <Status
            subtext={
              selectedDates
                ? `${
                    selectedDates[0].getMonth() + 1
                  }/${selectedDates[0].getDate()}/${selectedDates[0].getFullYear()} - ${
                    selectedDates[1].getMonth() + 1
                  }/${selectedDates[1].getDate()}/${selectedDates[1].getFullYear()}`
                : undefined
            }
            title={selectedDates ? 'Campaign dates selected' : 'Select dates'}
            type={selectedDates ? 'ok' : 'problem'}
          />
          <IconButton
            ref={anchorRef}
            accessibilityLabel="Open calendar"
            icon="edit"
            onClick={() => {
              if (!showComponent && selectedDates) {
                setStartDate(selectedDates[0]);
                setEndDate(selectedDates[1]);
              }

              setShowComponent((value) => !value);
            }}
            selected={showComponent}
          />
        </Flex>
      </Box>

      {showComponent ? (
        <Layer>
          <Popover
            anchor={anchorRef.current}
            idealDirection="down"
            onDismiss={() => {}}
            positionRelativeToAnchor={false}
            size="flexible"
          >
            <DateRange
              dateValue={{ startDate, endDate }}
              onCancel={() => {
                setStartDate(null);
                setEndDate(null);
                setShowComponent(false);
              }}
              onDateChange={(newStartDate, newEndDate) => {
                setStartDate(newStartDate.value);
                setEndDate(newEndDate.value);
              }}
              onSubmit={() => {
                if (startDate && endDate)
                  setSelectedDates([startDate, endDate]);
                setShowComponent(false);
              }}
            />
          </Popover>
        </Layer>
      ) : null}
    </Flex>
  );
}

With RadioGroup

This variant allow users to select common options of date ranges, so they can select an option without having to navigate through the calendar picker.

import { useMemo, useState } from 'react';
import { Flex, RadioGroup } from 'gestalt';
import { DateRange } from 'gestalt-datepicker';

export default function Example() {
  const [startDate, setStartDate] = useState(null);
  const [endDate, setEndDate] = useState(null);
  const [period, setPeriod] = useState(null);

  const radioGroup = useMemo(
    () => (
      <RadioGroup id="past radiogroup example" legend="Date range">
        <RadioGroup.RadioButton
          checked={period === '1'}
          id="1"
          label="Next week"
          name="1"
          onChange={() => {
            setStartDate(new Date());
            const now = new Date();
            now.setDate(now.getDate() + 1 * 7);
            setEndDate(now);
            setPeriod('1');
          }}
          value="1"
        />
        <RadioGroup.RadioButton
          checked={period === '2'}
          id="2"
          label="Next 2 weeks"
          name="2"
          onChange={() => {
            setStartDate(new Date());
            const now = new Date();
            now.setDate(now.getDate() + 2 * 7);
            setEndDate(now);
            setPeriod('2');
          }}
          value="2"
        />
        <RadioGroup.RadioButton
          checked={period === '4'}
          id="4"
          label="Next 4 weeks"
          name="4"
          onChange={() => {
            setStartDate(new Date());
            const now = new Date();
            now.setDate(now.getDate() + 4 * 7);
            setEndDate(now);
            setPeriod('4');
          }}
          value="4"
        />
        <RadioGroup.RadioButton
          checked={period === 'custom'}
          id="custom"
          label="Custom"
          name="custom"
          onChange={() => setPeriod('custom')}
          value="custom"
        />
      </RadioGroup>
    ),
    [period]
  );

  return (
    <Flex
      alignItems="center"
      height="100%"
      justifyContent="center"
      width="100%"
    >
      <DateRange
        dateValue={{ startDate, endDate }}
        onCancel={() => {}}
        onDateChange={(newStartDate, newEndDate) => {
          setPeriod('custom');
          setStartDate(newStartDate.value);
          setEndDate(newEndDate.value);
        }}
        onSubmit={() => {}}
        radioGroup={radioGroup}
      />
    </Flex>
  );
}

Future selection

Use RadioGroup to select pre-established date ranges in the future. For example, activation dates for a new campaign.

import { useMemo, useState } from 'react';
import { Flex, RadioGroup } from 'gestalt';
import { DateRange } from 'gestalt-datepicker';

export default function Example() {
  const [startDate, setStartDate] = useState(null);
  const [endDate, setEndDate] = useState(null);

  const [period, setPeriod] = useState(null);

  const radioGroup = useMemo(
    () => (
      <RadioGroup id="past radiogroup example" legend="Date range">
        <RadioGroup.RadioButton
          checked={period === '1'}
          id="1"
          label="Past week"
          name="1"
          onChange={() => {
            setEndDate(new Date());
            const now = new Date();
            now.setDate(now.getDate() - 1 * 7);
            setStartDate(now);
            setPeriod('1');
          }}
          value="1"
        />
        <RadioGroup.RadioButton
          checked={period === '2'}
          id="2"
          label="Past 2 weeks"
          name="2"
          onChange={() => {
            setEndDate(new Date());
            const now = new Date();
            now.setDate(now.getDate() - 2 * 7);
            setStartDate(now);
            setPeriod('2');
          }}
          value="2"
        />
        <RadioGroup.RadioButton
          checked={period === '4'}
          id="4"
          label="Past 4 weeks"
          name="4"
          onChange={() => {
            setEndDate(new Date());
            const now = new Date();
            now.setDate(now.getDate() - 4 * 7);
            setStartDate(now);
            setPeriod('4');
          }}
          value="4"
        />
        <RadioGroup.RadioButton
          checked={period === 'custom'}
          id="custom"
          label="Custom"
          name="custom"
          onChange={() => setPeriod('custom')}
          value="custom"
        />
      </RadioGroup>
    ),
    [period]
  );

  return (
    <Flex
      alignItems="center"
      height="100%"
      justifyContent="center"
      width="100%"
    >
      <DateRange
        dateValue={{ startDate, endDate }}
        onCancel={() => {}}
        onDateChange={(newStartDate, newEndDate) => {
          setPeriod('custom');
          setStartDate(newStartDate.value);
          setEndDate(newEndDate.value);
        }}
        onSubmit={() => {}}
        radioGroup={radioGroup}
      />
    </Flex>
  );
}

Past selection

Use RadioGroup to select pre-established date ranges in the past. For example, date ranges to analize performance metrics in ongoing campaigns.

Error messaging

DateRange can communicate errors when the user selects an invalid date. Use startDateErrorMessage, endDateErrorMessage, onEndDateError, onStartDateError, onStartDateBlur, onStartDateFocus, onEndDateBlur, onEndDateFocus to implement error messaging correctly.

The following implementation shows how to use all required props for error messaging.

The onEndDateError, onStartDateError event are very noisy. If the date fields are not pre-populated, leverage onStartDateBlur and onEndDateBlur to validate the error state after the date fields lose focus. If the date fields are pre-populated leverage React's useEffect to validate the error state.

import { useEffect, useState } from 'react';
import { Flex } from 'gestalt';
import { DateRange } from 'gestalt-datepicker';

export default function Example() {
  const date = new Date();
  const year = date.getFullYear();

  const minDate = new Date(year, 6, 1);
  const maxDate = new Date(year, 6, 31);

  const [startDate, setStartDate] = useState(new Date(year, 5, 1));
  const [endDate, setEndDate] = useState(null);
  const [currentEndErrorMessage, setCurrentEndErrorMessage] = useState(null);
  const [currentStartErrorMessage, setCurrentStartErrorMessage] =
    useState(null);
  const [endErrorMessage, setEndErrorMessage] = useState(null);
  const [startErrorMessage, setStartErrorMessage] = useState(null);

  useEffect(() => {
    if (currentStartErrorMessage && currentStartErrorMessage[0]) {
      setStartErrorMessage('Select a valid date in July');
    }
  }, [year, currentStartErrorMessage, startDate]);

  return (
    <Flex
      alignItems="center"
      height="100%"
      justifyContent="center"
      width="100%"
    >
      <DateRange
        dateErrorMessage={{
          startDate: startErrorMessage,
          endDate: endErrorMessage,
        }}
        dateValue={{ startDate, endDate }}
        maxDate={maxDate}
        minDate={minDate}
        onCancel={() => {}}
        onDateBlur={{
          startDate: () => {
            if (currentStartErrorMessage && currentStartErrorMessage[0]) {
              setStartErrorMessage('Select a valid date in July');
            } else {
              setStartErrorMessage(null);
            }
          },
          endDate: () => {
            if (currentEndErrorMessage && currentEndErrorMessage[0]) {
              setEndErrorMessage('Select a valid date in July');
            } else {
              setEndErrorMessage(null);
            }
          },
        }}
        onDateChange={(newStartDate, newEndDate) => {
          setStartDate(newStartDate.value);
          setEndDate(newEndDate.value);
        }}
        onDateError={{
          startDate: ({ errorMessage, value }) => {
            if (!errorMessage) {
              setStartErrorMessage(null);
            }
            setCurrentStartErrorMessage([errorMessage, value]);
          },
          endDate: ({ errorMessage, value }) => {
            if (!errorMessage) {
              setEndErrorMessage(null);
            }
            setCurrentEndErrorMessage([errorMessage, value]);
          },
        }}
        onSubmit={() => {}}
      />
    </Flex>
  );
}

Disable past & future dates

DateField supports disabling future and past dates from being selected. Use minDate for disabling past dates and maxDate for disabling futures dates.

  1. Disable past. Disable the past when the user should select dates ranges in the future. For example, activation dates for a new campaign.
  2. Disable future. Disable the future when the user should select dates ranges in the past. For example, date ranges to analize performance metrics in ongoing campaigns.
Disable past
import { useState } from 'react';
import { Flex } from 'gestalt';
import { DateRange } from 'gestalt-datepicker';

export default function Example() {
  const [startDate, setStartDate] = useState(null);
  const [endDate, setEndDate] = useState(null);
  const [startErrorMessage, setStartErrorMessage] = useState(null);

  return (
    <Flex
      alignItems="center"
      height="100%"
      justifyContent="center"
      width="100%"
    >
      <DateRange
        dateErrorMessage={{ startDate: startErrorMessage, endDate: null }}
        dateValue={{ startDate, endDate }}
        minDate={new Date()}
        onCancel={() => {}}
        onDateChange={(newStartDate, newEndDate) => {
          setStartDate(newStartDate.value);
          setEndDate(newEndDate.value);
        }}
        onDateError={{
          startDate: ({ errorMessage }) =>
            setStartErrorMessage(
              errorMessage ? 'Please, enter a valid date' : null
            ),
          endDate: () => {},
        }}
        onSubmit={() => {}}
      />
    </Flex>
  );
}

Disable future
import { useState } from 'react';
import { Flex } from 'gestalt';
import { DateRange } from 'gestalt-datepicker';

export default function Example() {
  const [startDate, setStartDate] = useState(null);
  const [endDate, setEndDate] = useState(null);
  const [endErrorMessage, setEndErrorMessage] = useState(null);

  return (
    <Flex
      alignItems="center"
      height="100%"
      justifyContent="center"
      width="100%"
    >
      <DateRange
        dateErrorMessage={{ startDate: null, endDate: endErrorMessage }}
        dateValue={{ startDate, endDate }}
        maxDate={new Date()}
        onCancel={() => {}}
        onDateChange={(newStartDate, newEndDate) => {
          setStartDate(newStartDate.value);
          setEndDate(newEndDate.value);
        }}
        onDateError={{
          startDate: () => {},
          endDate: ({ errorMessage }) =>
            setEndErrorMessage(
              errorMessage ? 'Please, enter a valid date' : null
            ),
        }}
        onSubmit={() => {}}
      />
    </Flex>
  );
}

External handlers

Experimental

DateRange consumes external handlers from GlobalEventsHandlerProvider.

Handlers:

  • onRender: executed when DateField mounts for the first time

See GlobalEventsHandlerProvider for more information.

Mobile

DateRange requires DeviceTypeProvider to enable its mobile user interface. The example below shows the mobile platform UI and its implementation.

On mobile devices, the radiogroup prop is not shown.

import { useState } from 'react';
import { DeviceTypeProvider, Flex } from 'gestalt';
import { DateRange } from 'gestalt-datepicker';

export default function Example() {
  const [startDate, setStartDate] = useState(null);
  const [endDate, setEndDate] = useState(null);

  return (
    <DeviceTypeProvider deviceType="mobile">
      <Flex
        alignItems="center"
        height="100%"
        justifyContent="center"
        width="100%"
      >
        <DateRange
          dateValue={{ startDate, endDate }}
          onCancel={() => {}}
          onDateChange={(newStartDate, newEndDate) => {
            setStartDate(newStartDate.value);
            setEndDate(newEndDate.value);
          }}
          onDateError={{ startDate: () => {}, endDate: () => {} }}
          onSubmit={() => {}}
        />
      </Flex>
    </DeviceTypeProvider>
  );
}

Secondary date range

DateRange supports a secondary date range in case you need to handle more that one range, in order to enable it you just need to pass the props secondaryDateValue and onSecondaryDateChange.

import { useState } from 'react';
import { DeviceTypeProvider, Flex } from 'gestalt';
import { DateRange } from 'gestalt-datepicker';

export default function Example() {
  const [startDate, setStartDate] = useState(null);
  const [endDate, setEndDate] = useState(null);
  const [secondaryStartDate, setSecondaryStartDate] = useState(null);
  const [secondaryEndDate, setSecondaryEndDate] = useState(null);

  return (
    <DeviceTypeProvider deviceType="desktop">
      <Flex
        alignItems="center"
        height="100%"
        justifyContent="center"
        width="100%"
      >
        <DateRange
          dateValue={{ startDate, endDate }}
          onCancel={() => {}}
          onDateChange={(newStartDate, newEndDate) => {
            setStartDate(newStartDate.value);
            setEndDate(newEndDate.value);
          }}
          onDateError={{ startDate: () => {}, endDate: () => {} }}
          onSecondaryDateChange={(newStartDate, newEndDate) => {
            setSecondaryStartDate(newStartDate.value);
            setSecondaryEndDate(newEndDate.value);
          }}
          onSubmit={() => {}}
          secondaryDateValue={{
            startDate: secondaryStartDate,
            endDate: secondaryEndDate,
          }}
        />
      </Flex>
    </DeviceTypeProvider>
  );
}

Writing

Do
  • Use concise labels to indicate what the date range selection is referring to
Don't
  • Add long and complicated labels to the date range picker and to the RadioGroup labels

Component quality checklist

Component quality checklist
Quality item
Status
Status description
Figma Library
Component is not currently available in Figma.
Responsive Web
Component does not respond to changing viewport sizes in web and mobile web.

Internal documentation

DateField
DateField is used when the user has to select a date. Compared to DatePicker, DateField has no supporting calendar to select a date, the user must input date values with a numeric keyboard.

DatePicker
DatePicker is used when the user has to select a date. Compared to DateField, DatePicker has a supporting calendar to select a date.