A polished calendar-driven date picker with month navigation.
DatePicker defines how EnterUI handles a polished calendar-driven date picker with month navigation, including default structure, accessible states, and token-driven styling.
Click the trigger to open a styled calendar popover.
<FormField label="Release date" labelProps={{ htmlFor: "datepicker-default" }}>
<DatePicker id="datepicker-default" />
</FormField>import { DateInput, DatePicker, FormField } from "@froggdesign/enter-ui-react";Pair DatePicker with FormField description for hints.
Used for visible release notes.
Pass invalid to surface field-level error styling.
Pick a date in the future.
Disabled DatePicker stays readable but inert.
Use two controlled DatePickers with a shared range to reproduce the
FroggTravel search form. Open either field to see
both endpoints and the dates between them highlighted. Check-in cannot be in
the past; check-out allows 1 to 30 nights. Changing check-in clears check-out
when the existing stay no longer fits. Clearing check-in also clears and
disables check-out until a new check-in is selected.
Two linked calendars with shared range highlighting and a valid stay window. Try changing or clearing check-in.
Choose a stay of 1 to 30 nights.
"use client";
import { DatePicker, FormField, Grid } from "@froggdesign/enter-ui-react";
import { useId, useState } from "react";
function addDays(isoDate: string, days: number) {
const date = new Date(`${isoDate}T12:00:00Z`);
date.setUTCDate(date.getUTCDate() + days);
return date.toISOString().slice(0, 10);
}
export function TravelDatePickerExample() {
const id = useId();
const [today] = useState(() => new Date().toISOString().slice(0, 10));
const [start, setStart] = useState(() => addDays(today, 14));
const [end, setEnd] = useState(() => addDays(today, 19));
const range = { start, end };
function changeStart(next: string) {
setStart(next);
if (!next || (end && (end <= next || end > addDays(next, 30)))) {
setEnd("");
}
}
return (
<Grid min={220} gap={16} alignItems="start">
<FormField label="Check-in" labelProps={{ htmlFor: `${id}-in` }}>
<DatePicker
id={`${id}-in`}
locale="en-GB"
min={today}
value={start}
onValueChange={changeStart}
range={range}
placeholder="Select check-in"
/>
</FormField>
<FormField
label="Check-out"
labelProps={{ htmlFor: `${id}-out` }}
description="Choose a stay of 1 to 30 nights."
>
<DatePicker
id={`${id}-out`}
locale="en-GB"
min={start ? addDays(start, 1) : today}
max={start ? addDays(start, 30) : undefined}
disabled={!start}
value={end}
onValueChange={setEnd}
range={range}
placeholder="Select check-out"
/>
</FormField>
</Grid>
);
}Choose a date within a stay window. Unavailable days and months cannot be selected.
Choose a stay of 1 to 30 nights.
<DatePicker
value={checkOut}
onValueChange={setCheckOut}
min={firstAvailableCheckout}
max={lastAvailableCheckout}
locale="en-GB"
placeholder="Select check-out"
/>DatePicker owns the calendar popover; use it instead of Input type="date" when
a consistent EnterUI calendar is required. Travel forms clear an incompatible
check-out after check-in changes rather than silently choosing a different stay.
DatePicker uses a button trigger that opens a custom calendar popover. The trigger surfaces the selected date via its visible label, supports keyboard activation, and accepts aria-label, aria-labelledby, and aria-describedby. Each calendar day renders as a button with a full date label for screen readers. Pair with Label or FormField for a labeled context.
Opening focuses the selected date, or an allowed date nearest today when empty.
Only one date is in the Tab sequence. Left/Right move one day, Up/Down one week,
Home/End to week boundaries, and Page Up/Down one month. Hold Shift with Page
Up/Down to move one year. Movement respects min and max; Enter or Space selects,
and Escape closes without changing the value. Selection returns focus to the
trigger. Today is identified with aria-current="date".
The popover stays within the viewport on narrow screens. The popover scrolls
on short screens so every date and footer action remains accessible. Day targets use a seven-column layout with readable labels;
month controls are disabled when that month contains no selectable dates.
Impossible ISO dates such as 2026-02-31 display the placeholder.
value / defaultValue: ISO date string (YYYY-MM-DD).onValueChange(value): fires when the user selects or clears a date.min / max: ISO date bounds.range={{ start, end }}: inclusive ISO date range highlighted in the calendar; bounds and selection remain controlled by each picker.invalid sets aria-invalid and applies the error border / focus ring.size: sm, md (default), or lg.weekStartsOn: 0 (Sunday) or 1 (Monday, default).locale: BCP 47 locale string for month, weekday, and date labels.name: when set, renders a hidden input for form submission.DateInput is exported as an alias.The date trigger uses the same medium (8px) radius as text and selection controls; the calendar popover retains its separate surface radius.
Every exported component accepts className when it renders an element. EnterUI styles use .eui-* classes and --eui-* variables mapped from FroggDesign theme tokens.
Pass the same range={{ start, end }} to both check-in and check-out pickers.
The inclusive range is highlighted green in either calendar, including dates in
adjacent months. Endpoints use a stronger green. The range only adds visual
context: each picker keeps its own value, callback, and min / max bounds.
Incomplete, invalid, or reversed ranges are not highlighted.
<DatePicker value={start} onValueChange={setStart} range={{ start, end }} />
<DatePicker value={end} onValueChange={setEnd} range={{ start, end }} min={start} />