Date Picker

An input-style date field that opens a calendar in a popover, built on the Popover and Calendar components.

Default

The DatePicker component extends the Calendar component, providing input style trigger and popover wrapper for calendar.

tsx

import * as React from "react";
import { format } from "date-fns";

import { Calendar, DatePicker, Field } from "@hummingbirdui/react";

export default function DatePickerSimple() {
  const [open, setOpen] = React.useState(false);
  const [date, setDate] = React.useState<Date>();

  return (
    <Field className="mx-auto w-48">
      <Field.Label htmlFor="date-picker-simple">Date</Field.Label>
      <DatePicker open={open} onOpenChange={setOpen}>
        <DatePicker.Trigger id="date-picker-simple" placeholder="Pick a date">
          {date && format(date, "PPP")}
        </DatePicker.Trigger>
        <DatePicker.Content>
          <Calendar
            mode="single"
            selected={date}
            defaultMonth={date}
            onSelect={(date) => {
              setDate(date);
              setOpen(false);
            }}
          />
        </DatePicker.Content>
      </DatePicker>
    </Field>
  );
}

Range Picker

Passing mode="range" and numberOfMonths to the calendar turns the picker into a range picker.

tsx

import * as React from "react";
import { addDays, format } from "date-fns";
import { type DateRange } from "react-day-picker";

import { Calendar, DatePicker, Field } from "@hummingbirdui/react";

export default function DatePickerWithRange() {
  const [date, setDate] = React.useState<DateRange | undefined>({
    from: new Date(new Date().getFullYear(), 0, 20),
    to: addDays(new Date(new Date().getFullYear(), 0, 20), 20),
  });

  return (
    <Field className="mx-auto w-64">
      <Field.Label htmlFor="date-picker-range">Date Picker Range</Field.Label>
      <DatePicker>
        <DatePicker.Trigger id="date-picker-range" placeholder="Pick a date">
          {date?.from &&
            (date.to
              ? `${format(date.from, "LLL dd, y")} - ${format(date.to, "LLL dd, y")}`
              : format(date.from, "LLL dd, y"))}
        </DatePicker.Trigger>
        <DatePicker.Content>
          <Calendar
            mode="range"
            defaultMonth={date?.from}
            selected={date}
            onSelect={setDate}
            numberOfMonths={2}
          />
        </DatePicker.Content>
      </DatePicker>
    </Field>
  );
}

Date of Birth

Setting captionLayout="dropdown" on the calendar adds month and year dropdowns.

tsx

import * as React from "react";

import { Calendar, DatePicker, Field } from "@hummingbirdui/react";

export default function DatePickerDateOfBirth() {
  const [open, setOpen] = React.useState(false);
  const [date, setDate] = React.useState<Date | undefined>(undefined);

  return (
    <Field className="mx-auto w-48">
      <Field.Label htmlFor="date-of-birth">Date of birth</Field.Label>
      <DatePicker open={open} onOpenChange={setOpen}>
        <DatePicker.Trigger id="date-of-birth" placeholder="Select date">
          {date?.toLocaleDateString()}
        </DatePicker.Trigger>
        <DatePicker.Content>
          <Calendar
            mode="single"
            selected={date}
            defaultMonth={date}
            captionLayout="dropdown"
            onSelect={(date) => {
              setDate(date);
              setOpen(false);
            }}
          />
        </DatePicker.Content>
      </DatePicker>
    </Field>
  );
}

Input

For typed entry, a regular Input parses the text while an icon button opens the calendar.

tsx

import * as React from "react";
import { CalendarIcon } from "lucide-react";

import {
  Button,
  Calendar,
  DatePicker,
  Field,
  Input,
  InputIcon,
} from "@hummingbirdui/react";

function formatDate(date: Date | undefined) {
  if (!date) {
    return "";
  }

  return date.toLocaleDateString("en-US", {
    day: "2-digit",
    month: "long",
    year: "numeric",
  });
}

function isValidDate(date: Date | undefined) {
  if (!date) {
    return false;
  }
  return !isNaN(date.getTime());
}

export default function DatePickerInput() {
  const [open, setOpen] = React.useState(false);
  const [date, setDate] = React.useState<Date | undefined>(
    new Date("2025-06-01"),
  );
  const [month, setMonth] = React.useState<Date | undefined>(date);
  const [value, setValue] = React.useState(formatDate(date));

  return (
    <Field className="mx-auto w-56">
      <Field.Label htmlFor="date-required">Subscription Date</Field.Label>
      <DatePicker open={open} onOpenChange={setOpen}>
        <DatePicker.Anchor asChild>
          <InputIcon>
            <Input
              id="date-required"
              value={value}
              placeholder="June 01, 2025"
              onChange={(e) => {
                const date = new Date(e.target.value);
                setValue(e.target.value);
                if (isValidDate(date)) {
                  setDate(date);
                  setMonth(date);
                }
              }}
              onKeyDown={(e) => {
                if (e.key === "ArrowDown") {
                  e.preventDefault();
                  setOpen(true);
                }
              }}
            />
            <InputIcon.End>
              <DatePicker.Trigger asChild>
                <Button
                  shape="circle"
                  variant="text"
                  size="sm"
                  aria-label="Select date"
                  className="-me-2"
                >
                  <CalendarIcon className="size-3.5" />
                </Button>
              </DatePicker.Trigger>
            </InputIcon.End>
          </InputIcon>
        </DatePicker.Anchor>
        <DatePicker.Content align="end" sideOffset={10}>
          <Calendar
            mode="single"
            selected={date}
            month={month}
            onMonthChange={setMonth}
            onSelect={(date) => {
              setDate(date);
              setValue(formatDate(date));
              setOpen(false);
            }}
          />
        </DatePicker.Content>
      </DatePicker>
    </Field>
  );
}

Date Time Picker

A date picker paired with a time input. The icon prop swaps the default calendar icon for a chevron.

tsx

import * as React from "react";
import { format } from "date-fns";
import { ChevronDownIcon } from "lucide-react";

import { Calendar, DatePicker, Field, Input } from "@hummingbirdui/react";

export default function DatePickerTime() {
  const [open, setOpen] = React.useState(false);
  const [date, setDate] = React.useState<Date | undefined>(undefined);

  return (
    <div className="mx-auto flex max-w-xs gap-2">
      <Field>
        <Field.Label htmlFor="date-picker-optional">Date</Field.Label>
        <DatePicker open={open} onOpenChange={setOpen}>
          <DatePicker.Trigger
            id="date-picker-optional"
            placeholder="Select date"
            icon={<ChevronDownIcon />}
          >
            {date && format(date, "PPP")}
          </DatePicker.Trigger>
          <DatePicker.Content>
            <Calendar
              mode="single"
              selected={date}
              captionLayout="dropdown"
              defaultMonth={date}
              onSelect={(date) => {
                setDate(date);
                setOpen(false);
              }}
            />
          </DatePicker.Content>
        </DatePicker>
      </Field>
      <Field className="w-32 shrink-0">
        <Field.Label htmlFor="time-picker-optional">Time</Field.Label>
        <Input
          type="time"
          id="time-picker-optional"
          step="1"
          defaultValue="10:30:00"
          className="appearance-none [&::-webkit-calendar-picker-indicator]:hidden [&::-webkit-calendar-picker-indicator]:appearance-none"
        />
      </Field>
    </div>
  );
}

API Reference

The date picker combines two components: a Calendar for date selection, wrapped in a Popover for the open/close behavior.

Calendar

The date grid placed inside DatePicker.Content. All selection props (mode, selected, onSelect, …) are documented in the Calendar API reference.

DatePicker

Contains all the parts of a date picker. Props are the same as Popover.

DatePicker.Trigger

The input-style button that toggles the popover. Renders the children as the formatted value, or the placeholder when children are empty.

PropTypeDefault
placeholderstring
iconReactNode
size"sm" | "md" | "lg""md"
state"valid" | "invalid"
asChildbooleanfalse
classNamestring

Data attributeValues
[data-state]"open" | "closed"
[data-empty]Present when no value is rendered

DatePicker.Anchor

An optional element to position the DatePicker.Content against. Props are the same as Popover.Anchor.

DatePicker.Content

The popover panel that the Calendar is placed into. It defaults to align="start", hides the popover arrow, and removes the popover's default width limit. All other props are the same as Popover.Content.

PropTypeDefault
arrowbooleanfalse
classNamestring

CSS variables

The trigger reuses Hummingbird's form-control classes, so it follows the same border, focus, and disabled styling as Input. The date-picker-trigger class exposes these CSS variables, which can be overridden to modify its appearance. Read more about customizing with CSS variables here.

.date-picker-trigger {
  --date-picker-placeholder-color: var(--text-color-muted);
  --date-picker-icon-color: var(--text-color-muted);
  --date-picker-icon-size: --spacing(4);
}