# Date picker

> Inline month calendar: months slide with the finger, the selection circle travels from day to day and stretches toward the new one, a range sweeps its band in from the start day, and booked days are crossed out. A compact wheel variant picks day, month and year in the locale's order.

- Type: Component (inputs)
- Access: Free, open source (MIT)
- Page: https://goodui.dev/native/components/date-picker
- Markdown: https://goodui.dev/native/components/date-picker.md
- Registry item: https://goodui.dev/native/r/date-picker.json
- Dependencies: react-native-gesture-handler, react-native-reanimated, react-native-svg
- Keywords: react native date picker, calendar picker expo, date range picker, booking calendar, check in check out calendar, inline calendar, birthday wheel picker, localized calendar intl

## When to use

- Picking a day where the weekday and the shape of the month matter: appointments, deliveries, reservations.
- Picking a stay or a period: use DateRangePicker; booked days block a range from spanning them.
- Dates far from today such as a birthday: DatePicker variant="wheel".

## When not to use

- Choosing a day within the next week or two above a schedule: use week-strip.
- Choosing a time: use time-slots for bookable times or wheel-picker for any time.
- Typing a known date quickly (card expiry): use text-field with a mask.

## Installation

With the shadcn CLI (needs a components.json in the Expo app), then `npx expo install --fix` so native dependencies match your Expo SDK. The foundation comes along the first time.

```bash
npx shadcn@latest add https://goodui.dev/native/r/date-picker.json
npx expo install --fix
```

The component lands in `components/goodui-native/date-picker/` and imports the foundation from `components/goodui-native/foundation/` with relative paths; it also installs icon-button, locale, wheel-picker.

Built-in strings follow `<GooduiLocaleProvider>` (one [language pack](https://goodui.dev/native/r/locale-tr.json) translates the whole kit); the `labels` prop overrides them for one instance.

## Usage

```tsx
import { useState } from "react";
import { DatePicker } from "@/components/goodui-native/date-picker/date-picker";

export function DeliveryDay() {
  const [day, setDay] = useState<Date | null>(null);
  const today = new Date();
  return (
    <DatePicker
      accessibilityLabel="Delivery day"
      value={day}
      onValueChange={setDay}
      min={today}
      max={new Date(today.getFullYear(), today.getMonth() + 2, 0)}
      isDateDisabled={(d) => d.getDay() === 0}
    />
  );
}
```

## Examples

### Stay with booked nights

```tsx
<DateRangePicker
  accessibilityLabel="Stay"
  value={stay}
  onValueChange={setStay}
  min={new Date()}
  isDateBooked={(d) => bookedKeys.has(d.toDateString())}
/>
```

### Birthday wheel in German order

```tsx
<DatePicker variant="wheel" locale="de-DE" defaultValue={new Date(1990, 4, 17)} max={new Date()} onValueChange={setBirthday} />
```

### Days with events and a Monday start

```tsx
<DatePicker weekStartsOn={1} isDateMarked={(d) => hasEvents(d)} value={day} onValueChange={setDay} />
```

## API reference

### DatePicker

`import { DatePicker } from "@/components/goodui-native/date-picker/date-picker";`

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `min` | `Date` | – | Earliest selectable day. Earlier days are disabled and the calendar does not page before its month. |
| `max` | `Date` | – | Latest selectable day. Later days are disabled and the calendar does not page past its month. |
| `isDateDisabled` | `(date: Date) => boolean` | – | Days that cannot be chosen, for example closed Sundays. Shown dimmed. |
| `isDateBooked` | `(date: Date) => boolean` | – | Days that are taken, for example fully booked. Shown crossed out, cannot be chosen, and a range cannot span them. |
| `isDateMarked` | `(date: Date) => boolean` | – | Days with something on them get a dot under the number. |
| `locale` | `string` | – | BCP 47 locale for month and weekday names, digits and the first day of the week. Defaults to the GooduiLocaleProvider's language, else the device locale. |
| `weekStartsOn` | `0 \| 1 \| 2 \| 3 \| 4 \| 5 \| 6` | – | First column: 0 Sunday to 6 Saturday. Defaults to the locale's first day of the week. |
| `defaultMonth` | `Date` | – | Month shown first when nothing is selected. Defaults to the selected date's month, else this month. |
| `onMonthChange` | `(month: Date) => void` | – | Called with the first day of the month after paging. |
| `haptic` | `boolean` | – | `false` silences the select haptic on a day and the warning on a refused range. |
| `accessibilityLabel` | `string` | – | Names the calendar for screen readers, for example "Check-in date". |
| `labels` | `Partial<DatePickerLabels>` | – | Overrides for the built-in strings; a GooduiLocaleProvider pack sets them app-wide. |
| `style` | `StyleProp<ViewStyle>` | – | Style for the outer container. |
| `testID` | `string` | – | Identifier for end-to-end tests. |
| `value` | `Date \| null` | – | Selected day (controlled). `null` for none. |
| `defaultValue` | `Date \| null` | – | Starting day when uncontrolled. |
| `onValueChange` | `(date: Date) => void` | – | Called with the chosen day (time set to midnight local). |
| `variant` | `"calendar" \| "wheel"` | `"calendar"` | `calendar` is an inline month grid. `wheel` is a compact day, month and year wheel in the locale's order, for birthdays and dates far from today. |

### DateRangePicker

`import { DateRangePicker } from "@/components/goodui-native/date-picker/date-picker";`

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `min` | `Date` | – | Earliest selectable day. Earlier days are disabled and the calendar does not page before its month. |
| `max` | `Date` | – | Latest selectable day. Later days are disabled and the calendar does not page past its month. |
| `isDateDisabled` | `(date: Date) => boolean` | – | Days that cannot be chosen, for example closed Sundays. Shown dimmed. |
| `isDateBooked` | `(date: Date) => boolean` | – | Days that are taken, for example fully booked. Shown crossed out, cannot be chosen, and a range cannot span them. |
| `isDateMarked` | `(date: Date) => boolean` | – | Days with something on them get a dot under the number. |
| `locale` | `string` | – | BCP 47 locale for month and weekday names, digits and the first day of the week. Defaults to the GooduiLocaleProvider's language, else the device locale. |
| `weekStartsOn` | `0 \| 1 \| 2 \| 3 \| 4 \| 5 \| 6` | – | First column: 0 Sunday to 6 Saturday. Defaults to the locale's first day of the week. |
| `defaultMonth` | `Date` | – | Month shown first when nothing is selected. Defaults to the selected date's month, else this month. |
| `onMonthChange` | `(month: Date) => void` | – | Called with the first day of the month after paging. |
| `haptic` | `boolean` | – | `false` silences the select haptic on a day and the warning on a refused range. |
| `accessibilityLabel` | `string` | – | Names the calendar for screen readers, for example "Check-in date". |
| `labels` | `Partial<DatePickerLabels>` | – | Overrides for the built-in strings; a GooduiLocaleProvider pack sets them app-wide. |
| `style` | `StyleProp<ViewStyle>` | – | Style for the outer container. |
| `testID` | `string` | – | Identifier for end-to-end tests. |
| `value` | `DateRange` | – | Selected range (controlled). `end` is null while the second day is still to be picked. |
| `defaultValue` | `DateRange` | – | Starting range when uncontrolled. |
| `onValueChange` | `(range: DateRange) => void` | – | Called after each tap: first with `{ start, end: null }`, then with both days. |
| `rangeBlockedMessage` | `string` | – | Text announced and shown nowhere else when a range would span a booked or disabled day. Same as `labels.rangeBlocked`. |

## Accessibility

- Every day is a button whose label starts with its number, then the full date from Intl, then states: today, fully booked or unavailable, start of range, end of range, in range, has events. accessibilityState carries selected and disabled.
- On the web days are buttons with aria-pressed and aria-disabled. One day is focusable at a time: arrows move by a day or a week, Page Up and Page Down by a month, Home and End to the week's ends, Enter or Space picks; moving past the month pages the calendar.
- The month title is a header and a polite live region, so paging is announced on Android. Previous month and Next month are icon buttons with labels and disable at min and max.
- A range that would span a booked or disabled day is refused with a warning haptic and an announcement (rangeBlockedMessage), and the tapped day starts a new range.
- Weekday names above the grid are hidden from screen readers; each day's label already names its weekday. Months off screen are hidden from screen readers.
- Cells are 44 pt tall and a seventh of the width.
- The wheel variant is three adjustable wheels (Day, Month, Year) in a group named by accessibilityLabel.

## Motion

- Months sit side by side and follow a horizontal drag 1:1, with resistance before min and past max; release pages one month by position and velocity on the smooth spring. The arrows page on the same spring.
- Single: one accent circle travels from day to day. The edges facing the new day lead on the snappy spring and the trailing edges follow on smooth, so it stretches toward the target and settles. Each number turns to the on-accent color by how much the circle covers it. A circle appearing in a month grows from 40 % on the morph spring.
- Range: start and end circles grow in on the morph spring and, when the end is picked, the band sweeps from the start day to the end day across week rows on the smooth spring.
- Reduced motion: paging with the arrows becomes an instant change with a short fade, a swipe settles with a short timing, the circle jumps (a newly shown one fades in), end circles and the band are simply there.

## Haptics

- select when a day is picked
- warning when a range is refused because it spans an unavailable day
- haptic={false} silences both

## Performance

- Only the visible month and its two neighbours are mounted; a far jump lands one month short and pages in.
- The paging position, the travelling circle, number colors and the band run on the UI thread; React renders when the month or the selection changes, never per frame.
- Pass stable functions for isDateDisabled, isDateBooked and isDateMarked when they are expensive: they run for every day of the three mounted months on each render.

## Notes for AI

- DatePicker value is Date | null; DateRangePicker value is { start, end } and onValueChange fires on both taps (end is null after the first). Tapping the start day again clears the range.
- Dates come back at local midnight. Compare days with toDateString() or year, month and date, not getTime().
- Month and weekday names, digits and the first weekday come from Intl with the locale prop; do not pass translated arrays. weekStartsOn overrides the first weekday.
- isDateBooked crosses a day out and blocks ranges across it; isDateDisabled dims it; min and max also stop paging.
- variant="wheel" exists on DatePicker only and ignores isDateBooked and isDateMarked.

## Related

- [Week strip](https://goodui.dev/native/components/week-strip.md): A horizontal week of days: one accent pill slides between days and stretches toward the new one, weeks page with the finger 1:1, dots mark busy days, and a header shows the month with a Today button.
- [Time slots](https://goodui.dev/native/components/time-slots.md): A booking grid of time slots grouped into morning, afternoon and evening: the picked slot pops into the accent, taken slots are crossed out, low availability shows "2 left", and a summary with the end time and the confirm button rides under the grid or pinned to the bottom of the screen.
- [Wheel picker](https://goodui.dev/native/components/wheel-picker.md): iOS-style wheel: rows sit on a cylinder in 3D perspective, follow the finger 1:1, keep the fling's momentum and settle exactly on a row with a select tick for every row that passes. Compose several into one picker with WheelPickerGroup. On the web it scrolls with the mouse wheel and drags with the mouse.
- [Bottom sheet](https://goodui.dev/native/components/bottom-sheet.md): A sheet that rests at detents, follows the finger 1:1, settles with velocity and hands drags off to its scroll view.

## Guidance for AI tools

Date picker: Inline month calendar: months slide with the finger, the selection circle travels from day to day and stretches toward the new one, a range sweeps its band in from the start day, and booked days are crossed out. A compact wheel variant picks day, month and year in the locale's order. Use only the documented props. Keep accessibility, reduced motion and both color schemes intact when adapting it.

Full library index: https://goodui.dev/native/llms.txt
