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
Props
Usage guidelines
- 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 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
- 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
- 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.
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 width="100%" height="100%" alignContent="start" justifyContent="start" > <Box padding={8}> <Flex gap={2} alignItems="center"> <Status type={selectedDates ? 'ok' : 'problem'} title={selectedDates ? 'Campaign dates selected' : 'Select dates'} subtext={ selectedDates ? `${ selectedDates[0].getMonth() + 1 }/${selectedDates[0].getDate()}/${selectedDates[0].getFullYear()} - ${ selectedDates[1].getMonth() + 1 }/${selectedDates[1].getDate()}/${selectedDates[1].getFullYear()}` : undefined } /> <IconButton accessibilityLabel="Open calendar" icon="edit" selected={showComponent} onClick={() => { if (!showComponent && selectedDates) { setStartDate(selectedDates[0]); setEndDate(selectedDates[1]); } setShowComponent((value) => !value); }} ref={anchorRef} /> </Flex> </Box> {showComponent ? ( <Layer> <Popover size="flexible" positionRelativeToAnchor={false} onDismiss={() => {}} anchor={anchorRef.current} idealDirection="down" > <DateRange endDateValue={endDate} onEndDateChange={({ value }) => setEndDate(value)} onEndDateError={() => {}} onStartDateError={() => {}} onStartDateChange={({ value }) => setStartDate(value)} onSubmit={() => { if (startDate && endDate) setSelectedDates([startDate, endDate]); setShowComponent(false); }} startDateValue={startDate} onCancel={() => { setStartDate(null); setEndDate(null); 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 legend="Date range" id="past radiogroup example"> <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 radioGroup={radioGroup} endDateValue={endDate} onStartDateChange={({ value }) => { setPeriod('custom'); setStartDate(value); }} onEndDateChange={({ value }) => { setPeriod('custom'); setEndDate(value); }} onStartDateError={() => {}} onEndDateError={() => {}} startDateValue={startDate} onSubmit={() => {}} onCancel={() => {}} /> </Flex> ); }
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 legend="Date range" id="past radiogroup example"> <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 radioGroup={radioGroup} endDateValue={endDate} onStartDateChange={({ value }) => { setPeriod('custom'); setStartDate(value); }} onEndDateChange={({ value }) => { setPeriod('custom'); setEndDate(value); }} onEndDateError={() => {}} onStartDateError={() => {}} startDateValue={startDate} onSubmit={() => {}} onCancel={() => {}} /> </Flex> ); }
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 endDateValue={endDate} endDateErrorMessage={endErrorMessage} minDate={minDate} maxDate={maxDate} onStartDateBlur={() => { if (currentStartErrorMessage && currentStartErrorMessage[0]) { setStartErrorMessage('Select a valid date in July'); } else { setStartErrorMessage(null); } }} onEndDateBlur={() => { if (currentEndErrorMessage && currentEndErrorMessage[0]) { setEndErrorMessage('Select a valid date in July'); } else { setEndErrorMessage(null); } }} onStartDateChange={({ value }) => setStartDate(value)} onEndDateChange={({ value }) => setEndDate(value)} onStartDateError={({ errorMessage, value }) => { if (!errorMessage) { setStartErrorMessage(null); } setCurrentStartErrorMessage([errorMessage, value]); }} onEndDateError={({ errorMessage, value }) => { if (!errorMessage) { setEndErrorMessage(null); } setCurrentEndErrorMessage([errorMessage, value]); }} startDateValue={startDate} startDateErrorMessage={startErrorMessage} onSubmit={() => {}} onCancel={() => {}} /> </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.
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 endDateValue={endDate} minDate={new Date()} onStartDateChange={({ value }) => setStartDate(value)} onEndDateChange={({ value }) => setEndDate(value)} onStartDateError={({ errorMessage }) => setStartErrorMessage( errorMessage ? 'Please, enter a valid date' : null ) } onEndDateError={() => {}} startDateErrorMessage={startErrorMessage} startDateValue={startDate} onSubmit={() => {}} onCancel={() => {}} /> </Flex> ); }
Disable the past when the user should select dates ranges in the future. For example, activation dates for a new campaign.
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 maxDate={new Date()} endDateValue={endDate} endDateErrorMessage={endErrorMessage} onStartDateChange={({ value }) => setStartDate(value)} onEndDateChange={({ value }) => setEndDate(value)} onStartDateError={() => {}} onEndDateError={({ errorMessage }) => setEndErrorMessage(errorMessage ? 'Please, enter a valid date' : null) } startDateValue={startDate} onSubmit={() => {}} onCancel={() => {}} /> </Flex> ); }
Disable the future when the user should select dates ranges in the past. For example, date ranges to analize performance metrics in ongoing campaigns.
External handlers
DateRange consumes external handlers from GlobalEventsHandlerProvider.
Handlers:
- onMount: executed when DateField mounts for the first time
See GlobalEventsHandlerProvider for more information.
Supporting locales
DateRange supports multiple locales. Adjust the date format to each date-fns locale. The following locale examples show the different locale format variants.
IMPORTANT: 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 Dropdown to try out different locales by passing in the localeData
prop.
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 startDateValue={startDate} endDateValue={endDate} onStartDateChange={({ value }) => setStartDate(value)} onEndDateChange={({ value }) => setEndDate(value)} onStartDateError={() => {}} onEndDateError={() => {}} onSubmit={() => {}} onCancel={() => {}} /> </Flex> </DeviceTypeProvider> ); }
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. |
Writing
- Use concise labels to indicate what the date range selection is referring to
- Add long and complicated labels to the date range picker and to the RadioGroup labels
Related
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.