Summary
New opt-in builder axis: components that are genuinely touch-optimized. Two mechanically distinct parts under (probably) one user-facing switch:
- Touch target sizing — control surfaces get a size/padding floor on touch screens (Apple 44pt / Material 48dp guidance).
- Adaptive overlay presentation — Select, Menu, DatePicker, ColorPicker, ContextMenu render their overlay as a bottom drawer on mobile instead of a popover. Combobox is the extreme case: the field renders as a button styled like an input that opens a drawer containing a search field + filtered list.
Why it's an axis
Systems disagree on it: React Spectrum adapts (popover → tray on mobile; its mobile ComboBox is exactly the button-opens-tray-with-search pattern), while Material, Geist, and most web systems don't. shadcn offers it only as a copy-paste doc recipe (the "responsive combobox"). Nobody ships it as a switch — a real differentiator for the builder.
Existing foundation
The registry is already most of the way there for the overlay part:
- Overlays are interchangeable wrappers —
www/src/registry/ui/menu/demos/overlay-type.tsx already does the responsive popover/drawer swap, using www/src/registry/lib/responsive/index.tsx and www/src/registry/hooks/use-mobile.ts.
- Select composes
Popover internally, so its overlay swap is contained.
- Overlays open only after user interaction, so the classic
useIsMobile hydration-flash problem doesn't apply to phase 2 (it does apply to Combobox — see phase 3).
- React Aria's
Autocomplete is the missing piece for mobile Combobox: DialogTrigger (button with field styling) → Drawer → SearchField + ListBox. Same architecture as Spectrum's mobile ComboBox, on primitives we already use.
Design decisions
Detection signal. Viewport width and pointer: coarse disagree in important cases (iPad landscape = wide but touch; narrow desktop window = small but mouse). Proposal: sizing keys off pointer type, presentation keys off viewport width. Decide deliberately rather than inheriting the 768px breakpoint.
Forceable signal for the builder preview. Axes must be runtime-switchable, but pointer: coarse and viewport width can't be faked from a toggle. So:
- sizing via CSS variables that flip under both
@media (pointer: coarse) and a [data-*] override attribute;
- the drawer swap via a context whose default comes from
matchMedia but which the preview (or a user's own device-preview tooling) can force.
Implies /create eventually wants a phone-frame preview mode, but that's a builder-rewrite concern — the axis mechanics must not depend on it.
Lean export when off. Opt-in means users who don't choose it get today's popover-only compositions. The publisher strips the mobile path at publish time. For Combobox this is genuinely two component trees, so the registry source must carry both paths in a shape where the strip is a shallow, mechanical cut (the publisher is queued for rewrite — don't deepen it).
Synced flip. All adaptive components flip together under one switch; per-component divergence would be an inconsistency footgun. Per-component opt-out can be earned later by preset evidence.
Phases
- Touch sizing — CSS-only (Tailwind v4
pointer-coarse: variant / CSS vars), lands the forceable-signal pattern. Applies to every control, including non-overlay ones (checkbox, slider, tabs).
- Adaptive overlays — Select, Menu, DatePicker, ColorPicker, ContextMenu; one shared mechanism.
- Adaptive Combobox — rebuild on
Autocomplete; trigger element differs between modes so the post-hydration swap needs its own verification pass (shadcn's recipe accepts the flash; probably right, but verify). TimePicker and Mention ride along after.
Open questions
- One builder switch or two? Recommendation: one "Mobile optimized" toggle to start — splitting later is easy, merging isn't.
- Drawer-only, or more mobile presentations? Spectrum uses tray for pickers but fullscreen for complex dialogs. Recommendation: start drawer-only; more options must be earned by preset evidence.
- Exact breakpoint / signal defaults (768px inherited from
use-mobile today).
Summary
New opt-in builder axis: components that are genuinely touch-optimized. Two mechanically distinct parts under (probably) one user-facing switch:
Why it's an axis
Systems disagree on it: React Spectrum adapts (popover → tray on mobile; its mobile ComboBox is exactly the button-opens-tray-with-search pattern), while Material, Geist, and most web systems don't. shadcn offers it only as a copy-paste doc recipe (the "responsive combobox"). Nobody ships it as a switch — a real differentiator for the builder.
Existing foundation
The registry is already most of the way there for the overlay part:
www/src/registry/ui/menu/demos/overlay-type.tsxalready does the responsive popover/drawer swap, usingwww/src/registry/lib/responsive/index.tsxandwww/src/registry/hooks/use-mobile.ts.Popoverinternally, so its overlay swap is contained.useIsMobilehydration-flash problem doesn't apply to phase 2 (it does apply to Combobox — see phase 3).Autocompleteis the missing piece for mobile Combobox: DialogTrigger (button with field styling) → Drawer → SearchField + ListBox. Same architecture as Spectrum's mobile ComboBox, on primitives we already use.Design decisions
Detection signal. Viewport width and
pointer: coarsedisagree in important cases (iPad landscape = wide but touch; narrow desktop window = small but mouse). Proposal: sizing keys off pointer type, presentation keys off viewport width. Decide deliberately rather than inheriting the 768px breakpoint.Forceable signal for the builder preview. Axes must be runtime-switchable, but
pointer: coarseand viewport width can't be faked from a toggle. So:@media (pointer: coarse)and a[data-*]override attribute;matchMediabut which the preview (or a user's own device-preview tooling) can force.Implies /create eventually wants a phone-frame preview mode, but that's a builder-rewrite concern — the axis mechanics must not depend on it.
Lean export when off. Opt-in means users who don't choose it get today's popover-only compositions. The publisher strips the mobile path at publish time. For Combobox this is genuinely two component trees, so the registry source must carry both paths in a shape where the strip is a shallow, mechanical cut (the publisher is queued for rewrite — don't deepen it).
Synced flip. All adaptive components flip together under one switch; per-component divergence would be an inconsistency footgun. Per-component opt-out can be earned later by preset evidence.
Phases
pointer-coarse:variant / CSS vars), lands the forceable-signal pattern. Applies to every control, including non-overlay ones (checkbox, slider, tabs).Autocomplete; trigger element differs between modes so the post-hydration swap needs its own verification pass (shadcn's recipe accepts the flash; probably right, but verify). TimePicker and Mention ride along after.Open questions
use-mobiletoday).