A continuation of Janady13/crawler-signal-reference / framework-html-meta-viewport.md. The complete source identity, every recorded occurrence, and linked equations are on its first page.
Source transcript · continued
Download complete source text ↓2,220 lines · 70.4 KB
}
}
```
### 19.6 The WCAG safe viewport (verification snippet)
```bash
#!/bin/bash
# Verify viewport meta tag is WCAG compliant
URL=$1
VIEWPORT=$(curl -s "$URL" | grep -oE 'meta name="viewport"[^>]+' | head -1)
echo "Viewport tag: $VIEWPORT"
if [[ "$VIEWPORT" =~ user-scalable=(no|0) ]]; then
echo "VIOLATION: user-scalable disabled (WCAG 1.4.4 failure)"
exit 1
fi
if [[ "$VIEWPORT" =~ maximum-scale=([01]\.?[0-9]*) ]]; then
SCALE="${BASH_REMATCH[1]}"
if (( $(echo "$SCALE < 2" | bc -l) )); then
echo "VIOLATION: maximum-scale $SCALE less than 2 (WCAG 1.4.4 failure)"
exit 1
fi
fi
echo "PASS: viewport tag is WCAG compliant"
```
### 19.7 Reduced motion media query
```css
.animated-element {
animation: slideIn 0.5s ease-out;
}
@media (prefers-reduced-motion: reduce) {
.animated-element {
animation: none;
}
* {
animation-duration: 0.01ms !important;
animation-iteration-count: 1 !important;
transition-duration: 0.01ms !important;
}
}
```
### 19.8 Dark mode aware
```css
:root {
--bg-primary: #ffffff;
--bg-secondary: #f5f5f5;
--fg-primary: #1a1a1a;
--fg-secondary: #6b7280;
--accent: #3B7434;
}
@media (prefers-color-scheme: dark) {
:root {
--bg-primary: #1a1a1a;
--bg-secondary: #2a2a2a;
--fg-primary: #f5f5f5;
--fg-secondary: #9ca3af;
--accent: #6B46C1;
}
}
body {
background: var(--bg-primary);
color: var(--fg-primary);
}
```
---
## 20. BUBBLES STANDARD PATTERN (PASTE READY)
The canonical Bubbles viewport configuration with all supporting CSS.
### 20.1 The Base Template
```html
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Page Title | ThatDeveloperGuy</title>
<meta name="description" content="...">
<!-- For Awwwards caliber sites with full bleed: -->
<!-- <meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover"> -->
<!-- Theme color (browser chrome) -->
<meta name="theme-color" content="#3B7434">
<!-- Stylesheets -->
<link rel="stylesheet" href="/styles.css">
</head>
<body>
<!-- content -->
<footer>
<p>Crafted by ThatDeveloperGuy.com</p>
</footer>
</body>
</html>
```
### 20.2 The Standard CSS Reset Plus Base
```css
/* /var/www/sites/example.com/styles.css */
/* Box sizing reset */
*,
*::before,
*::after {
box-sizing: border-box;
}
/* Document base */
html {
font-size: 16px;
-webkit-text-size-adjust: 100%;
text-size-adjust: 100%;
}
body {
margin: 0;
padding: 0;
font-family: system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
font-size: 16px;
line-height: 1.5;
color: #1a1a1a;
background: #fff;
}
/* Prevent iOS auto zoom on form inputs */
input,
textarea,
select {
font-size: 16px;
}
/* Minimum touch target sizes for accessibility */
button,
a.button,
input[type="submit"],
input[type="button"] {
min-height: 44px;
min-width: 44px;
padding: 12px 16px;
cursor: pointer;
}
/* Container with mobile first padding */
.container {
width: 100%;
max-width: 1200px;
margin: 0 auto;
padding: 0 1rem;
}
@media (min-width: 768px) {
.container {
padding: 0 2rem;
}
}
/* Responsive images */
img,
video,
iframe {
max-width: 100%;
height: auto;
}
/* Reduced motion support */
@media (prefers-reduced-motion: reduce) {
*,
*::before,
*::after {
animation-duration: 0.01ms !important;
animation-iteration-count: 1 !important;
transition-duration: 0.01ms !important;
}
}
```
### 20.3 The Audit Script
```bash
#!/bin/bash
# /usr/local/bin/viewport-audit.sh
URL=$1
echo "=== Viewport audit for $URL ==="
echo ""
VIEWPORT=$(curl -s "$URL" | head -c 4000 | grep -oE 'meta name="viewport"[^>]+' | head -1)
if [ -z "$VIEWPORT" ]; then
echo "FAIL: no viewport meta tag found"
exit 1
fi
echo "Viewport tag: $VIEWPORT"
echo ""
# Check for canonical pattern
if echo "$VIEWPORT" | grep -q "width=device-width" && \
echo "$VIEWPORT" | grep -q "initial-scale=1"; then
echo "OK: width=device-width and initial-scale=1 present"
else
echo "WARN: viewport does not match canonical pattern"
fi
# Check for WCAG violations
WCAG_VIOLATIONS=0
if [[ "$VIEWPORT" =~ user-scalable=(no|0) ]]; then
echo "FAIL: user-scalable disabled (WCAG 1.4.4 violation)"
WCAG_VIOLATIONS=$((WCAG_VIOLATIONS + 1))
fi
if [[ "$VIEWPORT" =~ maximum-scale=([0-9.]+) ]]; then
SCALE="${BASH_REMATCH[1]}"
if (( $(echo "$SCALE < 2" | bc -l 2>/dev/null) )); then
echo "FAIL: maximum-scale $SCALE less than 2 (WCAG 1.4.4 violation)"
WCAG_VIOLATIONS=$((WCAG_VIOLATIONS + 1))
fi
fi
if [ $WCAG_VIOLATIONS -eq 0 ]; then
echo "OK: no WCAG 1.4.4 violations"
fi
echo ""
echo "=== End audit ==="
if [ $WCAG_VIOLATIONS -gt 0 ]; then
exit 1
fi
```
Make executable:
```bash
sudo chmod +x /usr/local/bin/viewport-audit.sh
viewport-audit.sh https://example.com/
```
After any template change:
```bash
# Reload nginx if static files (most Bubbles client sites)
nginx -t && systemctl reload nginx
# Or restart FastAPI sidecar for templated content
systemctl restart fastapi-sidecar
# Verify
viewport-audit.sh https://example.com/
```
---
## 21. AUDIT CHECKLIST
Run through these 50 items for any production deployment.
### Core viewport tag
1. [ ] `<meta name="viewport">` tag present in every HTML page.
2. [ ] Tag placed in `<head>` after `<meta charset>`.
3. [ ] Tag within first 1024 bytes of document (within charset's window).
4. [ ] `width=device-width` set.
5. [ ] `initial-scale=1` set.
### WCAG 1.4.4 compliance (critical for SDVOSB federal work)
6. [ ] **NO `user-scalable=no` anywhere in any template.**
7. [ ] **NO `user-scalable=0` anywhere in any template.**
8. [ ] **NO `maximum-scale=1` (or below 2) anywhere in any template.**
9. [ ] Verified across all templates, not just homepage.
10. [ ] CI pre deploy check rejects WCAG violations.
### Notch and safe area (for full bleed designs)
11. [ ] `viewport-fit=cover` added if design requires full bleed.
12. [ ] CSS uses `env(safe-area-inset-*)` for interactive element positioning.
13. [ ] Tested on actual iPhone X+ device (not just simulator).
14. [ ] Landscape orientation also handled (safe-area-inset-left, safe-area-inset-right).
15. [ ] Fallback values provided in env() functions.
### Mobile first CSS
16. [ ] Base CSS targets smallest screens.
17. [ ] Media queries use `min-width` (not max-width).
18. [ ] `box-sizing: border-box` set globally.
19. [ ] No fixed pixel widths on containers.
20. [ ] Images use `max-width: 100%; height: auto;`.
### Form behavior
21. [ ] Form inputs have `font-size: 16px` minimum (prevents iOS auto zoom).
22. [ ] Form fields visible when keyboard appears (scroll behavior verified).
23. [ ] Submit buttons accessible during keyboard interaction.
24. [ ] `accept-charset="utf-8"` on forms (covered in framework-html-meta-charset.md).
### Touch targets (WCAG 2.5.5)
25. [ ] All interactive elements at least 44 by 44 CSS pixels.
26. [ ] Adjacent touch targets have at least 8px spacing.
27. [ ] Touch targets visible without zoom.
### Text readability (WCAG 1.4.4)
28. [ ] Body text at least 16px (1rem) by default.
29. [ ] Text remains readable when zoomed to 200%.
30. [ ] No horizontal scroll at 200% zoom.
### Mobile usability (Google ranking signal)
31. [ ] Google Search Console > Mobile Usability shows no errors.
32. [ ] Mobile Friendly Test (search.google.com/test/mobile-friendly) passes.
33. [ ] No "Content wider than screen" errors.
34. [ ] No "Text too small to read" errors.
35. [ ] No "Clickable elements too close together" errors.
### Cross device testing
36. [ ] Tested on iPhone (notched, latest iOS).
37. [ ] Tested on Android (Pixel or similar, latest Chrome).
38. [ ] Tested on iPad (portrait and landscape).
39. [ ] Tested on Android tablet.
40. [ ] Tested with browser zoom at 200%, 400%.
41. [ ] Tested with system level large text size enabled.
### Tooling and automation
42. [ ] Lighthouse Mobile audit score 95+.
43. [ ] Lighthouse Accessibility audit score 100.
44. [ ] axe DevTools audit passes for viewport related rules.
45. [ ] `viewport-audit.sh` script available at `/usr/local/bin/`.
46. [ ] Pre deploy check runs viewport audit.
### Documentation and process
47. [ ] Viewport configuration documented in site template.
48. [ ] WCAG compliance statement in accessibility documentation.
49. [ ] Federal client deliverable includes accessibility statement.
50. [ ] Quarterly audit reviews viewport configuration.
A site that passes all 50 has correctly configured viewport for mobile rendering, WCAG accessibility compliance, and Google mobile usability ranking signals.
---
## 22. COMMON PITFALLS
Fifteen patterns to recognize and avoid.
**Pitfall 1: `user-scalable=no` in the viewport tag.**
Symptom: Lighthouse, axe DevTools flag accessibility violation; users with low vision cannot zoom.
Why it breaks: WCAG 1.4.4 violation. Federal Section 508 audit failure.
Fix: remove `user-scalable=no` from the viewport meta tag.
**Pitfall 2: `maximum-scale=1` to "prevent zoom breakage".**
Symptom: same as above; WCAG 1.4.4 violation.
Why it breaks: WCAG requires zoom to at least 200%; 1 is below 2.
Fix: remove `maximum-scale` entirely (browser defaults to ~10x); or set `maximum-scale=2` minimum.
**Pitfall 3: Missing viewport meta tag.**
Symptom: site renders desktop layout shrunk on mobile; tiny text.
Why it breaks: mobile browser assumes ~980px desktop viewport by default.
Fix: add `<meta name="viewport" content="width=device-width, initial-scale=1">`.
**Pitfall 4: Viewport tag placed after large content in `<head>`.**
Symptom: viewport configuration applied late; some browsers ignore.
Why it breaks: viewport tag should be early in head for layout calculation.
Fix: place viewport meta tag immediately after `<meta charset>`.
**Pitfall 5: iOS auto zoom on form input focus.**
Symptom: tapping a form field causes the entire page to zoom in awkwardly.
Why it breaks: iOS auto zooms inputs with font-size less than 16px.
Fix: set `font-size: 16px` on input, textarea, select elements via CSS.
**Pitfall 6: Form fields hidden under keyboard on mobile.**
Symptom: user can't see what they're typing because keyboard covers the field.
Why it breaks: form not scrolling to keep focused input visible.
Fix: use `scroll-margin-bottom` CSS or programmatic scroll on focus.
**Pitfall 7: Horizontal scroll on mobile despite viewport tag.**
Symptom: viewport tag present but page has horizontal scroll on small screens.
Why it breaks: fixed width container or overflowing element wider than viewport.
Fix: audit containers for fixed pixel widths; ensure `max-width: 100%`.
**Pitfall 8: Content cut off by notch on iPhone X+.**
Symptom: header content or buttons obscured by notch.
Why it breaks: design extends to edges but no `env(safe-area-inset-*)` padding.
Fix: add `viewport-fit=cover` AND CSS env() padding on edge positioned elements.
**Pitfall 9: Three.js or WebGL canvas wrong size on mobile.**
Symptom: WebGL canvas appears stretched, distorted, or wrong aspect ratio.
Why it breaks: canvas resize not handled correctly; relies on viewport dimensions.
Fix: handle canvas resize via JavaScript using `visualViewport` API; use `100svh` not `100vh`.
**Pitfall 10: Page jumps when mobile keyboard appears.**
Symptom: visible content suddenly shifts when user taps an input.
Why it breaks: `100vh` height changes when keyboard appears.
Fix: use `100dvh` (dynamic viewport height) or `100svh` (smallest viewport height).
**Pitfall 11: Email client renders broken layout.**
Symptom: email looks fine in browser but breaks in Gmail/Outlook.
Why it breaks: email clients have different viewport handling; meta tag may be ignored.
Fix: use table based layout for emails; rely on inline styles; test in actual email clients.
**Pitfall 12: PWA install prompt shows wrong meta theme color.**
Symptom: PWA installs with default browser color instead of brand color.
Why it breaks: `<meta name="theme-color">` missing or incorrect.
Fix: add `<meta name="theme-color" content="#XXXXXX">` matching brand.
**Pitfall 13: User zoom causes text overlap.**
Symptom: at 200% zoom, text columns overlap or content becomes unreadable.
Why it breaks: layout uses fixed positioning that doesn't reflow.
Fix: use flexible layouts (flexbox, grid); test at 200% and 400% zoom.
**Pitfall 14: Dark mode meta theme color not updating.**
Symptom: browser chrome stays light blue in dark mode.
Why it breaks: only one `theme-color` declared.
Fix:
```html
<meta name="theme-color" content="#ffffff" media="(prefers-color-scheme: light)">
<meta name="theme-color" content="#1a1a1a" media="(prefers-color-scheme: dark)">
```
**Pitfall 15: Lighthouse passes but real device fails.**
Symptom: Lighthouse score 100, but actual iPhone displays differently.
Why it breaks: Lighthouse simulates a specific device profile; real device behavior varies.
Fix: test on real devices (iPhone, Android, iPad); do not rely solely on Lighthouse simulation.
---
## 23. DIAGNOSTIC COMMANDS
Reference of every command useful for viewport investigation.
### Inspect viewport meta tag
```bash
# Extract viewport tag from a URL
curl -s https://example.com/ | grep -oE 'meta name="viewport"[^>]+' | head -1
# Check viewport tag is within first 1024 bytes
curl -s https://example.com/ | head -c 1024 | grep -c "meta name=\"viewport\""
# Expected: 1
```
### Check for WCAG violations
```bash
# Find viewport meta tag and check for violations
URL=$1
VIEWPORT=$(curl -s "$URL" | grep -oE 'meta name="viewport"[^>]+' | head -1)
echo "$VIEWPORT"
# Check user-scalable
if [[ "$VIEWPORT" =~ user-scalable=(no|0) ]]; then
echo "VIOLATION: user-scalable disabled"
fi
# Check maximum-scale
if [[ "$VIEWPORT" =~ maximum-scale=([0-9.]+) ]]; then
SCALE="${BASH_REMATCH[1]}"
if (( $(echo "$SCALE < 2" | bc -l 2>/dev/null) )); then
echo "VIOLATION: maximum-scale $SCALE < 2"
fi
fi
```
### Bulk audit all client sites
```bash
# Audit every Bubbles client site
for site_dir in /var/www/sites/*/; do
INDEX="$site_dir/index.html"
if [ -f "$INDEX" ]; then
VIEWPORT=$(grep -oE 'meta name="viewport"[^>]+' "$INDEX" | head -1)
SITE=$(basename "$site_dir")
if [ -z "$VIEWPORT" ]; then
echo "MISSING: $SITE has no viewport tag"
elif [[ "$VIEWPORT" =~ user-scalable=(no|0) ]]; then
echo "WCAG VIOLATION: $SITE - $VIEWPORT"
elif [[ "$VIEWPORT" =~ maximum-scale=([01]\.?[0-9]*) ]]; then
echo "WCAG VIOLATION: $SITE - maximum-scale below 2"
else
echo "OK: $SITE"
fi
fi
done
```
### Test mobile usability via Lighthouse
```bash
# Install Lighthouse if not present
npm install -g lighthouse
# Run Lighthouse with mobile preset
lighthouse https://example.com/ \
--preset=desktop \
--only-categories=accessibility,seo \
--output=html \
--output-path=/tmp/lighthouse-mobile.html \
--chrome-flags="--headless"
# Check viewport specific audit
lighthouse https://example.com/ \
--only-audits=viewport,meta-viewport \
--output=json \
--output-path=/tmp/lighthouse-viewport.json \
--quiet
python3 -c "
import json
with open('/tmp/lighthouse-viewport.json') as f:
data = json.load(f)
for audit_id, audit in data.get('audits', {}).items():
if 'viewport' in audit_id.lower():
print(f'{audit_id}: {audit.get(\"score\")} - {audit.get(\"title\")}')
"
```
### Test mobile usability via Google's API
```bash
# Mobile Friendly Test API (deprecated as of 2025; use Search Console URL Inspection instead)
# Manual: search.google.com/test/mobile-friendly
# Or use Google PageSpeed Insights API (free tier)
API_KEY="YOUR_KEY"
URL="https://example.com/"
curl -s "https://www.googleapis.com/pagespeedonline/v5/runPagespeed?url=$URL&strategy=mobile&key=$API_KEY" | python3 -m json.tool | grep -A 3 "viewport"
```
### Browser DevTools verification
In Chrome DevTools:
1. Open the page.
2. Toggle device toolbar (Cmd/Ctrl + Shift + M).
3. Select a mobile device profile.
4. Check that the page renders at device width, not shrunk.
5. Use Lighthouse panel to run accessibility audit.
6. Use Inspect tool to verify `<meta name="viewport">` in DOM.
### Real device testing
```bash
# iPad/iPhone testing via Safari Web Inspector
# 1. Enable Web Inspector on iOS: Settings > Safari > Advanced > Web Inspector ON
# 2. Connect iOS device to Mac via USB
# 3. Open Safari on Mac: Develop menu > device name > page name
# 4. Inspect viewport via Console:
# document.documentElement.clientWidth
# document.documentElement.clientHeight
# window.innerWidth
# window.innerHeight
```
### Bulk fix WCAG violations
```bash
# Find and replace user-scalable=no
find /var/www/sites/ -name "*.html" -type f -exec \
sed -i 's/, *user-scalable=no//g; s/, *user-scalable=0//g; s/user-scalable=no, *//g; s/user-scalable=0, *//g' {} \;
# Find and replace maximum-scale=1
find /var/www/sites/ -name "*.html" -type f -exec \
sed -i 's/, *maximum-scale=1\([^.]\)/\1/g; s/, *maximum-scale=1\.0\b//g' {} \;
# Verify
for site_dir in /var/www/sites/*/; do
grep -l "user-scalable=no\|maximum-scale=1\b" "$site_dir"/index.html 2>/dev/null
done
# Should produce no output
```
### After viewport changes
```bash
# Reload nginx if templates served as static files
nginx -t && systemctl reload nginx
# Or restart FastAPI sidecar if templates rendered server side
systemctl restart fastapi-sidecar
# Verify the change
viewport-audit.sh https://example.com/
```
---
## 24. CROSS-REFERENCES
* [framework-html-meta-charset.md](framework-html-meta-charset.md): place the viewport meta tag immediately after `<meta charset="utf-8">` in `<head>`; both must be within the first 1024 bytes.
* [framework-html-meta-robots.md](framework-html-meta-robots.md): the first framework in the HTML signal track; controls indexing.
* [framework-http-seo-headers.md](framework-http-seo-headers.md): X-Robots-Tag HTTP header complements meta robots; not directly related to viewport but part of the same head section signal layer.
* [framework-http-performance-headers.md](framework-http-performance-headers.md) Section 5 (Alt-Svc): performance signals that affect mobile rendering speed.
* [UNIVERSAL-RANKING-FRAMEWORK.md](UNIVERSAL-RANKING-FRAMEWORK.md): the master ranking reference. Mobile usability is a documented ranking factor.
* [SEO-BUILD-REFERENCE.md](SEO-BUILD-REFERENCE.md) v2.4: the build playbook including viewport placement in templates.
* W3C CSS Device Adaptation: https://www.w3.org/TR/css-device-adapt/
* MDN viewport meta tag: https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/meta/name/viewport
* WCAG 2.1 Success Criterion 1.4.4 (Resize Text): https://www.w3.org/WAI/WCAG21/Understanding/resize-text.html
* WCAG ACT Rule b4f0c3 (Meta viewport allows for zoom): https://www.w3.org/WAI/standards-guidelines/act/rules/b4f0c3/
* Section 508 Standards: https://www.access-board.gov/ict/
* Google Mobile Friendly Test: https://search.google.com/test/mobile-friendly
* Lighthouse accessibility audits: https://web.dev/articles/accessibility-audits-with-lighthouse
---
## APPENDIX A: ONE PAGE QUICK REFERENCE
For the person who just wants the answer.
### The canonical pattern
```html
<meta name="viewport" content="width=device-width, initial-scale=1">
```
Place immediately after `<meta charset="utf-8">` in `<head>`.
### The forbidden patterns (WCAG 1.4.4 violations)
```html
<!-- NEVER USE -->
<meta name="viewport" content="..., user-scalable=no">
<meta name="viewport" content="..., maximum-scale=1">
<meta name="viewport" content="..., user-scalable=0">
```
### The full bleed pattern (for designs that extend under notch)
```html
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">
```
Pair with CSS:
```css
.header {
padding-top: env(safe-area-inset-top);
padding-left: env(safe-area-inset-left);
padding-right: env(safe-area-inset-right);
}
.footer {
padding-bottom: env(safe-area-inset-bottom);
}
```
### Five rules to memorize
1. **The canonical pattern is `width=device-width, initial-scale=1`. That's it for 95% of sites.**
2. **NEVER set `user-scalable=no` or `maximum-scale` below 2 (WCAG 1.4.4).**
3. **Add `viewport-fit=cover` only when design extends under notches.**
4. **Form inputs need `font-size: 16px` minimum (prevents iOS auto zoom).**
5. **Test on real devices, not just Chrome DevTools.**
### Five commands every operator should know
```bash
# 1. Check viewport tag
curl -s URL | grep -oE 'meta name="viewport"[^>]+'
# 2. Check for WCAG violations
curl -s URL | grep -oE 'meta name="viewport"[^>]+' | grep -E "user-scalable=(no|0)|maximum-scale=1\b"
# 3. Lighthouse mobile audit
lighthouse URL --only-categories=accessibility,seo --quiet --output=html --output-path=/tmp/lh.html
# 4. Bulk audit all sites
for site in /var/www/sites/*/index.html; do echo "$(basename $(dirname $site)): $(grep -oE 'meta name="viewport"[^>]+' $site | head -1)"; done
# 5. Apply changes
nginx -t && systemctl reload nginx
```
### Three end to end tests
```bash
# 1. Viewport tag present and canonical
URL=https://example.com/
VP=$(curl -s "$URL" | grep -oE 'meta name="viewport"[^>]+' | head -1)
[[ "$VP" =~ "width=device-width" ]] && [[ "$VP" =~ "initial-scale=1" ]] && echo "OK" || echo "FAIL"
# 2. No WCAG violations
[[ "$VP" =~ user-scalable=(no|0) ]] && echo "FAIL: WCAG violation" || echo "OK: WCAG"
# 3. Google Mobile Friendly Test passes
# (Manual: search.google.com/test/mobile-friendly with URL)
# Or use viewport-audit.sh
viewport-audit.sh "$URL"
```
If all three pass AND no horizontal scroll on real mobile devices AND Lighthouse accessibility score is 100, the viewport layer is correctly wired.
---
End of framework-html-meta-viewport.md.