feat(DateInput): show the browser/OS date picker on touch devices - #5261
Open
imdreamrunner wants to merge 1 commit into
Open
feat(DateInput): show the browser/OS date picker on touch devices#5261imdreamrunner wants to merge 1 commit into
imdreamrunner wants to merge 1 commit into
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
github-actions
Bot
requested review from
cvkxx,
ernestt,
kentonquatman and
rubyycheung
August 20, 2026 18:24
Contributor
PR Analysis Report📚 Storybook PreviewView Storybook for this PR 🧪 Sandbox PreviewView Sandbox for this PR Modified ComponentsDateInput (@astryxdesign/core) · View in Storybook
Bundle Size Summary
Accessibility AuditStatus: No accessibility violations detected. Generated by PR Enrichment workflow | Storybook | Sandbox | View full report |
imdreamrunner
force-pushed
the
feat/date-input-native-touch
branch
from
August 20, 2026 19:43
9334dfe to
bf47b05
Compare
imdreamrunner
force-pushed
the
feat/date-input-native-touch
branch
from
August 20, 2026 20:14
bf47b05 to
dbd16d0
Compare
imdreamrunner
force-pushed
the
feat/date-input-native-touch
branch
from
August 20, 2026 21:21
dbd16d0 to
1585cca
Compare
On a phone, tapping the calendar toggle opened a 320px month grid in a
popover — a desktop control shrunk onto a screen whose platform already
ships a date picker every user knows, with system hit areas, momentum
scrolling, and the OS locale and accessibility settings applied.
DateInput now renders `<input type="date">` when the pointer is coarse, so
iOS raises its wheel and Android its calendar dialog, and the toggle button
asks the engine for that picker (`showPicker()`, falling back to focus for
engines without it). The new `nativePicker` prop takes 'touch' (the default),
'always', or 'never' — every value naming when the native picker appears.
The decision is client-side (`useMediaQuery`, SSR-safe), so the server keeps
rendering the text field; a browser without `type="date"` support keeps it
too. In native mode the browser owns the field, so format, numberOfMonths,
and weekStartsOn stop applying; min and max carry over, and dateConstraints —
which a contiguous native range cannot express — keeps 'touch' on the
Calendar popover ('always' refuses a constrained date on commit and
announces it).
Three things the native control needs to sit like the text field it replaces.
Its intrinsic height comes from its inner edit fields rather than
line-height, so it rendered ~2px taller and its value sat off the text
variant's baseline in the same flex row; it is now pinned to one line box
(`1lh`, with a calc() fallback) with `appearance: none` to drop iOS's
button-like chrome.
A native date control has no placeholder — it renders its own mm/dd/yyyy
hint — so an empty field lost the one the text variant shows. The hint is
now hidden while the field is empty and unfocused (transparent text color,
which covers Chromium's ::-webkit-datetime-edit and Firefox's plain text
alike) and DateInput's placeholder is rendered over that space, matching the
text variant's position and token. Focus restores the hint, so the segment
being scrolled or typed is always visible; the overlay is aria-hidden and
pointer-transparent, so it neither double-speaks over the label nor
intercepts the tap that raises the picker.
And clearing no longer takes focus back in native mode: focusing a date
control is what raises the OS picker, so the old focus restore popped the
picker the clear tap had just dismissed, which read as the clear doing
nothing.
imdreamrunner
force-pushed
the
feat/date-input-native-touch
branch
from
August 21, 2026 01:24
1585cca to
9aafea7
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
On a phone, DateInput's calendar toggle opens a 320px month grid in a popover — a desktop control shrunk onto a screen whose platform already ships a date picker every user knows. The native one comes with system-sized hit areas, momentum scrolling, and the OS locale, calendar, and accessibility settings applied for free; ours arrives with mouse-sized day cells and a text field that invites a keyboard.
Change
DateInput renders
<input type="date">when the pointer is coarse, so iOS raises its wheel and Android its calendar dialog. The calendar toggle button asks the engine for that picker (showPicker(), falling back to focus on engines without it), and the Calendar popover is not mounted at all in that mode.New prop,
nativePicker— every value names when the native picker appears:'touch'(default)'always'<input type="date">'never'Behavior notes
useMediaQuery(useSyncExternalStore), so the server always renders the text field and the swap happens on the client's first committed render. A browser withouttype="date"support (feature-detected via value sanitization) keeps the text field too.format,numberOfMonths, andweekStartsOnno longer apply — the control shows the OS locale's date format.placeholderstill shows. A native date control has none of its own, just an OSmm/dd/yyyyhint, so an empty field lost the placeholder the text variant shows. That hint is now hidden while the field is empty and unfocused — a transparent text color, which covers Chromium's::-webkit-datetime-editand Firefox's plain text alike — and the placeholder is rendered over that space at the text variant's exact position and token. Focus restores the hint so the segment being scrolled or typed is always visible; the overlay isaria-hidden(the field already has a label) and pointer-transparent, so a tap on it still reaches the control and raises the picker.line-height, so it rendered ~2px taller and its value sat off the text variant's baseline in the same flex row; it is pinned to one line box (1lh, with acalc()fallback for older WebKit) withappearance: noneto drop iOS's button-like chrome, Chromium's duplicate calendar glyph hidden, and WebKit's centered value and inner spacing reset.min/maxcarry over to the native control.dateConstraintscannot — a native picker only expresses a contiguous range — so'touch'keeps the Calendar popover whenever constraints are set, rather than letting a user pick a date the field then refuses.'always'still goes native, refusing a constrained date on commit and announcing it through the existing live region.role="combobox",aria-expanded,aria-haspopup, andaria-controls— there is no in-page popup to describe — and keeps the label, description, status, required, and busy wiring. The toggle keeps its "Open calendar" name and never flips to "Close".changeAction,disabledMessage, InputGroup rendering, and the whole desktop path.Test plan
DateInputNative.test.tsx— 31 tests over the coarse-pointer switch, eachnativePickervalue, thedateConstraintsfallback and rejection, ISO value round-trip,min/maxforwarding,showPicker(including an engine that throws), the placeholder (default, custom, hidden when filled, restored on clear, yielded on focus,aria-hidden), clear + its focus behavior in both modes, disabled, ref forwarding, labelling, and the feature detection.DateInput.test.tsx's 94 existing tests pass untouched — the setup polyfill reports a fine pointer, so the desktop path is the default under test.pnpm lint:strict,pnpm test,pnpm buildall green (56 pre-existing warnings, 0 errors).devices['iPhone 15']anddevices['Pixel 7']: the native field's box is identical to thenativePicker="never"field's (22.84px tall, same top and left inset, centered in the same row); the placeholder overlay lands at delta 0 horizontally and vertically against where the value renders, in the samergb(78, 96, 111)the text variant's::placeholderuses, and a tap at its coordinates focuses the control underneath (Playwright reports the input "intercepts pointer events", which is the overlay being correctly transparent); the toggle callsshowPicker()and focuses the control; picking a date drives the controlled value and drops the placeholder; clearing empties the field, restores the placeholder, and does not re-raise the picker. Desktop context keeps the text combobox.