Skip to main content
/
/
/
/
Datepicker range with presets

Datepicker range with presets

If you don't need preset buttons, you can use DatePicker directly instead.

In this guide you'll learn how to build a date range picker with presets:

August 2026

Monday
Tuesday
Wednesday
Thursday
Friday
Saturday
Sunday
27
28
29
30
31
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
1
2
3
4
5
6
Selected: No range selected

Layout

One way to create the layout above, is by combining <Box>, <Grid>, and <Inline>:

TSX
<Box border radius="s" background>
  <Inline gap={0} style={{ flexWrap: "nowrap" }}>
    <Box padding={12}>
      <Grid gap={0}>[presetbuttons]</Grid>
    </Box>
    <Box
      background="base-2"
      padding
      radiusTopRight="s"
      radiusBottomRight="s"
      borderLeft
      style={{ alignSelf: "stretch" }}
    >
      <Grid gap={16}>
        [datepicker]
        <div>
          <span className="bfc-base-2">Selected: </span>
          [duration]
        </div>
      </Grid>
    </Box>
  </Inline>
</Box>
[preset buttons]
[datepicker]
Selected: [duration]

Preset buttons

The preset buttons are pretty close to what we can achieve using <Button>, with two small exceptions:

  • The button text should be left-aligned (not centered)
  • The font-weight should be normal (not bold)

Let's create our own component that solves this and also lets us mark it as "active" by styling it as a filled button.

PresetButton.tsx

TSX
import Button from "@intility/bifrost-react/Button";

export default function PresetButton({
  children,
  onClick,
  active = false,
}: {
  children?: React.ReactNode;
  onClick?: React.MouseEventHandler<HTMLButtonElement>;
  active?: boolean;
}) {
  return (
    <Button
      style={{ textAlign: "left", fontWeight: "normal" }}
      state={active ? "default" : "neutral"}
      variant={active ? "filled" : "flat"}
      onClick={onClick}
    >
      {children}
    </Button>
  );
}

Usage would look something like this

TSX
<PresetButton onClick={setDurationToday}>Today</PresetButton>
<PresetButton onClick={setDurationThisWeek} active>This week</PresetButton>
<PresetButton onClick={setDurationThisMonth}>This month</PresetButton>

Datepicker

The inline prop makes the <DatePicker> render as an always-visible calendar without an input field.

To give users both the visual calendar and a text input (for copy/paste or typing dates), render two <DatePicker> instances sharing the same state:

  • Input field: Set open={false} to render only the input without a popup
  • Visual calendar: Set inline to render the always-visible calendar

Both instances share the same selected, startDate, endDate, and onChange props:

TSX
<DatePicker
  selected={from}
  onChange={handleDateChange}
  startDate={from}
  endDate={to}
  selectsRange
  label="Select start and end date"
  open={false}
  isClearable
/>
<DatePicker
  selected={from}
  onChange={handleDateChange}
  startDate={from}
  endDate={to}
  selectsRange
  inline
  label=""
  hideLabel
  swapRange
/>

August 2026

Monday
Tuesday
Wednesday
Thursday
Friday
Saturday
Sunday
27
28
29
30
31
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
1
2
3
4
5
6

Duration

Use <FormatDuration> to get a localized duration text with a tooltip based on two Date objects.

TSX
<FormatDuration start={from} end={to} />
1 hour1 day

State and functions

In order to keep a date range in react state, we need to store a "from date" and "to date".

TSX
const [from, setFrom] = useState<Date | null>(null);
const [to, setTo] = useState<Date | null>(null);

The from and to dates should probably live outside our component, so they can be used to filter data elsewhere in the app, let's make them props along with a way to update them:

  • from: Date | null
  • to: Date | null
  • onChange: (dates: [Date | null, Date | null]) => void

We also want to highlight the active preset button. Let's make it a number in days for buttons like "last 24 hours" = 1 day, and "last 365 days" = 365, and a string for buttons like "Today" as "today" etc.

TSX
const [activeButton, setActiveButton] = useState<number | string | undefined>();

When clicking a preset button, we can use date-fns helper functions to update both:

TSX
import { endOfDay, startOfDay } from "date-fns";
TSX
<PresetButton
  active={activeButton === "today"}
  onClick={() => {
    const fromStartOfDay = startOfDay(new Date());
    const toEndOfDay = endOfDay(new Date());
    onChange([fromStartOfDay, toEndOfDay]);
    setActiveButton("today");
  }}
>
  Today
</PresetButton>

When picking dates with the datepickers, set both dates and clear any active preset button.

TSX
const handleDateChange = ([newFrom, newTo]: [Date | null, Date | null]) => {
  const fromStartOfDay = newFrom && startOfDay(newFrom);
  const toEndOfDay = newTo && endOfDay(newTo);
  onChange([fromStartOfDay, toEndOfDay]);
  // Clear active preset when user manually picks dates
  setActiveButton(undefined);
};

Combined code example

Include time

Since react-datepicker doesn't provide a way to pick two different times in the same datepicker instance, we need two datepickers to be able to pick a range between two datetime points:

Selected: No range selected
TSX
<DatePicker
  label="From"
  showTimeSelect
  selected={from}
  selectsStart
  startDate={from}
  endDate={to}
  maxDate={to || undefined}
  onChange={(newDate) => {
    onChange([newDate, to]);
    setActiveButton(undefined);
  }}
/>
<DatePicker
  label="To"
  showTimeSelect
  selected={to}
  selectsEnd
  startDate={from}
  endDate={to}
  minDate={from || undefined}
  onChange={(newDate) => {
    onChange([from, newDate]);
    setActiveButton(undefined);
  }}
/>

Otherwise it works a lot like the example above.

Combined code example with time

Placed in a dropdown

TSX
function MyDateDropdown() {
  const [from, setFrom] = useState<Date | null>(null);
  const [to, setTo] = useState<Date | null>(null);
  return (
    <Dropdown
      unstyled
      noArrow
      content={
        <InlineDateRangePreset
          from={from}
          to={to}
          onChange={([newFrom, newTo]) => {
            setFrom(newFrom);
            setTo(newTo);
          }}
        />
      }
    >
      <Button>
        {from && to ? (
          <FormatDuration start={from} end={to} />
        ) : (
          "Select date range"
        )}
      </Button>
    </Dropdown>
  );
}