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.
| Prop | Type | Default |
|---|---|---|
placeholder | string | — |
icon | ReactNode | — |
size | "sm" | "md" | "lg" | "md" |
state | "valid" | "invalid" | — |
asChild | boolean | false |
className | string | — |
| Data attribute | Values |
|---|---|
[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.
| Prop | Type | Default |
|---|---|---|
arrow | boolean | false |
className | string | — |
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);
}