Skip to content

Mobile-optimized components axis: touch sizing + adaptive overlays #623

Description

@mehdibha

Summary

New opt-in builder axis: components that are genuinely touch-optimized. Two mechanically distinct parts under (probably) one user-facing switch:

  1. Touch target sizing — control surfaces get a size/padding floor on touch screens (Apple 44pt / Material 48dp guidance).
  2. 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

  1. 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).
  2. Adaptive overlays — Select, Menu, DatePicker, ColorPicker, ContextMenu; one shared mechanism.
  3. 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).

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions