--- name: mobile-ux-optimizer description: Mobile-first UX optimization for touch interfaces, responsive layouts, and performance. Use for viewport handling, touch targets, gestures, mobile navigation. Activate on mobile, touch, responsive, dvh, viewport, safe area, hamburger menu. NOT for native app development (use React Native skills), desktop-only features, or general CSS (use Tailwind docs). allowed-tools: Read,Write,Edit,Bash,Grep,Glob category: Design & Creative tags: - mobile - ux - touch - responsive - viewport - safe-area - navigation --- # Mobile-First UX Optimization Build touch-optimized, performant mobile experiences with proper viewport handling and responsive patterns. ## When to Use ✅ **USE this skill for:** - Viewport issues (`100vh` problems, safe areas, notches) - Touch target sizing and spacing - Mobile navigation patterns (bottom nav, drawers, hamburger menus) - Swipe gestures and pull-to-refresh - Responsive breakpoint strategies - Mobile performance optimization ❌ **DO NOT use for:** - Native app development → use `react-native` or `swift-executor` skills - Desktop-only features → no skill needed, standard patterns apply - General CSS/Tailwind questions → use Tailwind docs or `web-design-expert` - PWA installation/service workers → use `pwa-expert` skill ## Core Principles ### Mobile-First Means Build Up, Not Down ```css /* ❌ ANTI-PATTERN: Desktop-first (scale down) */ .card { width: 400px; } @media (max-width: 768px) { .card { width: 100%; } } /* ✅ CORRECT: Mobile-first (scale up) */ .card { width: 100%; } @media (min-width: 768px) { .card { width: 400px; } } ``` ### The 44px Rule Apple's Human Interface Guidelines specify **44×44 points** as minimum touch target. Google Material suggests **48×48dp**. ```tsx // Touch-friendly button // Touch-friendly link with adequate padding Link text ``` ## Viewport Handling ### The `dvh` Solution Mobile browsers have dynamic toolbars. `100vh` includes the URL bar, causing content to be cut off. ```css /* ❌ ANTI-PATTERN: Content hidden behind browser UI */ .full-screen { height: 100vh; } /* ✅ CORRECT: Responds to browser chrome */ .full-screen { height: 100dvh; } /* Fallback for older browsers */ .full-screen { height: 100vh; height: 100dvh; } ``` ### Safe Area Insets (Notches & Home Indicators) ```css /* Handle iPhone notch and home indicator */ .bottom-nav { padding-bottom: env(safe-area-inset-bottom, 0); } .header { padding-top: env(safe-area-inset-top, 0); } /* Full safe area padding */ .safe-container { padding: env(safe-area-inset-top) env(safe-area-inset-right) env(safe-area-inset-bottom) env(safe-area-inset-left); } ``` **Required meta tag:** ```html ``` ### Tailwind Safe Area Classes ```tsx // Custom Tailwind utilities (add to globals.css) @layer utilities { .pb-safe { padding-bottom: env(safe-area-inset-bottom); } .pt-safe { padding-top: env(safe-area-inset-top); } .h-screen-safe { height: calc(100dvh - env(safe-area-inset-top) - env(safe-area-inset-bottom)); } } // Usage ``` ## Mobile Navigation Patterns ### Bottom Navigation (Recommended for Mobile) ```tsx // components/BottomNav.tsx 'use client'; import { usePathname } from 'next/navigation'; import Link from 'next/link'; const navItems = [ { href: '/', icon: HomeIcon, label: 'Home' }, { href: '/meetings', icon: CalendarIcon, label: 'Meetings' }, { href: '/tools', icon: ToolsIcon, label: 'Tools' }, { href: '/my', icon: UserIcon, label: 'My Recovery' }, ]; export function BottomNav() { const pathname = usePathname(); return ( ); } ``` ### Slide-Out Drawer (Side Menu) ```tsx 'use client'; import { useState, useEffect } from 'react'; import { createPortal } from 'react-dom'; interface DrawerProps { isOpen: boolean; onClose: () => void; children: React.ReactNode; } export function Drawer({ isOpen, onClose, children }: DrawerProps) { // Prevent body scroll when open useEffect(() => { if (isOpen) { document.body.style.overflow = 'hidden'; } return () => { document.body.style.overflow = ''; }; }, [isOpen]); // Close on escape useEffect(() => { const handleEscape = (e: KeyboardEvent) => { if (e.key === 'Escape') onClose(); }; document.addEventListener('keydown', handleEscape); return () => document.removeEventListener('keydown', handleEscape); }, [onClose]); if (!isOpen) return null; return createPortal(
{/* Backdrop */} , document.body ); } ``` ## Touch Gestures > **Full implementations in `references/gestures.md`** | Hook | Purpose | |------|---------| | `useSwipe()` | Directional swipe detection with configurable threshold | | `usePullToRefresh()` | Pull-to-refresh with visual feedback and resistance | **Quick usage:** ```tsx // Swipe to dismiss const { handleTouchStart, handleTouchEnd } = useSwipe({ onSwipeLeft: () => dismiss(), threshold: 50, }); // Pull to refresh const { containerRef, pullDistance, isRefreshing, handlers } = usePullToRefresh(async () => await refetchData()); ``` ## Mobile Performance ### Image Optimization ```tsx import Image from 'next/image'; // Responsive images with proper sizing Hero // Lazy load below-fold images Feature ``` ### Reduce Bundle Size ```tsx // Dynamic imports for heavy components const HeavyChart = dynamic(() => import('@/components/Chart'), { loading: () => , ssr: false, // Skip server render for client-only }); // Lazy load below-fold sections const Comments = dynamic(() => import('@/components/Comments')); ``` ### Skeleton Screens (Not Spinners) ```tsx // Skeleton that matches final content layout function MeetingCardSkeleton() { return (
); } // Usage {isLoading ? (
{[...Array(5)].map((_, i) => )}
) : ( meetings.map(m => ) )} ``` ## Responsive Patterns ### Tailwind Breakpoint Strategy ``` sm: 640px - Large phones (landscape) md: 768px - Tablets lg: 1024px - Small laptops xl: 1280px - Desktops 2xl: 1536px - Large screens ``` ```tsx // Mobile: stack, Tablet+: side-by-side
Content
// Mobile: bottom nav, Desktop: sidebar ``` ### Container Queries (CSS-only Responsive Components) ```css /* Component responds to its container, not viewport */ @container (min-width: 400px) { .card { flex-direction: row; } } ``` ```tsx
{/* Responds to parent container width */}
``` ## Testing on Real Devices ### Chrome DevTools Mobile Emulation 1. Open DevTools (F12) 2. Toggle device toolbar (Ctrl+Shift+M) 3. Select device or set custom dimensions 4. **Throttle network/CPU** for realistic performance ### Must-Test Scenarios - [ ] Content doesn't get cut off by notch/home indicator - [ ] Touch targets are at least 44×44px - [ ] Scrolling is smooth (no jank) - [ ] Bottom nav doesn't block content - [ ] Forms work with virtual keyboard visible - [ ] Landscape orientation works - [ ] Pull-to-refresh doesn't fight with scroll ### BrowserStack/Real Device Testing ```bash # Expose local dev server to internet npx localtunnel --port 3000 # or ngrok http 3000 ``` ## Quick Reference | Issue | Solution | |-------|----------| | Content cut off at bottom | Use `100dvh` instead of `100vh` | | Notch overlaps content | Add `pt-safe` / `pb-safe` | | Touch targets too small | Min 44×44px | | Scroll locked | Check `overflow: hidden` on body | | Keyboard covers input | Use `visualViewport` API | | Janky scrolling | Use `will-change: transform` | | Double-tap zoom | Add `touch-action: manipulation` | ## References See `/references/` for detailed guides: - `keyboard-handling.md` - Virtual keyboard and form UX - `animations.md` - Touch-friendly animations - `accessibility.md` - Mobile a11y requirements