Date picker
هذه الصفحة غير مترجمة بعد، وهي معروضة بالإنجليزية.
The slotsmith DatePicker is a React date picker for one date, several dates or a range. Values are ISO strings. It has bounds, blocked days, presets, a full keyboard grid and right-to-left support, and every part it renders is a slot you can replace.
Overview
The demo below uses the fallbacks only. Open a picker with the mouse or with ↓, move through the days with the arrow keys, and pick with Enter. The stay is a range that cannot start before today; its two shortcuts sit under the grid.
Due date
Stay
Due: none
Stay: none
import { useId, useState } from "react";
import { addDays, DatePicker, type DateRange, type ISODate } from "slotsmith/date-picker";
import { useToday } from "../shared/today";
export default function Overview() {
const dueLabel = useId();
const stayLabel = useId();
const today = useToday();
const [due, setDue] = useState<ISODate | null>(null);
const [stay, setStay] = useState<DateRange | null>(null);
return (
<div style={{ display: "grid", gap: "1rem", maxWidth: 288 }}>
<div>
<p id={dueLabel}>Due date</p>
{/* One date. Picking it closes the calendar. */}
<DatePicker value={due} onChange={setDue} aria-labelledby={dueLabel} />
</div>
<div>
<p id={stayLabel}>Stay</p>
{/* A range from today on, with two shortcuts under the grid. */}
<DatePicker
mode="range"
value={stay}
onChange={setStay}
minDate={today}
presets={[
{ label: "Next 7 days", value: { from: today, to: addDays(today, 6) } },
{ label: "Tomorrow", value: addDays(today, 1) },
]}
placeholder="Pick your dates"
aria-labelledby={stayLabel}
/>
</div>
<div>
<p>Due: {due ?? "none"}</p>
<p>Stay: {stay?.from ? `${stay.from} to ${stay.to ?? "…"}` : "none"}</p>
</div>
</div>
);
}import { useSyncExternalStore } from "react";
import { today, type ISODate } from "slotsmith/date-picker";
/** The date a prerendered page is built with, so its HTML is the same on every build. */
export const PRERENDER_TODAY: ISODate = "2026-03-16";
// Today does not change while the page is open, so there is nothing to subscribe to.
const subscribe = () => () => {};
/**
* Today as `YYYY-MM-DD`, safe to prerender: the fixed date on the server and
* while hydrating, so the markup matches, then the visitor's real date.
* An app that renders only in the browser can call `today()` directly.
*/
export function useToday(): ISODate {
return useSyncExternalStore(subscribe, today, () => PRERENDER_TODAY);
}Each feature has a guide with its own live demo: single, range and multiple, bounds and blocked days, presets and the “Go to today” control, day content such as a price under each date, validation with an error message, and the compound parts for your own layout.
Install
npm i slotsmithpnpm add slotsmithyarn add slotsmithbun add slotsmithimport "slotsmith/date-picker.css";Import the stylesheet once, at your app's entry. slotsmith/styles.css holds every component's styles instead.
Quick start
Pass value and onChange, and name the combobox with aria-label or aria-labelledby. A date is an ISO string, the instant the day starts ("2026-03-12T00:00:00+03:00"), or null when nothing is picked. valueFormat writes it another way.
import { useState } from "react";
import { DatePicker, type ISODate } from "slotsmith/date-picker";
import "slotsmith/date-picker.css";
export default function QuickStart() {
// A date is a "YYYY-MM-DD" string, or null when nothing is picked.
const [due, setDue] = useState<ISODate | null>(null);
// aria-label names the combobox; style and className go on the root element.
return <DatePicker value={due} onChange={setDue} aria-label="Due date" />;
}Guides
- Single, range and multiplePick one date, several dates or a range with the slotsmith DatePicker: the mode prop, the shape of the value in each mode, and how picking works.
- Typing a dateType a date into the slotsmith DatePicker's text field in the locale's order, open the calendar on focus, or use a single button trigger instead.
- Bounds and blocked daysLimit the slotsmith DatePicker with minDate and maxDate, block single days with disabledDates, and disable the whole control.
- PresetsAdd shortcuts such as Next 7 days under the slotsmith DatePicker's grid with the presets prop, and how the Go to today control differs from a preset.
- Day contentShow a price, a dot or a badge in each day of the slotsmith DatePicker with the DayContent slot, or add props to a day with slotProps.day.
- ValidationShow a failed check on the slotsmith DatePicker: the invalid prop, an error message tied to the combobox, and the --sdp-danger token.
- Value format and time zoneWrite the slotsmith DatePicker's value as DD/MM/YYYY, as an instant in a time zone, or your own way, and set the zone the calendar works in.
- Compound partsLay out the slotsmith DatePicker yourself with DatePicker.Provider and its parts, read its state from the context, or use the useDatePicker hook.
Reference
The API page is generated from the library’s source, so it lists every slot, prop and label the installed version has.
- SlotsEvery part you can replace, the props it receives and its fallback.
- PropsEvery prop, grouped, with its type and default.
- Labels & localesEvery string it shows or announces, and how to translate them.
- StylingIts class names and the tokens its stylesheet reads.
- AdaptersReady slot maps for shadcn/ui, MUI, Chakra, Ant Design and Radix Themes.
Adapters
The date picker works with any design system. Each slot takes a component, so a library’s own surface, buttons and selects can replace the fallbacks while the grid, the keyboard and the range logic stay the same. The Adapters page has ready slot maps for shadcn/ui, MUI, Chakra, Ant Design and Radix Themes, as examples of well-known libraries; the same approach works with your own.
Accessibility
The date picker follows the ARIA date picker dialog pattern: a combobox that opens a dialog holding a grid.
- The trigger is the element with
role="combobox": the text field by default, with the calendar button beside it named by theopenCalendarlabel, or the single button withtrigger="button". It is in the tab order, and carriesaria-haspopup="dialog",aria-expandedand, while open,aria-controlspointing at the popup. The naming and validation attributes you pass toDatePicker(aria-label,aria-labelledby,aria-describedby,aria-invalid,aria-requiredand the like, plusid) go to this element, not to the wrapper. With no name given, theplaceholderlabel names it; a text field with anidis left to its<label htmlFor>. - Invalid values.
invalidsetsaria-invalid="true"on the trigger anddata-invalidon the root and the trigger, and the default stylesheet draws the border in the danger colour. Point the trigger at your error message witharia-describedby. See validation. - The popup has
role="dialog"and is not modal (aria-modal="false"): focus is not trapped. It is named by thedialoglabel. Opening it moves focus to the picked day, or to today. When that day is outsideminDateandmaxDate, focus goes to the first day within them in the nearest allowed month. - The month is a
role="grid"named by the month and year (“March 2026”), withrow,columnheaderandgridcellinside. Each column header is named with the full weekday, so a one-letter header is still read as “Monday”. In multiple and range mode the grid hasaria-multiselectable="true". - Each day is a button named with its full date (“Monday, March 16, 2026”). Its cell has
aria-selected, and today hasaria-current="date". One day at a time is in the tab order (a rovingtabindex); the arrow keys move it. - Blocked days have
aria-disabled="true", not thedisabledattribute. They stay focusable, so the arrow keys cross them and a screen reader announces them, but they cannot be picked. See bounds and blocked days. - The header has a previous and a next button, named by the
previousandnextlabels and disabled at the bounds, and month and year selects named bymonthandyear. The clear button is named byclear. - Right to left. Inside a right-to-left ancestor the grid and the header mirror, and ← and → swap: ← moves to the next day, because that is the day on the left.
With focus in the text field (see Typing a date):
| Keys | Action |
|---|---|
| ↓ | Open the calendar and move into it, on the picked day or on today. |
| Enter | Read what was typed, and close the calendar if it is open. |
| Esc | Close the calendar; with it closed, drop what was typed. |
With focus on the button trigger (trigger="button"):
| Keys | Action |
|---|---|
| EnterSpace↓↑ | Open the calendar, with focus on the picked day or on today. |
| EnterSpaceEsc | With the calendar open: close it. |
With focus in the grid:
| Keys | Action |
|---|---|
| ←→ | Move one day. In right-to-left the two swap, so each arrow moves the way it points. |
| ↑↓ | Move one week. |
| HomeEnd | Move to the first or the last day of the week. |
| PgUpPgDn | Move one month. The month on screen follows. |
| Shift+PgUpShift+PgDn | Move one year. |
| EnterSpace | Pick the focused day. A blocked day is not picked. |
| Esc | Close, and move focus back to the trigger. |
| Tab | Move to the footer buttons, then out of the control. The calendar closes when focus lands on another element on the page; it stays open when focus leaves the page, such as for the address bar. |
Movement stops at minDate and maxDate. Picking a day in single mode closes the calendar and returns focus to the trigger; in multiple and range mode it stays open. A pointer press outside the control closes it. Every label can be translated or replaced; see Labels & locales.
Limitations
FAQ
What format are the date picker's values?
ISO strings, never Date objects. By default a day is the instant it starts in the picker's time zone, such as 2026-03-12T00:00:00+03:00, which new Date() reads as it is. valueFormat changes that: YYYY-MM-DD for the calendar day alone, another pattern such as DD/MM/YYYY, or a format of your own.
Can the date picker select a range or several dates?
Yes. The mode prop is single (the default), multiple or range. It also sets the shape of value and onChange: one date or null, an array of dates, or an object with from and to.
How do I block weekends or booked days?
Pass disabledDates, a function that receives a date and returns true to block it. minDate and maxDate set the earliest and latest date. Blocked days get aria-disabled and stay focusable, so the arrow keys cross them and a screen reader announces them.
Does the Go to today button pick today?
No. It shows today's month and makes today the day that takes focus, without changing the value. To pick today in one click, add a preset whose value is today.
Does the date picker work in Arabic and right to left?
Yes. Month and weekday names come from Intl in the locale you pass, and the labels from a locale pack. Inside a right-to-left ancestor the grid mirrors and the left and right arrow keys swap, so they follow what is on screen. The calendar itself is Gregorian.