Date picker
PortedA month grid to pick one day, several days or a range (mode), on react-day-picker. Arrow keys move by day and week, Page Up and Page Down by month (with Shift, by year), Home and End to the week's ends. Several months hide the days of other months, so no day shows twice.
import { DatePicker } from "@etched/monet/date-picker";
// Fixed dates in 2026, so the examples look the same every day. Open calendars show past months, so "today" is never in them.
const day = (month: number, date: number) => new Date(2026, month - 1, date);
export function Preview() {
return <DatePicker aria-label="Review date" defaultValue={day(10, 26)} />;
}Installation
import { Calendar, DatePicker } from "@etched/monet/date-picker";Examples
Basics
import { DatePicker } from "@etched/monet/date-picker";
// Fixed dates in 2026, so the examples look the same every day. Open calendars show past months, so "today" is never in them.
const day = (month: number, date: number) => new Date(2026, month - 1, date);
export function Basics() {
return (
<div className="row">
<DatePicker aria-label="Due date" />
<DatePicker aria-label="Review date" defaultValue={day(10, 26)} />
<DatePicker aria-label="Review date, small" size="sm" defaultValue={day(10, 26)} />
<DatePicker aria-label="Review date, read-only" defaultValue={day(10, 26)} readOnly />
<DatePicker aria-label="Review date, disabled" defaultValue={day(10, 26)} disabled />
</div>
);
}In a form
In Form, a picker is a field like any other. required stops a submit while no date is chosen and focuses the picker; the server's error for its name shows under it; onFormSubmit gets the date as yyyy-mm-dd. min and max bound the days and the months. Reset puts each defaultValue back; a disabled date is not sent. Here the server refuses November 26.
import { useState } from "react";
import { Button } from "@etched/monet/button";
import { Form, type FormErrors } from "@etched/monet/form";
import { DatePicker } from "@etched/monet/date-picker";
// Fixed dates in 2026, so the examples look the same every day. Open calendars show past months, so "today" is never in them.
const day = (month: number, date: number) => new Date(2026, month - 1, date);
export function InAForm() {
const [sent, setSent] = useState("");
const [errors, setErrors] = useState<FormErrors>();
return (
<Form
className="grid"
errors={errors}
onFormSubmit={(values) => {
const refused = values.onsite === "2026-11-26";
setErrors(refused ? { onsite: "Kai Ito is not in the office that day." } : {});
setSent(refused ? "" : Object.entries(values).map(([name, value]) => `${name}=${String(value)}`).join(", "));
}}
onReset={() => setSent("")}
>
<DatePicker label="Start date" name="start" defaultValue={day(11, 2)} min={day(10, 26)} max={day(12, 31)} description="Between October 26 and the end of the year." />
<DatePicker label="Offer expires" name="expires" required={false} />
<DatePicker label="Onsite date" name="onsite" required min={day(11, 2)} max={day(11, 30)} />
<DatePicker label="Signed on" name="signed" defaultValue={day(10, 19)} disabled />
<div className="row">
<Button type="submit" variant="primary">
Send invite
</Button>
<Button type="reset">Reset</Button>
{sent && <span>Sent: {sent}</span>}
</div>
</Form>
);
}Disabled days
Weekends and two holidays cannot be picked; arrow keys skip them.
import { DatePicker } from "@etched/monet/date-picker";
// Fixed dates in 2026, so the examples look the same every day. Open calendars show past months, so "today" is never in them.
const day = (month: number, date: number) => new Date(2026, month - 1, date);
export function DisabledDays() {
return (
<div className="grid">
<DatePicker label="Interview date" defaultValue={day(11, 24)} disabledDays={[{ dayOfWeek: [0, 6] }, day(11, 26), day(11, 27)]} />
</div>
);
}Locale
Month and day names, the date in the field and the first day of the week follow locale.
import { DatePicker } from "@etched/monet/date-picker";
// Fixed dates in 2026, so the examples look the same every day. Open calendars show past months, so "today" is never in them.
const day = (month: number, date: number) => new Date(2026, month - 1, date);
export function Locale() {
return (
<div className="row">
<DatePicker aria-label="Date, German" locale="de-DE" defaultValue={day(10, 26)} />
<DatePicker aria-label="Date, British" locale="en-GB" defaultValue={day(10, 26)} format={{ dateStyle: "long" }} />
<DatePicker aria-label="Date, Japanese" locale="ja-JP" defaultValue={day(10, 26)} />
</div>
);
}Calendar
The month grid on its own, for a panel or a filter.
| Sun | Mon | Tue | Wed | Thu | Fri | Sat |
|---|---|---|---|---|---|---|
import { useState } from "react";
import { Calendar } from "@etched/monet/date-picker";
// Fixed dates in 2026, so the examples look the same every day. Open calendars show past months, so "today" is never in them.
const day = (month: number, date: number) => new Date(2026, month - 1, date);
export function CalendarAlone() {
const [date, setDate] = useState<Date | null>(day(3, 18));
return (
<Calendar
mode="single"
value={date}
onValueChange={(next) => setDate(next ?? null)}
defaultMonth={day(3, 1)}
footer={date ? `Chosen: ${date.toLocaleDateString("en-US", { dateStyle: "long" })}` : "No date chosen"}
/>
);
}Several days
mode="multiple" with max. Grey days are taken.
| Sun | Mon | Tue | Wed | Thu | Fri | Sat |
|---|---|---|---|---|---|---|
import { useState } from "react";
import { Calendar } from "@etched/monet/date-picker";
// Fixed dates in 2026, so the examples look the same every day. Open calendars show past months, so "today" is never in them.
const day = (month: number, date: number) => new Date(2026, month - 1, date);
export function SeveralDays() {
const [dates, setDates] = useState<Date[]>([day(3, 10), day(3, 11)]);
return (
<Calendar
mode="multiple"
value={dates}
onValueChange={(next) => setDates(next ?? [])}
max={3}
defaultMonth={day(3, 1)}
disabled={[day(3, 5), day(3, 12), day(3, 19)]}
fixedWeeks
footer={`${dates.length} of 3 days chosen.`}
/>
);
}Month and year lists
captionLayout="dropdown" for dates far away; startMonth and endMonth bound the lists.
| Sun | Mon | Tue | Wed | Thu | Fri | Sat |
|---|---|---|---|---|---|---|
import { Calendar } from "@etched/monet/date-picker";
// Fixed dates in 2026, so the examples look the same every day. Open calendars show past months, so "today" is never in them.
const day = (month: number, date: number) => new Date(2026, month - 1, date);
export function MonthAndYearLists() {
return <Calendar mode="single" captionLayout="dropdown" defaultMonth={day(6, 1)} startMonth={day(1, 1)} endMonth={day(12, 1)} />;
}Accessibility
Enter,SpaceorArrowDownopens the calendar on the chosen day, or today. Arrow keys move by day and week and skip disabled days.PageUp/PageDownmove by month (withShift, by year), andHome/Endto the week's ends.Enterpicks and closes;Escapecloses with no change.- The trigger is a
comboboxwitharia-haspopup="dialog", named bylabeloraria-label. The calendar is adialog, named by the trigger'saria-label, or bycalendarLabel("Choose a date"). - Each month is a
gridnamed by the month. Each day is named in full, such as "Monday, November 2, 2026, selected", with "Today" first for today. - Without
label, give the trigger anaria-label. Setlocaleto the page's language, so screen readers hear the month and day names in it. - The month buttons are named "Previous month" and "Next month"; pass translated names with
previousMonthLabelandnextMonthLabel.
API reference
Calendar
| Prop | Type | Default | Description |
|---|---|---|---|
| locale | string | the browser's language | A BCP 47 language tag for month and day names, the labels screen readers hear and the first day of the week. |
| value | Mode["selected"] | null | – | The chosen day, days or range, when the app controls it. |
| defaultValue | Mode["selected"] | null | – | The first chosen day, days or range, when the calendar keeps its own. |
| onValueChange | Mode["onSelect"] | – | Called with the new value, the day that was picked, its modifiers and the event (react-day-picker's onSelect). |
DatePicker
| Prop | Type | Default | Description |
|---|---|---|---|
| label | ReactNode | – | Visible label. Without one, give the control an aria-label. |
| labelTooltip | ReactNode | – | More about the field, behind an info button after the label. |
| labelTooltipLabel | string | "More information" | Names the info button. |
| description | ReactNode | – | Help text below the control. Hidden while an error shows. |
| error | FieldError | – | |
| min | Date | – | The earliest day that can be picked. The calendar does not go to earlier months. |
| max | Date | – | The latest day that can be picked. The calendar does not go to later months. |
| disabledDays | Matcher | Matcher[] | – | Days that cannot be picked: dates, { before: date }, { after: date }, { dayOfWeek: [0, 6] }, { from, to } or a function. |
| locale | string | the browser's language | A BCP 47 language tag for the field text, month and day names, and the first day of the week. |
| format | Intl.DateTimeFormatOptions | { dateStyle: "medium" } | How the field shows dates, as Intl.DateTimeFormat options. |
| placeholder | ReactNode | – | Shown while no date is chosen. |
| size | "sm" | "base" | "base" | Same heights as Input. |
| name | string | – | Submits the value with a form, as yyyy-mm-dd. |
| required | boolean | – | A form does not submit while no date is chosen; the trigger takes focus. |
| readOnly | boolean | – | Shows the value, but does not open. |
| open | boolean | – | Whether the calendar is open, when the app controls it. |
| defaultOpen | boolean | false | Whether the calendar is open at first. |
| onOpenChange | (open: boolean) => void | – | Called when the calendar opens or closes. |
| calendarLabel | string | "Choose a date", or "Choose dates" for a range | Names the calendar's dialog when the trigger has no aria-label. |
| previousMonthLabel | string | "Previous month" | Names the calendar's previous month button. |
| nextMonthLabel | string | "Next month" | Names the calendar's next month button. |
| container | BasePopover.Portal.Props["container"] | document.body | The element the calendar's portal goes in, for example inside a dialog with its own stacking. |
| value | Date | null | – | |
| defaultValue | Date | null | – | |
| onValueChange | (value: Date | null) => void | – |