# Mobile Parity

> Ensures Kandev UI work uses native mobile interaction patterns instead of compressed desktop adaptations while preserving desktop/mobile capability parity, responsive behavior, and mobile Playwright E2E coverage. Use when implementing, planning, reviewing, or testing any new feature, page, component, workflow, form, dialog, sidebar, navigation, dashboard, or visual UI change; if work touches frontend or user-facing UI, this skill must run even when user mentions only desktop or says "new feature".

- Skill: `kdlbs/mobile-parity` (Agent Skill, multi-file: 3 files)
- Install (CLI): `npx skillmds@latest add kdlbs/mobile-parity`
- Raw SKILL.md: https://api.skillmd.com/api/skills/kdlbs/mobile-parity/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Web & Frontend
- Author: kdlbs (https://skillmd.com/u/kdlbs)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/kdlbs/mobile-parity

---


# Mobile Parity

Use this skill before planning or changing UI. Goal: desktop and mobile deliver the same user value, while each viewport gets an intentional composition and tests prove the mobile path.

Before proposing a mobile design, read [Kandev Mobile UI Language](references/kandev-mobile-ui-language.md) and inspect the closest shipped mobile surface. Responsive CSS alone does not establish mobile parity.

For buttons, single-line inputs, selectors, or control-size audits, also read
[Control sizing](references/control-sizing.md). It defines desktop sizes,
touch exceptions, and the sweep procedure.

## When It Applies

Apply when task changes user-facing UI:

- new or changed pages, routes, components, forms, dialogs, drawers, navigation, dashboards, tables, cards, toolbars, editors, settings, onboarding, or visual states
- new frontend behavior attached to backend/API work
- bug fixes where layout, touch behavior, scrolling, or viewport width can affect success

If task has no UI surface, say why this skill does not apply and continue.

## Specification ownership

When the task uses durable specifications, put mobile outcomes in the system
that owns the feature contract. Do not create a second UI requirement or design
only because the feature needs a phone surface. Use the UI system only for an
independent reusable interaction contract.

Keep the mobile composition in the owning feature design with its backend and
desktop boundaries. Link to an existing UI contract when the feature reuses one.

## Mobile Design Contract

When a task changes composition, navigation, overlays, touch behavior, scrolling, or breakpoint behavior, state these choices in the working plan or task notes. Keep them brief; do not create a separate document unless the task already uses a spec or committed plan. For copy, icon, color, or content-only styling inside an unchanged surface, identify the nearest mobile exemplar and the rendered mobile check instead of forcing the full contract.

- desktop user outcome and mobile entry point
- nearest shipped mobile exemplar and which interaction/geometry it contributes
- mobile information hierarchy and primary action
- presentation choice: inline, inset bottom drawer, full-height surface, or direct navigation
- surface rationale: why task frequency and content depth make that choice preferable to the alternatives
- single scroll owner, dynamic viewport behavior, safe-area handling, and touch targets
- shared state, view-model, filtering/selection, and business logic versus mobile-specific presentation
- mobile Playwright scenario proving the same user value

For a feature or fix design package, record the desktop and phone composition
as ASCII previews in the plan and relevant UI work orders. Follow
`docs/specs/guide/plans-and-work-orders.md#ascii-ui-previews`; include a compact
preview in the final conversation handoff. During implementation, compare the
rendered phone surface with the assigned preview's structure and annotations.

## Workflow

1. Map affected surfaces.
   - Identify every page, modal, menu, tab, empty state, loading state, and error state the feature touches.
   - Check where desktop layout assumptions can fail: fixed widths, hover-only controls, sidebars, tables, dense toolbars, keyboard shortcuts, overflow, and absolute positioning.
   - Use `rg` to find the nearest existing mobile component and the route's current `useResponsiveBreakpoint` branch. Name the closest curated exemplar from the reference and state which parts are reused.
   - Treat live code as the source of truth for APIs, state, and current behavior. Treat this skill's curated guide and exemplar list as the desired mobile interaction baseline; nearby legacy surfaces that deviate from it need justification, not automatic reuse.

2. Design desktop and mobile behavior together.
   - Preserve capability, data, and state semantics; do not require identical markup or navigation.
   - Apply the surface decision guide in the mobile UI language reference. Prefer a focused, one-dimensional phone flow over stacked desktop panes or two-axis scrolling.
   - Explicitly distinguish temporary choices that belong in a drawer from primary or dense content that deserves direct navigation or a full-height surface.
   - When a card or row has an obvious primary destination and no competing selection, drag, or inline-control behavior, make its body tap perform that action. Otherwise expose an explicit touch control. Put secondary actions behind a visible target, never hover, right-click, or undiscoverable long press.
   - Define mobile navigation, hierarchy, scroll owner, touch targets, truncation, empty/error states, and responsive fallback behavior before coding.

3. Implement responsive UI.
   - Reuse domain hooks, state, view-model derivation, filtering/selection, and action handlers across viewports; keep responsive wrappers focused on presentation. Branch composition when the desktop interaction model depends on width or a fine pointer. Do not mount a heavyweight desktop workbench and merely hide or squeeze it on phones.
   - Use `useResponsiveBreakpoint` and existing `@kandev/ui` or mobile primitives. Reuse current Dropdown/ContextMenu primitives for contextual actions; use `Drawer` or an existing picker shell for structured phone navigation and choices. Use `useTouchDrawer` when a hover disclosure needs a coarse-pointer alternative.
   - Use 28px for ordinary desktop buttons, single-line inputs, select triggers, and combobox triggers. Use 24px only for deliberate compact inline controls. Match the desktop Start Task dialog and shared default primitives.
   - Keep phone and coarse-pointer action targets at least 44px. Scope touch dimensions to those conditions. A desktop-visible action must not inherit an unconditional `h-11` or `min-h-11`. Check both height and minimum height: changing `h-*` does not remove an oversized `min-h-*`.
   - A Radix Tooltip that happens to open after Playwright `.tap()` is not a coarse-pointer alternative. Use `useTouchDrawer` with a Drawer branch and assert the drawer surface in mobile E2E.
   - Keep coarse-pointer and mobile touch targets large enough for touch use, generally at least 44px in the active dimension. This is an active hit-area rule, not a universal desktop visual-size rule: fine-pointer desktop controls should retain the surrounding design-system density, and touch-sized classes such as `h-11` must not become the shared desktop button size.
   - When one component serves both pointer modes, keep its fine-pointer size as the base class and add the 44px size only behind a coarse-pointer or phone variant. An unqualified `h-11` or `min-h-11` on a desktop-visible shared action is a sizing bug.
   - For a primary mobile dialog action whose contract requires an actual hit target of at least 44px, prefer 48px nominal sizing when browser scaling can produce fractional bounds. Assert the rendered `boundingBox().height` (or width for a horizontal target) is at least 44px; a utility class alone does not prove the physical target.
   - Use dynamic viewport units and an explicit internal scroll region for viewport-bound/full-height or potentially overflowing surfaces. Ensure bottom-fixed controls and tall drawers clear safe-area insets; short drawers can retain the shared primitive's intrinsic sizing. Keep document-level horizontal overflow at zero.
   - If mobile substitutes an unsupported desktop view, derive an effective mobile view without overwriting the user's saved desktop preference.
   - Use semantic controls, visible labels or accessible names, focus return, and existing design-system components.
   - Avoid hiding required functionality on mobile unless there is a clear alternate path.

4. Add E2E coverage.
   - Add or update Playwright tests for the feature's happy path on desktop if missing.
   - Add mobile Playwright coverage for the same user value, using existing mobile projects/devices when configured.
   - In this repo, name mobile test files `mobile-*.spec.ts` so the `mobile-chrome` Playwright project picks them up automatically.
   - Cover the actual mobile composition: drawer or full-height surface, visible overflow action, focused navigation, direct route, or bottom control.
   - For overlay and dense-navigation changes, assert viewport containment, internal scrolling, and the absence of document horizontal overflow where those properties are part of the regression.
   - For responsive visual or utility changes, define the base phone behavior and assert computed styles at the canonical phone viewport and just below and above the relevant breakpoint; geometry or overflow checks alone can miss a desktop style leaking into a full-screen mobile surface.
   - When responsive CSS changes an overlay or absolute child to normal flow (for example, `position: static`), re-check the parent's allocated width and height; a fixed-size desktop wrapper can under-report combined in-flow mobile content.
   - For containment regressions, compare the interactive control's bounding box with its row, drawer, or viewport bounds. An intrinsic 44px hitbox plus a document-overflow check does not prove that an ancestor is not clipping the control.
   - When a touch-only control is replaced or hidden, run `rg` across mobile E2E tests for the removed control. Replace every affected interaction with the intended gesture or alternate control, then run those tests together.

5. Verify visually and behaviorally.
   - Run the narrowest relevant viewport locally or with screenshots when possible.
   - Even small user-facing UI tweaks need at least focused rendered verification when feasible: dev-server/browser check, Playwright screenshot, or targeted E2E. If not run, report the exact reason.
   - Check phone ergonomics, not only responsive fit: entry point is discoverable, primary action is thumb-reachable, hierarchy is understandable, sheet shape matches nearby surfaces, and back/dismiss behavior is predictable.
   - Check that text does not overlap, controls remain clickable, focus/keyboard flows still work, safe-area content is unobstructed, and no unintended horizontal scroll appears.
   - Run the focused Playwright tests. If full E2E cannot run, report the command and blocker.
   - E2E runs against the production Vite build served by the Go backend, not a dev server, so rebuild after frontend changes: `make build-web` (and `make build-backend` for Go), or use `make test-e2e` which rebuilds both. Skipping this silently tests stale code. See `/e2e`.

## Mobile E2E Expectations

Every UI feature should end with one of these:

- mobile Playwright test added or updated
- existing mobile Playwright test explicitly identified as covering the changed behavior
- written justification for no mobile test, limited to impossible-to-test infrastructure gaps

For frontend changes that are purely state/data normalization inside an existing component and do not alter rendered layout, touch behavior, scrolling, navigation, or viewport-dependent interaction, targeted unit/component tests plus an explicit note can satisfy mobile parity. New mobile Playwright coverage is not required for that narrow case.

Good mobile tests assert real user outcomes, not only visibility. Prefer:

- open feature from mobile navigation and complete primary action
- use drawer/menu/sheet variant of desktop controls
- submit form and verify result
- handle empty/error/loading state on narrow viewport
- confirm no required action is desktop-only

## Playwright Routing

Create `mobile-*.spec.ts` files and let the `mobile-chrome` project apply its configured Pixel 5 device; do not add per-test device overrides. Follow `/e2e` for fixtures, page objects, selectors, build requirements, and local reproduction. Top-level `tests/mobile-*.spec.ts` files import `../fixtures/test-base`; nested specs adjust the depth, commonly using `../../fixtures/test-base`. Mobile parity owns which interaction and geometry contracts need proof; `/e2e` owns test mechanics.

## Done Checklist

- Desktop path still works.
- Structural/touch changes include a mobile design contract naming entry point, nearest shipped exemplar, hierarchy, surface, scroll owner, and primary action.
- For those changes, surface rationale explains why the chosen composition fits task frequency and content depth.
- Content-only styling changes identify the nearest mobile exemplar and focused rendered mobile check instead.
- Mobile path follows a shipped Kandev pattern or explains why a new pattern is needed.
- Mobile composition is intentional, not desktop UI stacked, squeezed, or hidden with CSS.
- Required controls are reachable by touch.
- No required workflow depends on hover, wide viewport, or hidden desktop-only UI.
- Viewport-bound/full-height or potentially overflowing surfaces use dynamic viewport sizing, safe-area clearance, and internal scrolling.
- Responsive fallbacks do not overwrite saved desktop preferences.
- Mobile E2E tests no longer invoke touch controls that the change replaced or hid.
- Mobile Playwright coverage exists or absence is justified.
- Focused rendered/visual verification was run for UI tweaks, or exact "not run" reason is reported.
- Focused tests were run, or exact blocker is reported.

