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:

November 2025

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
1
2
3
4
5
6
7
Selected: No range selected

Layout

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

<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

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

<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:

<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
/>

November 2025

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
1
2
3
4
5
6
7

Duration

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

<FormatDuration start={from} end={to} />
60 minutes24 hours

State and functions

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

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.

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

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

import { endOfDay, startOfDay } from "date-fns";
<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.

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

import DatePicker from "@intility/bifrost-react-datepicker";
import Box from "@intility/bifrost-react/Box";
import Button from "@intility/bifrost-react/Button";
import FormatDuration from "@intility/bifrost-react/FormatDuration";
import Grid from "@intility/bifrost-react/Grid";
import Inline from "@intility/bifrost-react/Inline";
import {
  addDays,
  endOfDay,
  endOfMonth,
  endOfWeek,
  endOfYear,
  startOfDay,
  startOfMonth,
  startOfWeek,
  startOfYear,
} from "date-fns";
import { useState } from "react";
import "@intility/bifrost-react-datepicker/datepicker.css";

export default function InlineDateRangePresetDemo() {
  const [from, setFrom] = useState<Date | null>(null);
  const [to, setTo] = useState<Date | null>(null);
  return (
    <InlineDateRangePreset
      from={from}
      to={to}
      onChange={([newFrom, newTo]) => {
        setFrom(newFrom);
        setTo(newTo);
      }}
    />
  );
}

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>
  );
}

function InlineDateRangePreset({
  from,
  to,
  onChange,
}: {
  from: Date | null;
  to: Date | null;
  onChange: (dates: [Date | null, Date | null]) => void;
}) {
  const [activeButton, setActiveButton] = useState<
    number | string | undefined
  >();

  const setDurationDays = (days: number) => {
    const newFrom = addDays(new Date(), days * -1);

    // set end date to "now" if that makes sense for your data
    // (includes future dates that we want to filter out)
    const newTo = new Date();

    // otherwise, setting no end date might make sense for "last 24 hours" if
    // dataset has no future dates (like an auto-updating log table)
    // const newTo = null;

    onChange([newFrom, newTo]);
    setActiveButton(days);
  };

  const handleDateChange = ([newFrom, newTo]: [Date | null, Date | null]) => {
    const fromStartOfDay = newFrom && startOfDay(newFrom);
    const toEndOfDay = newTo && endOfDay(newTo);
    onChange([fromStartOfDay, toEndOfDay]);
    setActiveButton(undefined);
  };

  return (
    <Box border radius="s" background style={{ display: "inline-block" }}>
      <Inline gap={0} style={{ flexWrap: "nowrap" }}>
        <Box padding={12}>
          <Grid gap={0}>
            <PresetButton
              active={activeButton === "today"}
              onClick={() => {
                const fromStartOfDay = startOfDay(new Date());
                const toEndOfDay = endOfDay(new Date());
                onChange([fromStartOfDay, toEndOfDay]);
                setActiveButton("today");
              }}
            >
              Today
            </PresetButton>
            <PresetButton
              active={activeButton === "week"}
              onClick={() => {
                const newFrom = startOfWeek(new Date(), { weekStartsOn: 1 });
                const newTo = endOfWeek(new Date(), { weekStartsOn: 1 });
                onChange([newFrom, newTo]);
                setActiveButton("week");
              }}
            >
              This week
            </PresetButton>
            <PresetButton
              active={activeButton === "month"}
              onClick={() => {
                const newFrom = startOfMonth(new Date());
                const newTo = endOfMonth(new Date());
                setActiveButton("month");
                onChange([newFrom, newTo]);
              }}
            >
              This month
            </PresetButton>
            <PresetButton
              active={activeButton === "year"}
              onClick={() => {
                const newFrom = startOfYear(new Date());
                const newTo = endOfYear(new Date());
                setActiveButton("year");
                onChange([newFrom, newTo]);
              }}
            >
              This year
            </PresetButton>
            <PresetButton
              active={activeButton === 1}
              onClick={() => setDurationDays(1)}
            >
              Last 24 hours
            </PresetButton>
            <PresetButton
              active={activeButton === 7}
              onClick={() => setDurationDays(7)}
            >
              Last 7 days
            </PresetButton>
            <PresetButton
              active={activeButton === 30}
              onClick={() => setDurationDays(30)}
            >
              Last 30 days
            </PresetButton>
            <PresetButton
              active={activeButton === 365}
              onClick={() => setDurationDays(365)}
            >
              Last 365 days
            </PresetButton>
          </Grid>
        </Box>
        <Box
          background="base-2"
          padding
          radiusTopRight="s"
          radiusBottomRight="s"
          borderLeft
          style={{ alignSelf: "stretch" }}
        >
          <Grid gap={16}>
            <Grid>
              <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
              />
            </Grid>
            <div>
              <span className="bfc-base-2">Selected: </span>
              {from && to ? (
                <FormatDuration
                  start={from}
                  // if `setDurationDays(N)` sets `newTo = new Date()`
                  end={to}
                  // if `setDurationDays(N)` sets `newTo = null`
                  // end={to ?? new Date()}
                />
              ) : (
                "No range selected"
              )}
            </div>
          </Grid>
        </Box>
      </Inline>
    </Box>
  );
}

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
<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

import DatePicker from "@intility/bifrost-react-datepicker";
import Box from "@intility/bifrost-react/Box";
import Button from "@intility/bifrost-react/Button";
import FormatDuration from "@intility/bifrost-react/FormatDuration";
import Grid from "@intility/bifrost-react/Grid";
import Inline from "@intility/bifrost-react/Inline";
import {
  addDays,
  endOfDay,
  endOfWeek,
  startOfDay,
  startOfWeek,
} from "date-fns";
import { useState } from "react";
import "@intility/bifrost-react-datepicker/datepicker.css";

export default function InlineDateRangePresetDemo() {
  const [from, setFrom] = useState<Date | null>(null);
  const [to, setTo] = useState<Date | null>(null);
  return (
    <InlineDateTimeRangePreset
      from={from}
      to={to}
      onChange={([newFrom, newTo]) => {
        setFrom(newFrom);
        setTo(newTo);
      }}
    />
  );
}

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>
  );
}

function InlineDateTimeRangePreset({
  from,
  to,
  onChange,
}: {
  from: Date | null;
  to: Date | null;
  onChange: (dates: [Date | null, Date | null]) => void;
}) {
  const [activeButton, setActiveButton] = useState<
    number | string | undefined
  >();

  const setDurationDays = (days: number) => {
    const newFrom = addDays(new Date(), days * -1);

    // set end date to "now" if that makes sense for your data
    // (includes future dates that we want to filter out)
    const newTo = new Date();

    // otherwise, setting no end date might make sense for "last 24 hours" if
    // dataset has no future dates (like an auto-updating log table)
    // const newTo = null;

    onChange([newFrom, newTo]);
    setActiveButton(days);
  };

  return (
    <Box border radius="s" background style={{ display: "inline-block" }}>
      <Inline gap={0} style={{ flexWrap: "nowrap" }}>
        <Box padding={12}>
          <Grid gap={0}>
            <PresetButton
              active={activeButton === "today"}
              onClick={() => {
                const fromStartOfDay = startOfDay(new Date());
                const toEndOfDay = endOfDay(new Date());
                onChange([fromStartOfDay, toEndOfDay]);
                setActiveButton("today");
              }}
            >
              Today
            </PresetButton>
            <PresetButton
              active={activeButton === "week"}
              onClick={() => {
                const newFrom = startOfWeek(new Date(), { weekStartsOn: 1 });
                const newTo = endOfWeek(new Date(), { weekStartsOn: 1 });
                onChange([newFrom, newTo]);
                setActiveButton("week");
              }}
            >
              This week
            </PresetButton>
            <PresetButton
              active={activeButton === 1}
              onClick={() => setDurationDays(1)}
            >
              Last 24 hours
            </PresetButton>
            <PresetButton
              active={activeButton === 7}
              onClick={() => setDurationDays(7)}
            >
              Last 7 days
            </PresetButton>
          </Grid>
        </Box>
        <Box
          background="base-2"
          padding
          radiusTopRight="s"
          radiusBottomRight="s"
          borderLeft
          style={{ alignSelf: "stretch", display: "grid" }}
        >
          <Inline>
            <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);
              }}
            />
          </Inline>
          <Inline align="center" style={{ alignSelf: "end" }}>
            <Inline.Stretch>
              <span className="bfc-base-2">Selected: </span>
              {from && to ? (
                <FormatDuration
                  start={from}
                  // if `setDurationDays(N)` sets `newTo = new Date()`
                  end={to}
                  // if `setDurationDays(N)` sets `newTo = null`
                  // end={to ?? new Date()}
                />
              ) : (
                "No range selected"
              )}
            </Inline.Stretch>
            <Button
              small
              variant="flat"
              state="neutral"
              onClick={() => {
                onChange([null, null]);
                setActiveButton(undefined);
              }}
              disabled={!from && !to}
            >
              Clear
            </Button>
          </Inline>
        </Box>
      </Inline>
    </Box>
  );
}

Placed in a dropdown

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>
  );
}