Honeycomb

date-picker

Experimental

This component is very new and might need some attention in the near future. We are actively monitoring any issues and greatly appreciate your feedback! This means it **might** receive some changes in the way it is used, specially when not in `readOnly` mode, but it is nonetheless fully ready to use.

Since: ver.16.0.0

Props

Prop nameTypeDefaultDescription
labelstring | LabelComp | undefined

The label is forwarded to the component and accepts a plain string or a custom made `Label` component. If the component does not have a visible label then `aria-label` is required. Label

innerRefRef<HTMLDivElement> | undefined

React ref forwarded to the outer div.

extraClassesstring | undefined

Additional classes to be added to the date picker container.

startDateDateRequired

First available day in this calendar.

endDateDate | undefined

Last available day in this calendar. If not provided, the end date will be he last day of the month provided in `startDate`.

expandedboolean | undefined

Controls if the calendar is shown or hidden.

inputPropsDatePickerInputProps | undefined

Rest props forwarded to Input component.

calendarPropsPartial<CalendarProps> | undefined

Rest props forwarded to Calendar component.

dialogAriaLabelstringRequired

Internationalized accessible label for the calendar dialog (dropdown). E.g.: "Choose a date"

dialogSize"fit" | "fill" | undefinedfit

Control the size of the dialog (calendar dropdown). By default the size fits the content of the calendars.

localestring | undefined

The locale forwarded to internationalization APIs for localizing date formatting. By default, uses the user's browser configured locale. See : useDateFormatter hook

userInputFormat"DD/MM/YYYY" | "DD-MM-YYYY" | "DD.MM.YYYY" | "YYYY/MM/DD" | "YYYY-MM-DD" | "YYYY.MM.DD" | "MM/DD/YYYY" | "MM-DD-YYYY" | "MM.DD.YYYY" | undefined'DD/MM/YYYY'

Date format expected by the user when using the input field and keyboard to input a date.

displayedValueFormatuseDateFormatterDisplays

Optionally format the displayed date in the input fields when the user it not editing it. Is forwarded to the `useDateFormatter` hook used under the hood for format the date. See : useDateFormatter hook

formatDisplayedValue((value: DatePickerDate) => string) | undefined

Custom displayed value formatting function. Replaces usage of `useDateFormatter` hook. Will be called with [`startDate`, `endDate`] when has a selected date range with a single input, so you can configure the separator as well as the date format.

readOnlyboolean | undefinedfalse

Makes the input field `readOnly` to only allow picking dates from the calendar or controllers.

validboolean | undefined

Component validation status. Behaves similarly as the fieldset, is forwarded accordingly to the input fields.

infoReactNode

Provides extra information for the date picker input fields. Behaves similarly as the fieldset

infoErrorReactNode

Provides an error message for invalid state feedback. Is displayed only when `valid` is set to false.

onToggleDatePickerOnToggleHandler | undefined

Fired whenever an event happens that toggles the expanded state.

inputEndPropsDatePickerInputProps | undefined

Rest props forwarded to range end Input component. _Enables range date picker._

valueDatePickerDate | DatePickerDateRange

Sets the selected date value for calendar and input field. Makes component controlled.

defaultValueDatePickerDate | DatePickerDateRange

Sets the initially selected date value for calendar and input field.

onChange((event: SyntheticEvent<Element, Event>, data?: { date: DatePickerDate; error?: DatePickerInputError | undefined; } | undefined) => void) | ((event: SyntheticEvent<...>, data?: { ...; } | undefined) => void) | undefined

Fired when a new date is selected from the calendar with the event thar triggered the change and a a data argument containing the following keys: ``` - date: Date | null | undefined - error?: { type: 'out-of-bounds' } ``` When is date range picker, data is: ``` - date: Date | null | undefined - newRange: [Date, Date] - error?: { type: 'out-of-bounds' } ```

The DatePicker provides an abstraction for the "date field" and "calendar" interactions to create an easy to use date picker input.

The inputProps and calendarProps are rest props forwarded to their respective components, you can use them to customize the underlying components as you wish in case any prop you need isn't already exposed by the DatePicker props. Some props are pre-defined by the DatePicker controller so be careful not to break the existing functionality and/or accessibility.

Make sure to inform the users of the expected date format for the input fields, the default format is DD/MM/YYYY (it can be customized with a prop) and it should be expressed preferably as the info text. You can make the input fields readOnly by providing this prop to the entire component (applies to both input fields in date range mode) or individually per input field.

You can customize the formatting of the displayed selected date with the displayedValueFormat prop. This should be one of the options available for the useDateFormatter hook that is used under the hood to format the date.

Basic usage

By default the end date will be set to the end of the month given on startDate and the date picker will only show one calendar.

The example bellow allows you to pick a date from the current month.

import { DatePicker } from '@flixbus/honeycomb-react';

const today = new Date();
// creates a new date object for the first day of the current month
const startDate = new Date(today.getFullYear(), today.getMonth(), 1);

<DatePicker
  label="Pick a date this month"
  dialogAriaLabel="Chose a date this month"
  readOnly
  displayedValueFormat="day, Mon D"
  startDate={startDate}
  calendarProps={{ showCurrentDate: true }}
/>

You can pass a date as the endDate to give users more date options.

import { DatePicker, CalendarHeaderCompact } from '@flixbus/honeycomb-react';

const today = new Date();
// creates a new date object for 60 days into the future
const endDate = new Date(today.getFullYear(), today.getMonth(), today.getDate() + 90);

<DatePicker
  label="Pick a date in the next 90 days"
  dialogAriaLabel="90 days availability calendar"
  readOnly
  displayedValueFormat="day, Mon D"
  startDate={today}
  endDate={endDate}
  calendarProps={{ calendarHeader: <CalendarHeaderCompact /> }}
/>

Validation

The props valid, info and infoError of the component are treated the same way as a fieldset, and will be associated to both inputs in date range pickers (see Date range selection example bellow).

It is also possible to use the inputProps props if you wish to customize individual inputs. In the next example we only use one input field, so we are going to use the component props.

The onChange callbacks are called with an extra data argument that contains an error key. If the user inputs a date that is outside of the date picker limits (defined by startDate and endDate), an out-of-bounds error is provided in the data argument. You can then handle validation within this event handler.

If the user inputs an incomplete date (the component matches the userInputFormat), the component onChange will not be called when the input field is blurred the previously selected date will be used. If you need to run code on intermediate states of the input field (when the date is still null), use the onChange from the inputProps instead, that will be called with null for incomplete dates.

import { DatePicker, CalendarHeaderBirthday } from '@flixbus/honeycomb-react';

const [validation, setValidation] = React.useState({});

const startDate = new Date(1950, 0, 1);
const endDate = new Date(2000, 11, 31);

const handleChange = (event, data) => {
  const { date, error } = data;
  console.log(event, date, error);

  if (!date) {
    setValidation({
      valid: false,
      message: 'Please fill in your birthday date to proceed.'
    });
    return;
  }

  if (error && error.type === 'out-of-bounds') {
    setValidation({
      valid: false,
      message: 'Promotional offers are only available for customers born between 1950 and 2000.'
    });
    return;
  }

  if (date.getFullYear() < 1980 || date.getFullYear() > 1994) {
    setValidation({
      valid: false,
      message: 'This promotional offer is only valid for Millennials.'
    });
    return;
  }

  setValidation({
    valid: true,
    message: '',
  });
}

<DatePicker
  label="Birthday"
  dialogAriaLabel="Select your birth date"
  startDate={startDate}
  endDate={endDate}
  valid={validation.valid}
  info="Format: DD/MM/YYYY"
  infoError={validation.message}
  onChange={handleChange}
  calendarProps={{ calendarHeader: <CalendarHeaderBirthday /> }}
  inputProps={{ onChange: (event, { date }) => console.log(event, { date }) }}
/>

Date range selection

For range date selection the DatePicker will allow the user to choose the start and end dates of a range.

To enable range selection you must provide at least a proper label for the end input field via the inputEndProps, the component will render start and end input fields separately.

During range selection, the value, defaultValue and all their references will be a pair of [startValue, endValue].

The component onChange callback will be called with an extra data key newRange. The individual input fields onChange callbacks can be used to perform logic specific for that input field.

The date picker validation is handled similarly to a Fieldset validation, but you can add individual input validation status and information by passing them via inputProps and inputEndProps.

import { DatePicker, CalendarHeaderSimple } from '@flixbus/honeycomb-react';

const [validationStart, setValidationStart] = React.useState({});
const [validationEnd, setValidationEnd] = React.useState({});

const startDate = new Date();
startDate.setHours(0, 0, 0, 0);

const endDate = new Date(
  startDate.getFullYear(),
  startDate.getMonth() + 7,
  -2
);

const validState = { valid: true };

const handleStartChange = (event, { error }) => {
  if (error) {
    setValidationStart({
      valid: false,
      message: 'The departure date must not be in the past or after the selected return date.'
    });
  }
}

const handleEndChange = (event, { error }) => {
  if (error) {
    setValidationEnd({
      valid: false,
      message: 'The return date must not be before the selected departure date or too far in the future.'
    });
  }
}

const handleChange = (event, { date, newRange }) => {
  const [departureDate, returnDate] = newRange;

  console.log(event, date, newRange);

  if (departureDate <= returnDate) {
    if (departureDate >= startDate) {
      setValidationStart(validState);
    }
    if (returnDate <= endDate) {
      setValidationEnd(validState);
    }
  }
}

<DatePicker
  label="Departure"
  dialogAriaLabel="Chose a start and end date"
  dialogSize="fill"
  startDate={startDate}
  endDate={endDate}
  onChange={handleChange}
  calendarProps={{
    calendarHeader: <CalendarHeaderSimple monthsAtATime={2} />,
    horizontal: true,
    showCurrentDate: true,
  }}
  inputProps={{
    placeholder: 'DD/MM/YYYY',
    valid: validationStart.valid,
    infoError: validationStart.message,
    onChange: handleStartChange,
  }}
  inputEndProps={{
    label: 'Return',
    placeholder: 'DD/MM/YYYY',
    valid: validationEnd.valid,
    infoError: validationEnd.message,
    onChange: handleEndChange,
  }}
/>

Previous and next day controllers

⚠️ Unless you plan on controlling the sate externally (see controlled example in the end of the docs): do not use the controllers prop! The DatePickerInput has it's own internal handlers to select the dates.

You can enable the controllers for selecting previous and next days by providing an accessible label via the controllersProps prop. The structure of controllers props is as follows:

The controllers are only visible when a value is already selected.

import { DatePicker, useDateFormatter } from '@flixbus/honeycomb-react';

const today = new Date();
today.setHours(0, 0, 0, 0);
const startDate = new Date(today.getFullYear(), today.getMonth(), 1);
const { formatter } = useDateFormatter({ display: 'Month YYYY' });

<DatePicker
  label={`${formatter.format(today)}`}
  dialogAriaLabel="Choose a date this month"
  defaultValue={today}
  startDate={startDate}
  inputProps={{
    controllersProps: {
      previous: { 'aria-label': 'Previous date' },
      next: { 'aria-label': 'Next date' },
    },
  }}
/>

Adding controllers in range date selection is also also easy. Simply provide accessible and clear labels for the start and end controllers for both inputs.

import { DatePicker } from '@flixbus/honeycomb-react';

const today = new Date();
today.setHours(0, 0, 0, 0);

const startDate = new Date(today.getFullYear(), today.getMonth(), 1);
const endDate = new Date(today.getFullYear(), today.getMonth() + 1, 28);

<DatePicker
  label="Start"
  dialogAriaLabel="Choose a week this month"
  defaultValue={[today, null]}
  startDate={startDate}
  endDate={endDate}
  inputProps={{
    controllersProps: {
      previous: { 'aria-label': 'Previous start date' },
      next: { 'aria-label': 'Next start date' },
    },
  }}
  inputEndProps={{
    label: 'End',
    controllersProps: {
      previous: { 'aria-label': 'Previous end date' },
      next: { 'aria-label': 'Next end date' },
    },
  }}
  calendarProps={{
    horizontal: true,
  }}
/>

Custom displayed day formatting

Another way of formatting the displayed value is by passing a custom formatter function via the formatDisplayedValue prop.

In this case you can format the displayed value however you please, which is useful if you want to perform conditional formatting depending on the selected date.

import { DatePicker, CalendarHeaderSimple } from '@flixbus/honeycomb-react';

const startDate = new Date();
startDate.setHours(0, 0, 0, 0);

const customFormatter = (date) => {
  if (!date) {
    return '';
  }

  const today = new Date();
  // compare without h/m/s/ms if the selected date is today
  if (date.toDateString() === today.toDateString()) {
    return 'Today!';
  }

  return date.toGMTString();
}

<DatePicker
  label="Departure"
  dialogAriaLabel="Chose a start and end date"
  startDate={startDate}
  info="Format: DD/MM/YYYY"
  formatDisplayedValue={customFormatter}
  calendarProps={{
    showCurrentDate: true,
  }}
/>

International date formats

Internationalization of the DatePicker is possible in more ways beyond the calendar props (months and days). As mentioned before, the displayed date is handled by the useDateFormatter hook that you can customize via the displayedValueFormat, including the locale as provided to the component (by default the hook uses the user's browser configured locale).

Another point of localization is the userInputFormat prop, which is responsible for the expected date format in the input fields.

The default date input format is DD/MM/YYYY as it is the most common format used around the globe, but you might want to fine tailor it depending on where your users are from.

Please refer to this List of date formats by country on Wikipedia for a full reference of the different date formats to choose an adequate one for your use case.

The example bellow is identical to the Birthday example, but in Danish.

import { DatePicker, CalendarHeaderBirthday } from '@flixbus/honeycomb-react';

const [validation, setValidation] = React.useState({});

const startDate = new Date(1950, 0, 1);
const endDate = new Date(2000, 11, 31);


const handleChange = (event, data) => {
  const { date, error } = data;

  if (!date) {
    setValidation({
      valid: false,
      message: 'Udfyld venligst din fødselsdato for at fortsætte.'
    });
    return;
  }

  if (error && error.type === 'out-of-bounds') {
    setValidation({
      valid: false,
      message: 'Tilbuddene er kun tilgængelige for kunder født mellem 1950 og 2000.'
    });
    return;
  }

  if (date.getFullYear() < 1980 || date.getFullYear() > 1994) {
    setValidation({
      valid: false,
      message: 'Dette kampagnetilbud gælder kun for millennials.'
    });
    return;
  }

  setValidation({
    valid: true,
    message: '',
  });
}

<DatePicker
  locale="da-DK"
  label="Fødselsdag"
  dialogAriaLabel="Vælg din fødselsdato"
  startDate={startDate}
  endDate={endDate}
  userInputFormat="YYYY-MM-DD"
  displayedValueFormat="day, Mon D YYYY"
  valid={validation.valid}
  info="Datoformat: YYYY-MM-DD"
  infoError={validation.message}
  onChange={handleChange}
  calendarProps={{
    days: [
      { short: 'ma', long: 'mandag' },
      { short: 'ti', long: 'tirsdag' },
      { short: 'on', long: 'onsdag' },
      { short: 'to', long: 'torsdag' },
      { short: 'fre', long: 'fredag' },
      { short: 'lø', long: 'lørdag' },
      { short: 'sø', long: 'søndag' },
    ],
    months: ['Januar', 'Februar', 'Marts', 'April', 'Maj', 'Juni', 'Juli', 'August', 'September', 'Oktober', 'November', 'December'],
    calendarHeader: <CalendarHeaderBirthday />,
  }}
/>

Uncontrolled and controlled

The examples bellow show how the component value can be uncontrolled (the value state is internally managed and you can set the initial state with defaultValue) versus controlled (the state is managed externally and the value is provided by the parent).

The onChange callback is fired with the event that triggered a date change and the new value that has been selected.

import { FormRow, DatePicker, Button } from '@flixbus/honeycomb-react';

const startDate = new Date('2025-10-1');
const [value, setValue] = React.useState(new Date('2025-10-31'));

<>
  <FormRow>
    <DatePicker label="Uncontrolled" dialogAriaLabel="Chose a date" startDate={startDate} defaultValue={value} />
  </FormRow>
  <FormRow>
    <DatePicker label="Controlled" dialogAriaLabel="Chose a date" startDate={startDate} value={value} onChange={(_e, { date }) => setValue(date)} />
  </FormRow>
  <FormRow>
    <Button onClick={() => setValue(null)}>Clear</Button>
  </FormRow>
</>

When using the date range picker, the value and defaultValue and all its references (e.g.: arguments on callbacks) will become [startValue, endValue] instead. The onChange event will also include the newState as an extra key in the data argument, you can use it on your side if wish.

import { DatePicker } from '@flixbus/honeycomb-react';

const today = new Date();
const nextMonth = new Date(today.getFullYear(), today.getMonth() + 2, 0);

const compRef = React.useRef();
const [expanded, setExpanded] = React.useState(false);
const [departureDate, setDepartureDate] = React.useState(today);
const [returnDate, setReturnDate] = React.useState(today);

const handleChange = (event, { date, newRange }) => {
  console.log(event, date, newRange)

  if (departureDate === undefined) {
    setDepartureDate(date);
    return;
  }

  if (returnDate === undefined) {
    setReturnDate(date);
    setExpanded(false);
    return;
  }

  setReturnDate(undefined);
  setDepartureDate(date);
  return;
}

<DatePicker
  innerRef={compRef}
  expanded={expanded}
  label="Start"
  dialogAriaLabel="Chose a start and end date"
  dialogSize="fill"
  startDate={today}
  endDate={nextMonth}
  value={[departureDate, returnDate]}
  onChange={handleChange}
  calendarProps={{
    horizontal: true,
    showCurrentDate: true,
  }}
  inputEndProps={{
    label: 'End',
  }}
  onFocus={(event) => {
    setExpanded(true);
  }}
  onBlur={(event) => {
    if (!compRef.current.contains(event.relatedTarget)) {
      setExpanded(false);
    }
  }}
/>

There is a slightly different behavior when you keep the component's expanded state on versus when you let the component control the expanded state internally.

While the component remains expanded, the focus goes back and forth from one input to the other, allowing the user to continuously edit the range. This is important to note because some date pickers might have the calendar always visible for some reason, and it should easily allow users to toggle the date.

import { DatePicker, Button } from '@flixbus/honeycomb-react';

const startDate = new Date();
startDate.setDate(1);
startDate.setHours(0, 0, 0, 0);

const endDate = new Date(
  startDate.getFullYear(),
  startDate.getMonth() + 2,
  0
);

<div style={{ height: '410px' }}>
  <DatePicker
    expanded
    label="Departure"
    dialogAriaLabel="Chose a start and end date"
    dialogSize="fill"
    startDate={startDate}
    endDate={endDate}
    calendarProps={{
      horizontal: true,
      showCurrentDate: true,
    }}
    inputEndProps={{
      label: 'Return'
    }}
  />
</div>