Phase 9d · Tier B
RN-universal Calendar pilot
ScaffoldedMonth-grid date picker with single, multiple, and range modes. Mirrors the shadcn Calendar surface (which wraps react-day-picker). Hand-rolled — no new runtime dep. Same code path on web and native.
Side-by-side: single date
shadcn (react-day-picker)
May 2026
| Su | Mo | Tu | We | Th | Fr | Sa |
|---|---|---|---|---|---|---|
Selected: Fri May 01 2026
RN
May 2026
Sun
Mon
Tue
Wed
Thu
Fri
Sat
Selected: Fri May 01 2026
Multiple selection
Tap to toggle. Tap an already-selected day to deselect.
May 2026
Sun
Mon
Tue
Wed
Thu
Fri
Sat
0 day(s) selected: (none)
Range selection (with weekends disabled)
First tap sets the start; second tap sets the end. Tapping a third time starts a new range.
May 2026
Sun
Mon
Tue
Wed
Thu
Fri
Sat
26
2
3
9
10
16
17
23
24
30
31
6
From: (none) · To: (none)
From / to bounds
Constrain selectable dates with fromDate and toDate (e.g. an admissions intake window).
May 2026
Sun
Mon
Tue
Wed
Thu
Fri
Sat
26
27
28
29
30
1
2
3
4
5
6
Library decision
Per Phase 9 Q2, every Tier B primitive must record its library choice. RnCalendar ships with no new runtime dependency.
- react-day-picker — web-only (DOM
<table>/<button>elements + DOM event handlers). Same.web/.nativeblocker as cmdk / sonner / input-otp. - react-native-calendars@1.1314.0 (MIT, ~454 k/wk) — RN-only; not RN-Web-friendly. Adopting both would force a
.web/.nativesplit that no other Phase 9d primitive uses. - react-native-modal-datetime-picker (~700 k/wk) — modal wrapper around the OS pickers; surface doesn't match shadcn's inline grid.
- @expo/react-native-calendar-picker (~30 k/wk) — lacks range mode.
- This implementation — pure RN core.
DateAPI for math,View/Pressable/Textfor the grid. ~280 LOC; same shape as our other no-deps primitives.
Semantics
| Aspect | shadcn (react-day-picker) | RN | Notes |
|---|---|---|---|
| Modes | single / multiple / range | single / multiple / range | Same surface |
| Disabled dates | array or function or matcher | array or function | Subset of shadcn (no DateMatcher complex types) |
| Range bounds | fromDate / toDate / disabled | fromDate / toDate / disabled | Same |
| Outside days | showOutsideDays | showOutsideDays | Same default true |
| Locale | react-intl / dateFnsLocale | weekdayLabels + monthLabel props | Consumer brings their own i18n |
| Multiple months | numberOfMonths | single only in v1 | 0.3.x candidate |
| Keyboard nav | arrow keys | tap (Tab on RN-Web) | 0.3.x candidate |