Use when building or editing any web UI, CSS, or responsive layout — chat, overlays, modals, bottom sheets, drawers, forms — or fixing iOS/Safari/Android bugs around the on-screen keyboard, input auto-zoom, viewport height, safe areas, or position:fixed overlays that let the page show through. Apply BEFORE shipping mobile-facing UI, not after a device report.
How this skill is triggered — by the user, by Claude, or both
Slash command
/mobile-web-correctness:mobile-web-correctnessThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
Mobile browsers (especially iOS Safari) have deterministic, well-known quirks that desktop and headless testing never surface. Shipping a web UI without accounting for them reads as carelessness — they are not edge cases. **Apply this checklist up front whenever you build or touch a mobile-facing surface.** Each item is: symptom → rule → fix.
Mobile browsers (especially iOS Safari) have deterministic, well-known quirks that desktop and headless testing never surface. Shipping a web UI without accounting for them reads as carelessness — they are not edge cases. Apply this checklist up front whenever you build or touch a mobile-facing surface. Each item is: symptom → rule → fix.
position:fixed UI.<input>/<textarea>/<select>, form, or composer.100vh, sticky bottom bars).Skip only for purely desktop/internal tooling with no mobile surface.
Symptom: Tapping an input zooms the whole page in. Rule: every focusable form control needs font-size ≥ 16px. Watch: when a component is moved into a portal/different subtree, descendant-scoped overrides (e.g. .someWrapper .input) stop applying — the base rule must already be safe. Do NOT "fix" it with maximum-scale=1 on the viewport meta (breaks pinch-zoom / accessibility).
.myInput { font-size: 16px; } /* never < 16px on mobile */
visualViewport, not 100vhSymptom: keyboard covers the input, or a docked composer floats behind it. Rule: the keyboard shrinks the visual viewport but not the layout viewport (what position:fixed and 100vh use). For a full-screen overlay with a docked input, pin it to window.visualViewport with top + height (never bottom) and listen to resize/scroll.
const vv = window.visualViewport;
const apply = () => { el.style.height = `${vv.height}px`; el.style.top = `${vv.offsetTop}px`; };
apply(); vv.addEventListener("resize", apply); vv.addEventListener("scroll", apply);
document.bodySymptom: on mobile the page "bleeds through" behind a full-screen overlay (works on desktop). Cause: ANY ancestor with transform / filter / will-change / perspective / contain turns position:fixed into "fixed relative to that ancestor", not the viewport. Fix: render the overlay at the document root (e.g. React createPortal(node, document.body)) and add a full-viewport backdrop behind it so nothing can show through.
position:fixed on bodySymptom: the page scrolls behind the open overlay / into the keyboard gap. Rule: overflow:hidden on body is ignored by iOS Safari. Pin the body and restore scroll on close.
const y = window.scrollY;
body.style.position = "fixed"; body.style.top = `-${y}px`; body.style.width = "100%";
// on close: clear those, then window.scrollTo(0, y);
dvh, not vh100vh includes the area behind the iOS URL bar and jumps as it shows/hides. Use 100dvh (dynamic) for static full-height; use visualViewport (item 2) when the keyboard is involved.
Docked bars/composers need padding-bottom: max(12px, env(safe-area-inset-bottom)) (and safe-area-inset-top under the notch) so content clears the home indicator / notch.
Tap targets ≥ ~44px. Drag handles (resizable sheets) need touch-action: none so the gesture doesn't scroll the page. Native bottom sheets snap to points (e.g. peek / half / full) — don't ship free-drag-only.
Set inputmode (e.g. email, tel, numeric), enterkeyhint, and autocomplete so the right keyboard + Enter label appear.
Headless browsers (Playwright/Puppeteer) at a phone viewport catch layout, snap handles, and the portal target — but they do NOT emulate the soft keyboard, visualViewport resize, or iOS input-zoom. Those need a real device or the iOS Simulator. When you can't test the keyboard, say so explicitly instead of claiming it's verified.
| Mistake | Fix |
|---|---|
| Input at 14–15px "looks fine on desktop" | 16px minimum (item 1) |
height: 100vh on a full-screen layer | 100dvh + visualViewport when keyboard is up |
| Fixed overlay rendered deep in the tree | portal to body + backdrop (item 3) |
overflow:hidden to lock scroll on iOS | position:fixed body lock (item 4) |
| "Verified on mobile" (meaning headless) | headless ≠ keyboard; flag the gap |
When a NEW recurring mobile bug is found, append a numbered item to "The checklist" in the same symptom → rule → fix shape (and a row to Common mistakes). Keep entries concrete and tied to a real failure. PRs welcome.
Creates, edits, and optimizes skills for Claude Code, including drafting, evaluating with test prompts, iterating on performance, and improving skill descriptions for better triggering accuracy.
npx claudepluginhub daniel-lopez-puig/claude-skills --plugin mobile-web-correctness