Skip to main content

mobile-web-accessibility-touch-targets

Implements mobile web accessibility with proper touch target sizing (44/48pt minimum), gesture alternatives, viewport configuration for zoom, orientation support, and screen reader testing methodology for VoiceOver/TalkBack.

Informações da origem

Repositório
paulpas/agent-skill-router
Última atividade na origem
23 de setembro de 2026 às 17:17
Idioma detectado do SKILL.md
inglês
Estrelas
6
Forks
0

Opções de instalação

Por padrão, está selecionado o prompt que primeiro revisa a origem. Você pode mudar para um comando direto ou baixar uma cópia local.

Revise os arquivos de origem

Leia o SKILL.md e os arquivos complementares exibidos pelo SkillsMP antes de decidir se vai instalar.

Exibindo SKILL.md

SKILL.md
Instruções da origem · Visualização somente leitura
name
mobile-web-accessibility-touch-targets
description
Implements mobile web accessibility with proper touch target sizing (44/48pt minimum), gesture alternatives, viewport configuration for zoom, orientation support, and screen reader testing methodology for VoiceOver/TalkBack.
license
MIT
compatibility
opencode
metadata
{"version":"1.0.0","domain":"coding","role":"implementation","scope":"implementation","output-format":"code","content-types":["code","patterns","guidance"],"triggers":"mobile accessibility, touch targets, VoiceOver, TalkBack, gesture alternatives, viewport meta, touch events, screen reader testing","related-skills":"wcag-21-aa-fundamentals, keyboard-navigation-focus-management","archetypes":["tactical","enforcement"],"anti_triggers":["desktop-only","brainstorming","backend"],"response_profile":{"verbosity":"low","directive_strength":"high","abstraction_level":"operational"}}
# Mobile Web Accessibility: Touch Targets & Screen Reader Testing Implements touch-accessible web experiences with proper target sizing (WCAG 2.5.8: 24px minimum, 44/48pt recommended), gesture alternatives for swipe/pinch, viewport meta configuration, orientation support, and mobile screen reader testing (VoiceOver on iOS, TalkBack on Android). Load when building mobile-first interfaces, ensuring accessibility for touch devices, or testing with mobile screen readers. ## TL;DR Checklist - [ ] Touch targets: minimum 44×44px (CSS pixels) or 24×24px with 8px spacing - [ ] 8px gap minimum between adjacent touch targets - [ ] Provide gesture alternatives: single-tap for swipe/pinch/long-press - [ ] Viewport meta: `viewport-fit=cover, user-scalable=yes` (never disable zoom) - [ ] Test reflow at 320px width (mobile landscape on small phone) - [ ] Support device orientation (portrait/landscape/reverse) - [ ] Test with VoiceOver (iOS) and TalkBack (Android) - [ ] Text resizable to 200% without loss of functionality - [ ] Touch events and pointer events both supported --- ## When to Use Use this skill when: - Building responsive mobile interfaces - Ensuring touch targets meet WCAG 2.5.8 requirements - Implementing gesture-based interactions (swipe, pinch, drag) - Testing with mobile screen readers (VoiceOver/TalkBack) - Optimizing for small screens (320px width) - Supporting multiple device orientations --- ## When NOT to Use Avoid this skill for: - Desktop-only experiences (use general accessibility skills) - Touch event technical reference (use MDN/browser docs) - Mobile UI design (use design/UX skills) - iOS/Android native app development (different platforms) --- ## Core Workflow 1. **Size Touch Targets** — Ensure all interactive elements 44×44px minimum (CSS pixels) 2. **Add Spacing** — Maintain 8px gap between adjacent targets 3. **Configure Viewport** — Set viewport meta for zoom support and reflow 4. **Support Gestures** — Provide single-tap alternatives to swipe/pinch 5. **Test Orientation** — Verify portrait/landscape work correctly 6. **Test with Screen Readers** — VoiceOver (iOS), TalkBack (Android) 7. **Test at 320px** — Verify mobile landscape small phone works 8. **Test Text Resizing** — 200% text scale without overflow/loss --- ## Touch Target Sizing ### Minimum Size Requirement WCAG 2.1 SC 2.5.5 (Enhanced, AAA): **44×44 CSS pixels** minimum. WCAG 2.2 SC 2.5.8 (AA): **24×24 CSS pixels** minimum, but with **8px gap** between adjacent targets. **Recommendation for AA compliance (most common):** - **44×44px minimum** (no gap math needed) - **48×48px recommended** (larger on mobile) - **8px gap** between adjacent targets ### Implementation ```css /* ✅ GOOD: Touch target sizing --> button, [role="button"], input[type="checkbox"], input[type="radio"] { min-height: 44px; min-width: 44px; display: flex; align-items: center; justify-content: center; } /* Form inputs --> input[type="text"], input[type="email"], input[type="password"], textarea, select { min-height: 44px; padding: 8px 12px; } /* Icon buttons with implicit padding --> .icon-button { min-height: 48px; min-width: 48px; padding: 8px; display: inline-flex; align-items: center; justify-content: center; } /* Toolbar with spacing between items --> .toolbar { display: flex; gap: 8px; /* Minimum gap between adjacent targets */ } /* ❌ BAD: Too small for touch --> button { height: 32px; width: 32px; } /* ❌ BAD: No gap between targets --> .nav-item { display: inline-block; width: 40px; /* Adjacent items touch — hard to target */ } ``` ### Touch Target Calculator For elements smaller than 44px, use 8px gap: ``` Target 1: 32px Target 2: 32px Gap: 8px Total space: 32 + 8 + 32 = 72px (or stack vertically) ``` For multiple adjacent targets: ```html <!-- ✅ GOOD: Spaced buttons for touch --> <div style="display: flex; gap: 8px;"> <button>Action 1</button> <button>Action 2</button> <button>Action 3</button> </div> <!-- ✅ GOOD: Icon buttons with implicit padding --> <div style="display: flex; gap: 4px;"> <button style="padding: 12px;"> <svg><!-- icon --></svg> </button> <button style="padding: 12px;"> <svg><!-- icon --></svg> </button> </div> <!-- ❌ BAD: Targets too close --> <div style="display: flex;"> <button style="padding: 2px;">A</button> <button style="padding: 2px;">B</button> </div> ``` --- ## Viewport Configuration ### Viewport Meta Tag ```html <!-- ✅ GOOD: Allow user zoom, support full-width layouts --> <meta name="viewport" content=" width=device-width, initial-scale=1, viewport-fit=cover, user-scalable=yes, maximum-scale=5 "> <!-- ❌ BAD: Disable zoom (violates WCAG) --> <meta name="viewport" content=" width=device-width, initial-scale=1, user-scalable=no, maximum-scale=1 "> ``` ### Viewport Properties Explained | Property | Value | Purpose | |----------|-------|---------| | `width` | `device-width` | Match device width (responsive) | | `initial-scale` | `1` | Don't pre-zoom (let user decide) | | `viewport-fit` | `cover` | Use notch/safe area (iPhone X+) | | `user-scalable` | `yes` | Allow pinch-to-zoom (required) | | `maximum-scale` | `5` | Allow zoom to 500% minimum | --- ## Responsive Design at Small Viewports ### Testing at 320px Width ```css /* Mobile-first approach --> @media (min-width: 320px) { /* Base styles: single column, full-width buttons */ body { font-size: 16px; } button { width: 100%; } .card { width: 100%; } } @media (min-width: 640px) { /* Tablet: two-column layout */ .grid { display: grid; grid-template-columns: 1fr 1fr; } } @media (min-width: 1024px) { /* Desktop: wider layout */ .grid { grid-template-columns: repeat(3, 1fr); } } /* Ensure no horizontal scrolling at any width --> * { max-width: 100%; overflow-x: hidden; /* Last resort, fix layout instead */ } ``` ### Orientation Support ```css /* Detect portrait vs landscape */ @media (orientation: portrait) { /* Portrait-specific styles */ .sidebar { display: block; } } @media (orientation: landscape) { /* Landscape-specific styles */ .sidebar { display: none; } /* Or sidebar layout */ } /* Safe area support (notches on modern phones) */ @supports (padding: max(0px)) { body { padding-left: max(16px, env(safe-area-inset-left)); padding-right: max(16px, env(safe-area-inset-right)); padding-top: max(16px, env(safe-area-inset-top)); } } ``` --- ## Gesture Implementation Patterns ### Pattern 1: Swipe-to-Dismiss with Tap Alternative ```typescript import { useState, useRef, useEffect } from 'react'; interface SwipeDismissProps { onDismiss: () => void; children: React.ReactNode; } export function SwipeDismiss({ onDismiss, children }: SwipeDismissProps) { const ref = useRef<HTMLDivElement>(null); const [startX, setStartX] = useState(0); const handleTouchStart = (e: React.TouchEvent) => { setStartX(e.touches[0].clientX); }; const handleTouchEnd = (e: React.TouchEvent) => { const endX = e.changedTouches[0].clientX; const diff = startX - endX; // Swipe left > 100px to dismiss if (diff > 100) { onDismiss(); } }; return ( <div ref={ref} onTouchStart={handleTouchStart} onTouchEnd={handleTouchEnd} className="swipe-container" > <div className="swipe-content">{children}</div> {/* Tap to dismiss alternative (required for accessibility) */} <button className="dismiss-button" aria-label="Dismiss notification" onClick={onDismiss} > ✕ </button> </div> ); } // Usage export function NotificationDemo() { const [show, setShow] = useState(true); return ( show && ( <SwipeDismiss onDismiss={() => setShow(false)}> <p>Swipe left or tap X to dismiss</p> </SwipeDismiss> ) ); } ``` ### Pattern 2: Pinch-to-Zoom Alternative ```typescript import { useState, useRef } from 'react'; interface PinchZoomProps { children: React.ReactNode; } export function PinchZoom({ children }: PinchZoomProps) { const [scale, setScale] = useState(1); const startDistanceRef = useRef(0); const getDistance = (touches: TouchList) => { const dx = touches[0].clientX - touches[1].clientX; const dy = touches[0].clientY - touches[1].clientY; return Math.hypot(dx, dy); }; const handleTouchStart = (e: React.TouchEvent) => { if (e.touches.length === 2) { startDistanceRef.current = getDistance(e.touches); } }; const handleTouchMove = (e: React.TouchEvent) => { if (e.touches.length === 2 && startDistanceRef.current > 0) { const currentDistance = getDistance(e.touches); const scaleChange = currentDistance / startDistanceRef.current; // Limit zoom between 1x and 3x const newScale = Math.min(3, Math.max(1, scale * scaleChange)); setScale(newScale); startDistanceRef.current = currentDistance; } }; const handleTouchEnd = () => { startDistanceRef.current = 0; }; return ( <div onTouchStart={handleTouchStart} onTouchMove={handleTouchMove} onTouchEnd={handleTouchEnd} className="pinch-container" style={{ transform: `scale(${scale})` }} > {children} {/* Zoom buttons for non-touch or accessibility */} <div className="zoom-controls"> <button onClick={() => setScale(Math.max(1, scale - 0.1))} aria-label="Zoom out"> − </button> <span>{Math.round(scale * 100)}%</span> <button onClick={() => setScale(Math.min(3, scale + 0.1))} aria-label="Zoom in"> + </button> </div> </div> ); } ``` ### Pattern 3: Long-Press with Tap Alternative ```typescript import { useState, useRef } from 'react'; interface LongPressProps { onLongPress: () => void; onTap: () => void; duration?: number; children: React.ReactNode; } export function LongPressButton({ onLongPress, onTap, duration = 500, children, }: LongPressProps) { const timerRef = useRef<NodeJS.Timeout>(); const [isPressed, setIsPressed] = useState(false); const handleTouchStart = () => { setIsPressed(true); timerRef.current = setTimeout(() => { onLongPress(); setIsPressed(false); }, duration); }; const handleTouchEnd = () => { if (timerRef.current) { clearTimeout(timerRef.current); } if (isPressed) { onTap(); } setIsPressed(false); }; return ( <div onTouchStart={handleTouchStart} onTouchEnd={handleTouchEnd} className={`long-press-button ${isPressed ? 'pressed' : ''}`} > {children} {/* Explicit button for non-touch/accessibility */}
Ver no GitHub
Este SKILL.md e muito grande, entao o SkillsMP mostra aqui apenas a primeira secao. Ver no GitHub