diff --git a/.changeset/date-picker.md b/.changeset/date-picker.md
new file mode 100644
index 00000000..f6c990ea
--- /dev/null
+++ b/.changeset/date-picker.md
@@ -0,0 +1,24 @@
+---
+"@tailor-platform/app-shell": minor
+---
+
+Add DateField, DatePicker, and Calendar components (@internationalized/date + Base UI implementation)
+
+Introduces three accessible date-input components, implemented on `@internationalized/date` (value layer) and Base UI (`Popover`) with hand-rolled segmented-input and calendar-grid behaviour:
+
+- `DateField` — segmented date/time input with label, description, and error message
+- `DatePicker` — date field with a popover calendar
+- `Calendar` — standalone calendar grid
+
+All three accept `LocalizedString` labels/descriptions and resolve locale + timezone from the AppShell context. The `@internationalized/date` value types (`CalendarDate`, `CalendarDateTime`, `ZonedDateTime`, …) and helpers (`today`, `parseDate`, `getLocalTimeZone`, …) are re-exported from `@tailor-platform/app-shell`.
+
+New AppShell context hooks:
+
+- `useResolvedLocale()` — full BCP-47 locale (e.g. `"en-GB"`) plus the language code
+- `useTimeZone()` — the configured IANA timezone, falling back to the user's local timezone
+
+AppShell now accepts an optional `timeZone` prop.
+
+> This is the **`@internationalized/date` + Base UI** variant — the lighter foundation tracked in the design proposal (§9). Net-new dependency is just `@internationalized/date` (~11 KB gz); Base UI is already in the bundle. Public API and accessibility contract are identical to the react-aria variant.
+>
+> **Known limitation:** the segmented input is built from `role="spinbutton"` elements that aren't `contentEditable`, so on-screen-keyboard typing on touch devices is limited — the calendar popover is the touch-friendly path (desktop keyboard entry works fully). The APG behaviour is unit-tested but not yet screen-reader-audited.
diff --git a/docs/components/data-table.md b/docs/components/data-table.md
index 1c0d0c9f..5b9c8140 100644
--- a/docs/components/data-table.md
+++ b/docs/components/data-table.md
@@ -492,7 +492,7 @@ The `filter` property on a column accepts a `FilterConfig` object. When set, the
| `string` | Text | `eq`, `ne`, `contains`, `notContains`, `hasPrefix`, `hasSuffix`, `notHasPrefix`, `notHasSuffix`, `in`, `nin` |
| `number` | Number | `eq`, `ne`, `gt`, `gte`, `lt`, `lte`, **`between`**, `in`, `nin` |
| `datetime` | Datetime-local | `eq`, `ne`, `gt`, `gte`, `lt`, `lte`, **`between`**, `in`, `nin` |
-| `date` | Date | `eq`, `ne`, `gt`, `gte`, `lt`, `lte`, **`between`**, `in`, `nin` |
+| `date` | **DatePicker** | `eq` (_exact date_), `gte` (_after_), `lte` (_before_), **`between`** |
| `time` | Time | `eq`, `ne`, `gt`, `gte`, `lt`, `lte`, **`between`**, `in`, `nin` |
| `enum` | Dropdown | `eq`, `ne`, `in`, `nin` |
| `boolean` | Toggle | `eq`, `ne` |
@@ -500,6 +500,19 @@ The `filter` property on a column accepts a `FilterConfig` object. When set, the
When the user selects the `between` operator on a `number`, `datetime`, `date`, or `time` column, the filter chip renders a range input with **min** and **max** bounds.
+### Date Filters
+
+`date` columns use the app-shell [`DatePicker`](./date-picker.md) as the filter input (single value and `between` ranges) and present a friendlier, slimmer operator set:
+
+| Operator | Label | Meaning |
+| --------- | ------------ | -------------------------- |
+| `eq` | _exact date_ | matches that calendar date |
+| `gte` | _after_ | on or after (inclusive) |
+| `lte` | _before_ | on or before (inclusive) |
+| `between` | _between_ | inclusive min–max range |
+
+`gt` / `lt` / `ne` are intentionally dropped — the inclusive _after_ / _before_ cover the intent. The filter chip shows the value as a locale-formatted date (e.g. `15 Jun 2026`), and the picker resolves its locale/timezone from the AppShell context. (Only `date` is remapped this way; `datetime` and `time` keep the full numeric operator set and native inputs.)
+
### String Filter Case Sensitivity
String filters are **case-insensitive by default** — they use the Tailor Platform `regex` operator with an `(?i)` prefix. The filter chip renders a **"Case sensitive"** checkbox that lets users opt into exact-case matching.
diff --git a/docs/components/date-picker.md b/docs/components/date-picker.md
new file mode 100644
index 00000000..8f0a9dec
--- /dev/null
+++ b/docs/components/date-picker.md
@@ -0,0 +1,182 @@
+---
+title: DatePicker
+description: Accessible date input components (@internationalized/date + Base UI)
+---
+
+# DatePicker
+
+Three related components for date input — a segmented field, a field with a calendar popover, and a standalone calendar grid. Built on [`@internationalized/date`](https://react-spectrum.adobe.com/internationalized/date/) (the value layer) and Base UI (`Popover`), with the segmented input and calendar grid implemented to the [ARIA Authoring Practices](https://www.w3.org/WAI/ARIA/apg/patterns/) date-picker/grid patterns. They integrate automatically with AppShell's locale and timezone context.
+
+> **Implementation note.** This is the `@internationalized/date` + Base UI variant. The public API and accessibility contract are identical to the react-aria variant; only the internals differ. See `docs/proposals/date-picker-impl-comparison.md`.
+
+## Import
+
+```tsx
+import {
+ DateField,
+ DatePicker,
+ Calendar,
+ // Date value helpers (re-exported from @internationalized/date)
+ today,
+ parseDate,
+ getLocalTimeZone,
+ type CalendarDate,
+ type DateValue,
+} from "@tailor-platform/app-shell";
+```
+
+No separate `@internationalized/date` install needed — the value types and helpers are re-exported from `@tailor-platform/app-shell`.
+
+## DateField
+
+A segmented input that lets users type dates digit-by-digit, with per-segment Up/Down, type-to-fill auto-advance, and full keyboard support.
+
+```tsx
+
+```
+
+### With description and error
+
+```tsx
+
+```
+
+### Controlled
+
+```tsx
+const [date, setDate] = useState(null);
+;
+```
+
+## DatePicker
+
+A `DateField` with a calendar popover.
+
+```tsx
+
+```
+
+### Constrained + unavailable dates
+
+```tsx
+ {
+ const dow = date.toDate(getLocalTimeZone()).getDay();
+ return dow === 0 || dow === 6; // weekends
+ }}
+/>
+```
+
+### Week start
+
+```tsx
+
+```
+
+## Calendar
+
+A standalone calendar grid for custom date-selection UIs (e.g. reporting filters).
+
+```tsx
+ console.log(date)} />
+```
+
+## Localization
+
+Locale and timezone come from AppShell automatically. Override per field with `locale` / `timeZone`:
+
+```tsx
+
+```
+
+Segment order, first-day-of-week, and month/weekday names all follow the resolved locale.
+
+## Keyboard
+
+- **Segments:** `↑`/`↓` increment/decrement, digits type-to-fill (auto-advance), `←`/`→` move between segments, `Backspace` clears.
+- **Calendar grid:** arrows move by day/week, `Home`/`End` to week start/end, `PageUp`/`PageDown` by month, `Shift`+`PageUp`/`PageDown` by year, `Enter`/`Space` selects.
+
+## Accessibility
+
+- The segmented field is a labelled `role="group"` of `role="spinbutton"` segments with `aria-valuemin`/`max`/`now`/`text`.
+- The calendar is a `role="grid"`; each day is a button with a full-date `aria-label`; disabled/unavailable days are announced via `aria-disabled`.
+- The popover is a labelled `role="dialog"`.
+
+> **Known limitations (this variant).** The segments are `
` that aren't `contentEditable`, so a touch device's on-screen keyboard doesn't open for typing — on mobile, use the calendar popover to pick a date (desktop keyboard entry and the calendar both work fully). The APG patterns are implemented and unit-tested but **not yet screen-reader-audited**, and RTL arrow-key flipping isn't handled. See [the implementation comparison](../proposals/date-picker-impl-comparison.md) ("Known gaps vs react-aria") for the full list.
+
+## Props
+
+The tables below list props this variant **actually implements** for v1 (date granularity). A few props are part of the type surface — kept identical to the react-aria variant so a later swap is source-compatible — but aren't acted on yet; those are called out under [Proposed / not yet implemented](#proposed--not-yet-implemented).
+
+### DateFieldProps
+
+| Prop | Type | Description |
+| ----------------------------------------- | -------------------------------- | ----------------------------------------------------------------------- |
+| `label` | `LocalizedString` | Field label |
+| `description` | `LocalizedString` | Helper text below the field |
+| `errorMessage` | `LocalizedString` | Error text; also sets the invalid state |
+| `value` / `defaultValue` | `DateValue \| null` | Controlled / uncontrolled value (`CalendarDate` at date granularity) |
+| `onChange` | `(v: DateValue \| null) => void` | Fires on a complete, valid value; `null` when cleared |
+| `isDisabled` / `isReadOnly` / `isInvalid` | `boolean` | State flags |
+| `isRequired` | `boolean` | Sets `aria-required` on the segments (no visual required indicator yet) |
+| `placeholderValue` | `DateValue` | Seeds unset segments (increment start + segment order) |
+| `autoFocus` | `boolean` | Focus the first segment on mount |
+| `locale` | `string` | BCP-47 locale override (defaults to the AppShell formatting locale) |
+| `name` | `string` | Emits a hidden `` with the ISO value for form submission |
+| `aria-label` | `string` | Accessible name when there's no visible `label` (e.g. compact filters) |
+| `className` | `string` | Root element class |
+
+> `DateField` has no calendar, so `minValue` / `maxValue` / `isDateUnavailable` don't apply to it — they're honoured by `DatePicker` and `Calendar` below.
+
+### DatePickerProps
+
+All `DateFieldProps`, plus:
+
+| Prop | Type | Description |
+| ----------------------- | ------------------------------------------------------------- | -------------------------------------------------------------------- |
+| `minValue` / `maxValue` | `DateValue` | Earliest / latest selectable date in the calendar |
+| `isDateUnavailable` | `(date: DateValue) => boolean` | Mark individual dates unselectable (still keyboard-navigable) |
+| `firstDayOfWeek` | `"sun" \| "mon" \| "tue" \| "wed" \| "thu" \| "fri" \| "sat"` | Force the calendar's first column; omit to follow the locale |
+| `timeZone` | `string` | IANA timezone for resolving "today"; defaults to AppShell `timeZone` |
+
+### CalendarProps
+
+The standalone calendar grid. It has no segmented input, so its surface is listed in full:
+
+| Prop | Type | Description |
+| -------------------------------------- | ------------------------------ | ------------------------------------------------------------- |
+| `value` / `defaultValue` | `DateValue \| null` | Controlled / uncontrolled selected date |
+| `onChange` | `(v: DateValue) => void` | Fires when a date is selected |
+| `minValue` / `maxValue` | `DateValue` | Earliest / latest selectable date |
+| `isDateUnavailable` | `(date: DateValue) => boolean` | Mark individual dates unselectable (still keyboard-navigable) |
+| `focusedValue` / `defaultFocusedValue` | `DateValue` | Controlled / initial focused (visible) date |
+| `onFocusChange` | `(date: CalendarDate) => void` | Fires when the focused date changes (arrows, month paging) |
+| `firstDayOfWeek` | `"sun" \| "mon" \| …` | Force the first column; omit to follow the locale |
+| `isDisabled` / `isReadOnly` | `boolean` | Disable the grid / prevent selection changes |
+| `timeZone` | `string` | IANA timezone for "today"; defaults to AppShell `timeZone` |
+| `locale` | `string` | BCP-47 locale override |
+| `aria-label` / `aria-labelledby` | `string` | Accessible name for the grid |
+| `className` | `string` | Root element class |
+
+### Proposed / not yet implemented
+
+Accepted by the prop types (for parity with the react-aria variant) but **not acted on** in this variant yet:
+
+| Prop | Type | Status |
+| -------------- | ----------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
+| `granularity` | `"day" \| "hour" \| "minute" \| "second"` | Only `"day"` is supported (the default). Time granularities — and the `CalendarDateTime` / `ZonedDateTime` values they produce — are the tracked **DateTime fast-follow**; the calendar has no time selection yet. |
+| `hourCycle` | `12 \| 24` | No effect until time granularity lands (12h/24h only matters with an hour segment). |
+| `hideTimeZone` | `boolean` | Unused; only relevant to `ZonedDateTime` display (time granularity). |
+
+See the proposal's [Post-v1 fast-follows](../proposals/date-picker.md) for the DateTime plan.
+
+## Related
+
+- [Form](./form.md) — wrap date fields with validation
+- [Input](./input.md) — plain text input
diff --git a/docs/proposals/date-picker-impl-comparison.md b/docs/proposals/date-picker-impl-comparison.md
new file mode 100644
index 00000000..4e2e89f5
--- /dev/null
+++ b/docs/proposals/date-picker-impl-comparison.md
@@ -0,0 +1,63 @@
+# DatePicker — implementation comparison (measured)
+
+Two complete, API-identical implementations of `DateField` / `DatePicker` / `Calendar` were built to validate the design proposal's library-choice analysis (`docs/proposals/date-picker.md`, §1.1 and §9) against real code:
+
+| Branch | Behaviour/a11y foundation |
+| --------------------------------- | ----------------------------------------------------- |
+| `feat/date-picker-pp-1093` | **react-aria-components** (Adobe) |
+| `feat/date-picker-baseui-pp-1093` | **`@internationalized/date` + Base UI** (this branch) |
+
+Both expose the **same public API** (`DateFieldProps` / `DatePickerProps` / `CalendarProps`), the same `LocalizedString` label/description/error props, the same AppShell `useResolvedLocale()` / `useTimeZone()` wiring, and the same `@internationalized/date` re-exports. A consumer cannot tell them apart from the import surface — exactly the "foundation-agnostic API" the proposal relies on for a low-regret swap.
+
+## What "Base UI + raw @internationalized/date" actually means
+
+Base UI has **no date primitives**, so this branch hand-rolls the behaviour react-aria would otherwise supply, on top of two new state engines:
+
+- **`use-date-field-state.ts`** — segmented spinbutton field: locale-driven segment ordering via `DateFormatter.formatToParts`, per-segment increment/decrement with context-aware limits (e.g. day max from `endOfMonth`), numeric type-to-fill with auto-advance, and value-type discrimination by `granularity` (`CalendarDate` / `CalendarDateTime` / `ZonedDateTime`). No `Date` ever escapes.
+- **`use-calendar-state.ts`** — calendar grid: locale + first-day-of-week week computation (`getWeeksInMonth` / `startOfWeek`), selection, roving focus, and full APG keyboard navigation (arrows, Home/End, PageUp/PageDown, Shift+PageUp/Down).
+
+Base UI provides only the **`Popover`** (positioning, dismiss, portal) and the field's label/description/error structure idioms.
+
+## Measured bundle cost (esbuild, minified + gzipped, react/react-dom external)
+
+| Date slice | gzipped | Net-new for an app-shell consumer |
+| ------------------------------------------------- | ----------: | ------------------------------------------------ |
+| `@internationalized/date` only | **11.2 KB** | both branches pay this |
+| Base UI `Popover` + `@internationalized/date` | 46.3 KB | Base UI already in bundle → marginal ≈ **11 KB** |
+| react-aria date slice + `@internationalized/date` | 73.7 KB | whole second foundation → **+73.7 KB** |
+
+The decisive point: **Base UI is already in the app-shell bundle** (Dialog, Select, Combobox, Popover all use it), so the marginal dependency cost of the date components on this branch is essentially just `@internationalized/date` (~11 KB gz). The react-aria branch adds an entire second headless foundation (~74 KB gz) that overlaps Base UI. This confirms the proposal's projected ~11 KB vs ~73 KB (§1.1).
+
+## Cost we take on: code we now own
+
+| Implementation | Source LOC | Hand-rolled behaviour logic |
+| ----------------------------------- | ---------: | ---------------------------- |
+| react-aria variant | ~565 | 0 (rented from Adobe) |
+| `@internationalized/date` + Base UI | ~1,526 | ~670 (the two state engines) |
+
+~2.7× the source, ~670 LOC of date-interaction behaviour to maintain ourselves — the "highest build effort" the proposal flagged, now quantified.
+
+## Parity verified
+
+- **Tests:** the same 23-test suite (public behaviour + the DOM a11y contract — spinbutton segments, `role="grid"` cells with `data-selected`/`data-disabled`/`data-unavailable`/`data-outside-month`, `role="dialog"` popover, roving-focus keyboard nav) passes against both implementations unchanged.
+- **Localization (verified in-browser):** segment order follows the locale — `en-US` → `mm/dd/yyyy`, `en-GB` → `dd/mm/yyyy`, `ja-JP` → `yyyy/mm/dd`; first-day-of-week follows the locale (Monday-first for `en-GB`); month/weekday names and the cell `aria-label` are `DateFormatter`-localized.
+- **Value correctness:** `onChange` emits `@internationalized/date` values (never `Date`); time is preserved when picking a day in a date+time picker.
+
+## Accessibility coverage (matched, verified in-browser)
+
+- **Segmented field:** labelled `role="group"` of `role="spinbutton"` segments carrying `aria-valuemin`/`max`/`now`/`text`, `aria-label`, `aria-invalid`, `aria-disabled`, `aria-readonly`; per-segment `↑`/`↓`, digit type-to-fill with auto-advance, `←`/`→`/`Home`/`End` between segments, `Backspace` to clear. Errors render as `role="alert"` and link via `aria-describedby`.
+- **Calendar grid:** `role="grid"` with localized `columnheader`s (full-day `aria-label`s), full-date cell `aria-label`s, roving `tabindex`, and APG keyboard — `←`/`→` day, `↑`/`↓` week, `Home`/`End` week ends, `PageUp`/`PageDown` month, `Shift`+`PageUp`/`PageDown` year, `Enter`/`Space` select. Focus follows across month boundaries; the month heading is an `aria-live` region.
+- **Popover/dialog:** opening the picker moves focus into the grid (today/selected cell) so arrows work immediately; selecting or `Escape` closes it and returns focus to the trigger (Base UI). The trigger gets `aria-haspopup="dialog"`/`aria-expanded` from Base UI.
+
+## Known gaps vs react-aria (the price of owning it)
+
+- **Mobile/touch text entry:** react-aria's segments are `contentEditable` spans with `inputmode="numeric"`, so a touch device surfaces the on-screen keyboard and types into them. Ours are **non-editable** (`contentEditable={false}`) `role="spinbutton"` divs driven by `keydown` — which soft keyboards don't emit — so touch typing doesn't work (desktop keyboard + the calendar both work fully; the calendar is the touch path). The biggest remaining gap, but **addressable** by matching react-aria: make the segments `contentEditable` with `inputmode="numeric"` and route `beforeinput` (a focused, contained fast-follow — needs real-device QA).
+- **RTL arrow direction:** `←`/`→` are LTR-fixed; in RTL locales react-aria flips them. Needs a direction check in the grid key handler.
+- **Calendar systems:** robust for Gregorian; the 13 non-Gregorian calendars react-aria supports automatically are not wired up (the value layer can represent them; the field/grid interaction would need extension).
+- **Localized chrome strings:** segment placeholders (`yyyy`/`mm`/`dd`) and the prev/next-month button labels are English; react-aria localizes these from its bundled CLDR strings.
+- **Dedicated navigation live-region:** we rely on roving focus + cell `aria-label` (the APG-standard announcement) plus an `aria-live` month heading; react-aria adds an extra visually-hidden live region for navigation/selection announcements.
+- **Breadth of SR/browser testing:** react-aria ships vendor-maintained cross-browser + screen-reader coverage; ours is the APG pattern implemented and unit-tested, not yet SR-audited.
+
+## Takeaway
+
+The Base-UI-native build is real and shippable: identical API, identical a11y/DOM contract, full localization for the common case, at ~1/7th the dependency weight — at the cost of ~670 LOC of behaviour we maintain and a few advanced-i18n gaps. This is precisely the trade the proposal framed as the §9 "future possibility," now de-risked with working code on both sides.
diff --git a/docs/proposals/date-picker.md b/docs/proposals/date-picker.md
new file mode 100644
index 00000000..d95e3106
--- /dev/null
+++ b/docs/proposals/date-picker.md
@@ -0,0 +1,449 @@
+# Proposal: DatePicker / DateField (and future DateRangePicker)
+
+> Status: **Decided — v1 builds on the §9 lighter foundation (`@internationalized/date` + Base UI), approved 2026-06-26 (superseding the original `react-aria-components` decision). See the Revision note.**
+> Scope: v1 ships `DateField`, `DatePicker`, and a standalone `Calendar`. `DateRangePicker` / `RangeCalendar` are designed-for but deferred.
+
+## Revision — 2026-06-26 (foundation swap approved)
+
+The original v1 foundation decision (`react-aria-components`, 2026-06-17, §1 below) has been **superseded**: v1 now builds on the **§9 lighter foundation** — `@internationalized/date` + Base UI — measured at **~11 KB gz vs ~74 KB** for react-aria, with the same public API and value layer (see [`date-picker-impl-comparison.md`](date-picker-impl-comparison.md)). The swap was **approved on 2026-06-26**.
+
+- **Decision (v1):** ship the `@internationalized/date` + Base UI variant. Public API and value layer are unchanged, so this is invisible to consumers.
+- **Trade-off accepted:** we own ~670 LOC of APG behaviour instead of Adobe-audited primitives. Known gaps tracked for follow-up: **mobile/touch typing** (segments are `role="spinbutton"` with no hidden numeric ``, so on-screen-keyboard entry is limited — the calendar is the touch path), **RTL arrow-key flipping**, and the pattern is unit-tested but **not yet screen-reader-audited**.
+
+## Post-v1 fast-follows (Base UI variant)
+
+Owning the segmented input + grid means a handful of behaviours react-aria gave us for free are now our own follow-up work. The full inventory is in [`date-picker-impl-comparison.md`](date-picker-impl-comparison.md) ("Known gaps vs react-aria"); the highest-value item is written up here so the approach isn't lost.
+
+### Mobile / touch text entry (biggest gap)
+
+**Problem.** Our segments are `role="spinbutton"` `
`s with `contentEditable={false}`, and input is read only from `keydown`. On-screen keyboards don't emit usable `keydown`, and with no editable element focused the keyboard doesn't surface at all — so on touch devices you can't _type_ a date (the calendar popover is the only touch path). Desktop keyboard + calendar work fully.
+
+**Approach — mirror react-aria.** react-aria makes each segment a `contentEditable` span with `inputmode="numeric"`; that's exactly what surfaces the numeric soft keyboard and lets it type into the element (verified: react-aria's month segment is ``). To match it in `DateInputGroup`:
+
+1. Render each editable segment with `contentEditable` (when not disabled/readonly), `inputMode="numeric"` (omit for the AM/PM segment), plus `enterKeyHint="next"`, `autoCorrect="off"`, `autoCapitalize="off"`, `spellCheck={false}`. Keep the transparent caret so the field still renders our locale-formatted text, not an editing caret.
+2. Add a **native** `beforeinput` listener that **always `preventDefault`s** — the segment text stays React-controlled, so the browser must never mutate the `contentEditable` DOM — then translates the event into the existing engine:
+ - `insertText` → per character: digit → `setDigit(...)` (same typed-count / auto-advance path as `keydown`); `a`/`p` on the day-period segment → `setDayPeriod`.
+ - `deleteContentBackward` / `deleteContentForward` → `clearSegment`.
+ - anything else → ignored (already prevented), which also swallows stray characters so the segment can't accumulate junk.
+3. Keep the current `keydown` path for physical keyboards. The two are mutually exclusive: a handled `keydown` calls `preventDefault`, which suppresses the follow-on `beforeinput`; soft keyboards emit no actionable `keydown`, so `beforeinput` is the only path that fires there.
+
+**Why native `beforeinput`, not React's `onBeforeInput`:** React's synthetic `onBeforeInput` is unreliable for cancelling the native default on `contentEditable`; a native listener (attached via ref/effect) makes `preventDefault` reliably stop DOM mutation across browsers.
+
+**Scope:** touches only `DateInputGroup` (segment attributes + the input handler). No public-API or value-layer change. Segment DOM snapshots update (the new `contentEditable`/`inputmode` attributes).
+
+**Verification:** the desktop `keydown` path stays covered by the existing unit tests; the `beforeinput` path can be exercised by dispatching a synthetic `InputEvent("beforeinput", { inputType, data })`. The soft keyboard _appearing on focus_ is platform behaviour driven purely by the `contentEditable` + `inputmode` attributes, so it can't be unit-tested — it needs **real-device QA** (physical phone or BrowserStack) before sign-off.
+
+### Other gaps
+
+RTL arrow-key flipping, non-Gregorian calendar systems, localized placeholder/nav-button strings, a dedicated navigation live-region, and a full screen-reader audit — all inventoried in the comparison doc's "Known gaps vs react-aria" section.
+
+---
+
+Everything below is the **original 2026-06-17 decision and analysis**, kept verbatim for the record (now superseded by the notes above).
+
+## 1. Summary & decision
+
+> **Decision (v1):** build on **`react-aria-components`** (Adobe) for the behaviour/a11y layer, with **`@internationalized/date`** as the value layer, wrapped in a thin app-shell presentation layer that matches every other component in the library. We accept the ~75 KB cost (§7) in exchange for audited APG accessibility at the lowest build risk. A lighter foundation is kept as a deliberate **future possibility (§9)**.
+
+This choice is low-regret: the public API (§3) and the value layer are foundation-agnostic, so a later swap to a lighter foundation touches only the internal wrapper — not consumers, not stored values. The alternatives measured before settling here are kept in §1.1 for the record.
+
+Verified facts behind this decision:
+
+- **Single bundled stack.** Modern `react-aria-components` v1 ships as 3 pre-bundled artifacts (`react-aria-components`, `react-aria`, `react-stately`), not the 30+ split `@react-aria/*` packages of the v0 era. Net-new footprint ≈ 10 packages, 3 substantive.
+- **Base UI has no date primitives** and none on the near roadmap, so "wait for Base UI" is not an option. Base UI remains our primitive provider for everything else; react-aria is scoped strictly to the date family.
+
+The trade-off we accept: a second headless stack exists in the bundle, **bounded to date components** and invisible to consumers (they never import from `react-aria-components`).
+
+## 1.1 Library choice — alternatives re-evaluated
+
+The 75 KB figure prompted a re-test of the hypothesis: _is there a slimmer option that doesn't add a primitive foundation competing with Base UI?_ The deepest form of that concern is not bundle size — it's that **`react-aria-components` is itself a general primitive suite** (Popover, Dialog, Select, Menu, ComboBox, Table…) that directly overlaps Base UI. Measured with identical methodology (esbuild, minified + gzipped, `react`/`react-dom` externalised, tree-shaken to the date slice):
+
+| Option | Date slice (gz) | Value layer | Adds a competing primitive foundation? | A11y | Build effort |
+| --------------------------------------- | --------------: | ------------------------------- | ----------------------------------------------------------- | ---------------------------------------- | -------------------------------- |
+| react-aria-components | 73.4 KB | `@internationalized/date` ✅ | **Yes** — full primitive suite overlaps Base UI | Best-in-class, APG-audited, SR-tested | Lowest |
+| Ark UI / Zag.js | 42.7 KB | `@internationalized/date` ✅ | **Yes** — Ark is also a full headless suite (lighter) | Good — Zag implements APG | Low |
+| Zag machine only + Base UI | 39.4 KB | `@internationalized/date` ✅ | Partial — own popper/dismiss/live-region (dual positioning) | Good | Medium (awkward wiring) |
+| **`@internationalized/date` + Base UI** | **10.8 KB** | _is_ the value layer ✅ | **No** — value/math lib only | **We own it** | **Highest** |
+| react-day-picker v10 + Base UI | 20.4 KB | ❌ `date-fns`, **`Date`-based** | No — calendar widget only | Calendar grid only; build input + dialog | High (+ loses value correctness) |
+
+What the data changes:
+
+- **The hypothesis is partly confirmed.** Slimmer, better-aligned options exist. react-aria is the _heaviest_ candidate and, with Ark available, no longer the obvious pick.
+- **Ark UI/Zag strictly dominates react-aria** _if_ a second behaviour layer is acceptable: ~40% lighter, the **same** value layer (`@internationalized/date` → identical calendar systems, `ZonedDateTime`, value-type correctness), comparable APG a11y. react-aria's only remaining edges are maturity/ecosystem (Adobe, larger adoption) and an exceptionally polished segmented input.
+- **`@internationalized/date` + Base UI is the only option that honours "no competing foundation"** while keeping value correctness — ~7× slimmer than react-aria. The cost is real: we build and _own_ the segmented input + calendar grid + APG dialog a11y (date pickers are the #1 place teams ship broken a11y). De-riskable by shipping the simpler APG _text-input + calendar-dialog_ pattern first (Base UI `Dialog` already handles focus trap/return/dismiss), with the segmented input as a fast-follow.
+- **Drop react-day-picker:** `Date`-based, abandons the value-type thesis, _and_ still calendar-only (you build the input + dialog anyway). Worst of both.
+- **Drop raw Zag-machine-on-Base-UI:** not meaningfully lighter than Ark (39 vs 43 KB) and forces two positioning systems.
+
+**Outcome:** for v1 we chose **react-aria-components** (audited a11y, least build risk, largest ecosystem) and accept the bundle cost. Ark UI/Zag and the Base-UI-native build are retained as a **future direction** — see §9.
+
+## 2. Why wrap — what the wrapper actually buys us
+
+The interface-slimming is the _least_ of it. Ranked by value:
+
+1. **Styling ownership (the main reason).** react-aria ships **zero CSS**. Without a wrapper, "use the DatePicker" means assembling ~12 headless sub-components and re-authoring all the `astw:` / theme-token / dark-mode / animation classes at every call site. The wrapper is where the visual identity lives — **once**. This is the same reason `Select` is wrapped today.
+2. **Abstraction seam / vendor insulation (the strategic one).** Wrapping makes react-aria an _implementation detail_ behind our own stable API. If react-aria reshapes its composition, or we later move the popover to Base UI, or migrate off react-aria entirely, it's a one-package change — not a consumer-wide migration. This is precisely what makes "we took on a second stack" reversible rather than a long-term lock-in.
+3. **Library consistency.** Consumers get ``, shaped like every other app-shell component, instead of a foreign 12-part Adobe composition. The second stack never enters the consumer's mental model.
+4. **Footgun masking (correctness).** Bake in safe defaults: site timezone instead of `getLocalTimeZone()`, full BCP-47 locale, no `Date` round-trip, `granularity` → value-type discrimination. Hide the knobs that let people do the wrong thing.
+5. **Interface slimming itself.** ~40 props + 12 sub-components → ~12 flat props. Real, but a _consequence_ of 1–4, not the goal.
+
+**Counter-discipline:** don't re-design what's already good. Keep `value` / `onChange` / `minValue` / `maxValue` / `granularity` / `isDateUnavailable` named exactly as react-aria has them. We slim and add safety; we do not invent a new vocabulary (that would create a translation tax against upstream docs and churn).
+
+## 3. Proposed public API
+
+Three exports in v1, plus two deferred:
+
+```tsx
+ // segmented typed input only, no popover → DateValue
+ // input + popover + calendar → DateValue
+ // standalone inline calendar (reporting filters, no popover)
+
+// deferred, designed-for:
+ // → { start: DateValue; end: DateValue }
+
+```
+
+Single-date and range are **separate components, not a `mode` prop** — they have different value types (`T` vs `{ start: T; end: T }`), and a `mode` prop forces every callsite to discriminate.
+
+### 3.1 Value type is set by `granularity`
+
+```tsx
+ // → CalendarDate (no time, no tz)
+ // → CalendarDateTime (local wall-time)
+
+ // → ZonedDateTime (tz-aware)
+```
+
+`onChange` returns the `@internationalized/date` type — **never a JS `Date`**. Returning `Date` discards the calendar system and corrupts timezone reasoning, which is the whole point of the stack. For callers that genuinely need a `Date`, the value carries `.toDate(timeZone)`; ISO round-tripping to the backend lives in a codec layer (see §7).
+
+### 3.2 Props we pass through (the 90% surface — names unchanged from react-aria)
+
+| Prop | Type | Notes |
+| ------------------------------------------ | ----------------------------------------- | ------------------------------------------------------- |
+| `value` / `defaultValue` / `onChange` | `DateValue` (typed by `granularity`) | Controlled or uncontrolled |
+| `granularity` | `"day" \| "hour" \| "minute" \| "second"` | Default `"day"`. Drives the value type |
+| `minValue` / `maxValue` | `DateValue` | Same type as `value` |
+| `isDateUnavailable` | `(date) => boolean` | Single predicate — holidays, weekends, lead-time |
+| `isDisabled` / `isReadOnly` / `isRequired` | `boolean` | |
+| `isInvalid` | `boolean` | |
+| `autoFocus` | `boolean` | |
+| `hourCycle` | `12 \| 24` | Overrides locale default |
+| `hideTimeZone` | `boolean` | Cosmetic, `ZonedDateTime` only |
+| `placeholderValue` | `DateValue` | Seeds the segment placeholder (e.g. default year) |
+| `firstDayOfWeek` | `"sun" \| "mon" \| …` | Forwarded to the inner `Calendar`; defaults from locale |
+| `name` | `string` | Native form / form-library integration |
+
+### 3.3 Props we add (app-shell conveniences)
+
+| Prop | Type | Why |
+| -------------- | ----------------- | -------------------------------------------------------------------------------------------------- |
+| `label` | `LocalizedString` | Renders `