# Date picker

> A React date picker for one date, several dates or a range, with bounds, blocked days, presets and a full keyboard grid. Every part is replaceable.

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 <kbd>↓</kbd>, move through the days with the arrow keys, and pick with <kbd>Enter</kbd>. The stay is a range that cannot start before today; its two shortcuts sit under the grid.

```tsx
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>
  );
}
```

Each feature has a guide with its own live demo: [single, range and multiple](https://slotsmith.dev/components/date-picker/guides/modes/), [bounds and blocked days](https://slotsmith.dev/components/date-picker/guides/bounds-and-blocked-days/), [presets](https://slotsmith.dev/components/date-picker/guides/presets/) and the "Go to today" control, [day content](https://slotsmith.dev/components/date-picker/guides/day-content/) such as a price under each date, [validation](https://slotsmith.dev/components/date-picker/guides/validation/) with an error message, and [the compound parts](https://slotsmith.dev/components/date-picker/guides/compound-parts/) for your own layout.

## Install

```bash
npm i slotsmith
```

```ts
import "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`](https://slotsmith.dev/components/date-picker/guides/value-format-and-time-zone/) writes it another way.

```tsx
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 multiple](https://slotsmith.dev/components/date-picker/guides/modes/): Pick 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 date](https://slotsmith.dev/components/date-picker/guides/typing/): Type 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 days](https://slotsmith.dev/components/date-picker/guides/bounds-and-blocked-days/): Limit the slotsmith DatePicker with minDate and maxDate, block single days with disabledDates, and disable the whole control.
- [Presets](https://slotsmith.dev/components/date-picker/guides/presets/): Add 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 content](https://slotsmith.dev/components/date-picker/guides/day-content/): Show 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.
- [Validation](https://slotsmith.dev/components/date-picker/guides/validation/): Show 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 zone](https://slotsmith.dev/components/date-picker/guides/value-format-and-time-zone/): Write 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 parts](https://slotsmith.dev/components/date-picker/guides/compound-parts/): Lay 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](https://slotsmith.dev/components/date-picker/api/) is generated from the library's source, so it lists every slot, prop and label the installed version has.

- [Slots](https://slotsmith.dev/components/date-picker/api/#slots): Every part you can replace, the props it receives and its fallback.
- [Props](https://slotsmith.dev/components/date-picker/api/#props): Every prop, grouped, with its type and default.
- [Labels & locales](https://slotsmith.dev/components/date-picker/api/#labels): Every string it shows or announces, and how to translate them.
- [Styling](https://slotsmith.dev/components/date-picker/api/#styling): Its class names and the tokens its stylesheet reads.
- [Adapters](https://slotsmith.dev/components/date-picker/adapters/): Ready 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](https://slotsmith.dev/components/date-picker/adapters/) 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 the [`openCalendar`](https://slotsmith.dev/components/date-picker/api/#label-openCalendar) label, or the single button with `trigger="button"`. It is in the tab order, and carries `aria-haspopup="dialog"`, `aria-expanded` and, while open, `aria-controls` pointing at the popup. The naming and validation attributes you pass to `DatePicker` (`aria-label`, `aria-labelledby`, `aria-describedby`, `aria-invalid`, `aria-required` and the like, plus `id`) go to this element, not to the wrapper. With no name given, the [`placeholder`](https://slotsmith.dev/components/date-picker/api/#label-placeholder) label names it; a text field with an `id` is left to its `<label htmlFor>`.
- **Invalid values.** `invalid` sets `aria-invalid="true"` on the trigger and `data-invalid` on the root and the trigger, and the default stylesheet draws the border in the danger colour. Point the trigger at your error message with `aria-describedby`. See [validation](https://slotsmith.dev/components/date-picker/guides/validation/).
- **The popup** has `role="dialog"` and is not modal (`aria-modal="false"`): focus is not trapped. It is named by the [`dialog`](https://slotsmith.dev/components/date-picker/api/#label-dialog) label. Opening it moves focus to the picked day, or to today. When that day is outside `minDate` and `maxDate`, 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"), with `row`, `columnheader` and `gridcell` inside. Each column header is named with the full weekday, so a one-letter header is still read as "Monday". In [multiple and range mode](https://slotsmith.dev/components/date-picker/guides/modes/) the grid has `aria-multiselectable="true"`.
- **Each day** is a button named with its full date ("Monday, March 16, 2026"). Its cell has `aria-selected`, and today has `aria-current="date"`. One day at a time is in the tab order (a roving `tabindex`); the arrow keys move it.
- **Blocked days** have `aria-disabled="true"`, not the `disabled` attribute. 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](https://slotsmith.dev/components/date-picker/guides/bounds-and-blocked-days/).
- **The header** has a previous and a next button, named by the [`previous`](https://slotsmith.dev/components/date-picker/api/#label-previous) and [`next`](https://slotsmith.dev/components/date-picker/api/#label-next) labels and disabled at the bounds, and month and year selects named by [`month`](https://slotsmith.dev/components/date-picker/api/#label-month) and [`year`](https://slotsmith.dev/components/date-picker/api/#label-year). The clear button is named by [`clear`](https://slotsmith.dev/components/date-picker/api/#label-clear).
- **Right to left.** Inside a right-to-left ancestor the grid and the header mirror, and <kbd>←</kbd> and <kbd>→</kbd> swap: <kbd>←</kbd> moves to the next day, because that is the day on the left.

With focus in the text field (see [Typing a date](https://slotsmith.dev/components/date-picker/guides/typing/#keyboard)):

| 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 |
| --- | --- |
| Enter, Space, ↓, ↑ | Open the calendar, with focus on the picked day or on today. |
| Enter, Space, Esc | 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. |
| Home, End | Move to the first or the last day of the week. |
| PgUp, PgDn | Move one month. The month on screen follows. |
| Shift+PgUp, Shift+PgDn | Move one year. |
| Enter, Space | 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](https://slotsmith.dev/components/date-picker/api/#labels).

## Limitations

- Gregorian calendar only. A locale changes the names, the digits and the first day of the week, not the calendar. Hijri and Persian calendars are planned; see the [roadmap](https://slotsmith.dev/roadmap/#calendars).
- Days only: there is no time of day to pick. A value is the instant the day starts, or the calendar day alone with `valueFormat`.
- A typed date is read in the locale's numeric order with a four-digit year, not in free text such as "next Friday"; see [Typing a date](https://slotsmith.dev/components/date-picker/guides/typing/). For another shape, such as an inline calendar, build on the [`useDatePicker`](https://slotsmith.dev/components/date-picker/guides/compound-parts/) hook.
- One month is shown at a time.
- A range can span blocked days: `disabledDates` stops a blocked day from being picked as an end, not from sitting inside the range. See [bounds and blocked days](https://slotsmith.dev/components/date-picker/guides/bounds-and-blocked-days/).
- The popup renders in place by default, with `position: fixed`, so it escapes `overflow: hidden` and makes up for an ancestor with a `transform`. Pass [`portal`](https://slotsmith.dev/components/date-picker/api/#props) to render it in `document.body` or an element of your own; set the `--ss-*` tokens and the dark class on `:root` or that element, which the popup then inherits from.

## 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. See https://slotsmith.dev/components/date-picker/guides/modes/

### 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. See https://slotsmith.dev/components/date-picker/guides/modes/

### 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. See https://slotsmith.dev/components/date-picker/guides/bounds-and-blocked-days/

### 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. See https://slotsmith.dev/components/date-picker/guides/presets/

### 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. See https://slotsmith.dev/components/date-picker/#accessibility
