@ctxr/skill-frontend-excellence 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,519 @@
1
+ # Responsive Layout
2
+
3
+ Framework-agnostic guidance on building layouts that work across the full range of devices, from 320px phones to 4K displays, in portrait and landscape, with light and dark themes, with system text scaling, and with browser zoom.
4
+
5
+ ## Mobile-First
6
+
7
+ Always design and code mobile first.
8
+
9
+ - Mobile constraints (small viewport, touch input, slow network, limited CPU) force the right tradeoffs.
10
+ - Adding desktop affordances on top of mobile is easier than stripping mobile complexity from a desktop-first design.
11
+ - Most users are mobile. Treating mobile as an afterthought is treating users as an afterthought.
12
+
13
+ CSS pattern:
14
+
15
+ ```css
16
+ /* Default styles target mobile */
17
+ .nav {
18
+ padding: 12px;
19
+ flex-direction: column;
20
+ }
21
+
22
+ /* Min-width media queries layer up */
23
+ @media (min-width: 768px) {
24
+ .nav {
25
+ padding: 16px 24px;
26
+ flex-direction: row;
27
+ }
28
+ }
29
+ ```
30
+
31
+ Avoid `max-width` queries layered downward; they invert the natural flow.
32
+
33
+ ## Breakpoints
34
+
35
+ Pick a small, consistent set. Common scales:
36
+
37
+ | Breakpoint | Pixel | Targets |
38
+ |-----------|-------|---------|
39
+ | (default) | < 640px | Phones |
40
+ | `sm` | 640px | Large phones, small tablets |
41
+ | `md` | 768px | Tablets portrait |
42
+ | `lg` | 1024px | Tablets landscape, small laptops |
43
+ | `xl` | 1280px | Laptops, desktops |
44
+ | `2xl` | 1536px | Large desktops |
45
+ | `3xl` (optional) | 1920px | Very large displays |
46
+
47
+ Tailwind's defaults match this. Custom breakpoints should be deliberate.
48
+
49
+ Don't add a breakpoint per device. Three or four well-chosen breakpoints handle every reasonable layout.
50
+
51
+ ## Canonical Audit Capture Viewports
52
+
53
+ For multi-page polish work, two specific viewport sizes are the canonical capture targets. They are the sizes used by the screenshot loop and the geometry sweep.
54
+
55
+ | Role | Width x Height | Notes |
56
+ |------|----------------|-------|
57
+ | Desktop audit | `1440x900` | Common laptop and external display proxy; wide enough to see desktop nav, narrow enough to catch hero overflow |
58
+ | Mobile audit | `375x812` | iPhone-class viewport; small enough to expose mobile drawer, drift, and touch-target failures |
59
+
60
+ Both sizes are required for any cross-page audit. See [audit-workflow.md](audit-workflow.md) Phase 4 for the capture procedure and [defects.md](defects.md) for the geometry sweep that runs at both sizes.
61
+
62
+ ## Container Queries
63
+
64
+ Container queries (`@container`) let components respond to their container, not the viewport. Use when the same component appears in different layouts:
65
+
66
+ ```css
67
+ .card {
68
+ container-type: inline-size;
69
+ container-name: card;
70
+ }
71
+
72
+ @container card (min-width: 400px) {
73
+ .card-body {
74
+ flex-direction: row;
75
+ }
76
+ }
77
+ ```
78
+
79
+ When to use:
80
+
81
+ - A card that's full-width on mobile and a 1/3 column on desktop, where its internal layout should respond to its width, not the viewport.
82
+ - Reusable components that appear in sidebars, modals, and main content.
83
+
84
+ When NOT to use:
85
+
86
+ - Top-level page layout. Viewport queries are simpler and more universally supported.
87
+
88
+ ## Fluid Typography
89
+
90
+ Use `clamp()` to scale type smoothly between breakpoints rather than stepping at fixed media queries:
91
+
92
+ ```css
93
+ .h1 {
94
+ font-size: clamp(2rem, 1.5rem + 2.5vw, 4rem);
95
+ /* min 32px, max 64px, scales with viewport between */
96
+ }
97
+ ```
98
+
99
+ Format: `clamp(min, preferred, max)`. The preferred value should reach the min at the smallest viewport you support and the max at the largest.
100
+
101
+ For vertical rhythm, also use clamp on `line-height` if needed, though usually keeping `line-height` as a unitless multiplier (`line-height: 1.2`) handles fluid scaling automatically.
102
+
103
+ ## Viewport Units
104
+
105
+ | Unit | Meaning |
106
+ |------|--------|
107
+ | `vw` / `vh` | 1% of viewport width/height (legacy, includes browser chrome on mobile) |
108
+ | `dvw` / `dvh` | Dynamic viewport (changes as browser chrome shows/hides). Use for full-height mobile layouts. |
109
+ | `svw` / `svh` | Small viewport (smallest possible, browser chrome visible) |
110
+ | `lvw` / `lvh` | Large viewport (largest possible, browser chrome hidden) |
111
+ | `cqw` / `cqh` | Container query units |
112
+
113
+ For full-height mobile heroes, use `min-height: 100dvh` (not `100vh`). `100vh` overflows on iOS Safari when the address bar is showing.
114
+
115
+ ## Safe Areas
116
+
117
+ Modern phones have notches, dynamic islands, gesture bars, and home indicators. Respect them.
118
+
119
+ ```css
120
+ .fixed-bottom-bar {
121
+ padding-bottom: max(16px, env(safe-area-inset-bottom));
122
+ }
123
+
124
+ .fixed-top-bar {
125
+ padding-top: max(16px, env(safe-area-inset-top));
126
+ }
127
+ ```
128
+
129
+ In your viewport meta:
130
+
131
+ ```html
132
+ <meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover" />
133
+ ```
134
+
135
+ The `viewport-fit=cover` is required for `env(safe-area-inset-*)` to work properly.
136
+
137
+ ## Logical Properties
138
+
139
+ For RTL support, use logical properties instead of physical:
140
+
141
+ | Physical | Logical |
142
+ |---------|---------|
143
+ | `margin-left` | `margin-inline-start` |
144
+ | `margin-right` | `margin-inline-end` |
145
+ | `padding-top` | `padding-block-start` |
146
+ | `padding-bottom` | `padding-block-end` |
147
+ | `text-align: left` | `text-align: start` |
148
+ | `text-align: right` | `text-align: end` |
149
+ | `border-left` | `border-inline-start` |
150
+ | `width` | `inline-size` |
151
+ | `height` | `block-size` |
152
+
153
+ When `<html dir="rtl">`, logical properties automatically flip. Physical properties don't.
154
+
155
+ ## Layout Primitives
156
+
157
+ Build layouts from a small set of primitives:
158
+
159
+ ### Stack
160
+
161
+ Vertical layout with consistent gap.
162
+
163
+ ```css
164
+ .stack {
165
+ display: flex;
166
+ flex-direction: column;
167
+ gap: var(--space-4);
168
+ }
169
+ ```
170
+
171
+ ### Cluster
172
+
173
+ Horizontal layout, wraps to multiple lines if needed, consistent gap.
174
+
175
+ ```css
176
+ .cluster {
177
+ display: flex;
178
+ flex-wrap: wrap;
179
+ gap: var(--space-3);
180
+ align-items: center;
181
+ }
182
+ ```
183
+
184
+ ### Sidebar
185
+
186
+ Two-column layout where one column is fixed-width and the other flexes.
187
+
188
+ ```css
189
+ .with-sidebar {
190
+ display: flex;
191
+ flex-wrap: wrap;
192
+ gap: var(--space-6);
193
+ }
194
+
195
+ .with-sidebar > .sidebar {
196
+ flex-basis: 280px;
197
+ flex-grow: 1;
198
+ }
199
+
200
+ .with-sidebar > .main {
201
+ flex-basis: 0;
202
+ flex-grow: 999;
203
+ min-inline-size: 50%;
204
+ }
205
+ ```
206
+
207
+ ### Switcher
208
+
209
+ Multi-column layout that becomes a stack below a threshold.
210
+
211
+ ```css
212
+ .switcher {
213
+ display: flex;
214
+ flex-wrap: wrap;
215
+ gap: var(--space-4);
216
+ }
217
+
218
+ .switcher > * {
219
+ flex-grow: 1;
220
+ flex-basis: calc((600px - 100%) * 999);
221
+ }
222
+ ```
223
+
224
+ When the container drops below 600px, items stack.
225
+
226
+ ### Center
227
+
228
+ Center an element with optional max-width.
229
+
230
+ ```css
231
+ .center {
232
+ box-sizing: content-box;
233
+ max-inline-size: 65ch;
234
+ margin-inline: auto;
235
+ padding-inline: var(--space-4);
236
+ }
237
+ ```
238
+
239
+ ### Cover
240
+
241
+ Fill the available space with header, centered content, footer.
242
+
243
+ ```css
244
+ .cover {
245
+ display: flex;
246
+ flex-direction: column;
247
+ min-block-size: 100dvh;
248
+ padding: var(--space-4);
249
+ }
250
+
251
+ .cover > main {
252
+ margin-block: auto;
253
+ }
254
+ ```
255
+
256
+ ### Frame
257
+
258
+ A box with a fixed aspect ratio.
259
+
260
+ ```css
261
+ .frame {
262
+ aspect-ratio: 16 / 9;
263
+ overflow: hidden;
264
+ }
265
+
266
+ .frame > img,
267
+ .frame > video {
268
+ inline-size: 100%;
269
+ block-size: 100%;
270
+ object-fit: cover;
271
+ }
272
+ ```
273
+
274
+ ### Reel
275
+
276
+ Horizontal scrolling cluster (carousel without arrows).
277
+
278
+ ```css
279
+ .reel {
280
+ display: flex;
281
+ gap: var(--space-4);
282
+ overflow-x: auto;
283
+ scroll-snap-type: x mandatory;
284
+ scrollbar-width: thin;
285
+ }
286
+
287
+ .reel > * {
288
+ flex: 0 0 auto;
289
+ scroll-snap-align: start;
290
+ }
291
+ ```
292
+
293
+ ### Grid
294
+
295
+ CSS Grid with auto-fit:
296
+
297
+ ```css
298
+ .grid {
299
+ display: grid;
300
+ grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
301
+ gap: var(--space-4);
302
+ }
303
+ ```
304
+
305
+ This wraps from 1 column to 2/3/4 columns as the container grows.
306
+
307
+ ## Containers and Max Widths
308
+
309
+ ### Page container
310
+
311
+ Most surfaces benefit from a max content width:
312
+
313
+ ```css
314
+ .container {
315
+ max-inline-size: var(--container-width, 1280px);
316
+ margin-inline: auto;
317
+ padding-inline: clamp(16px, 4vw, 48px);
318
+ }
319
+ ```
320
+
321
+ Common max-widths:
322
+
323
+ | Width | Use |
324
+ |-------|-----|
325
+ | 640px | Long-form reading content |
326
+ | 768px | Reading with a sidebar |
327
+ | 1024px | Routine application UI |
328
+ | 1280px | Hero or feature-rich layouts |
329
+ | 1440px | Big imagery, generous whitespace |
330
+ | 1600px | Wide screens, data-heavy surfaces |
331
+
332
+ Don't go full-width on huge displays. Lines longer than ~75 characters become hard to read.
333
+
334
+ ### Reading containers
335
+
336
+ For prose, set `max-inline-size: 65ch` to constrain line length.
337
+
338
+ ## Horizontal Scroll: Forbidden
339
+
340
+ The single most common responsive bug: horizontal scroll on mobile.
341
+
342
+ Causes:
343
+
344
+ - Fixed-pixel widths exceeding viewport (`width: 1200px` on a 375px screen).
345
+ - Long unbreakable text (URLs, code, words in CJK).
346
+ - Images without `max-width: 100%`.
347
+ - Tables without overflow handling.
348
+ - Sidebars that don't collapse.
349
+
350
+ Fixes:
351
+
352
+ - Use `max-inline-size: 100%` (or `max-width: 100%` for non-RTL contexts) on images and embeds.
353
+ - Use `overflow-wrap: break-word` and `word-break: break-word` for long strings.
354
+ - Use `<div style="overflow-x: auto">` to wrap wide tables.
355
+ - Make sidebars collapse at small breakpoints.
356
+
357
+ Test: at 320px width, scroll horizontally; if you can, fix it.
358
+
359
+ ## Accessibility at Different Viewports
360
+
361
+ WCAG 1.4.10 (Reflow): content must be usable at 320 CSS pixels wide without horizontal scroll.
362
+
363
+ WCAG 1.4.4 (Resize Text): content must be usable at 200% browser zoom without loss of functionality.
364
+
365
+ Test:
366
+
367
+ - Resize the browser to 320px wide.
368
+ - Set browser zoom to 200%.
369
+ - Set OS text size to largest.
370
+
371
+ If the layout breaks, fix it.
372
+
373
+ ## Orientation
374
+
375
+ Some users hold phones in landscape. Don't lock layout to portrait.
376
+
377
+ - Don't assume landscape phones have a tablet-class screen height (they're short).
378
+ - For full-screen forms or inputs, support both orientations.
379
+ - Test landscape mode on an actual phone.
380
+
381
+ ## Dynamic Type / Font Scaling
382
+
383
+ iOS and Android both support system-wide font scaling for accessibility. Web should respect browser zoom (which scales `rem`) and the system text size where exposed.
384
+
385
+ ```css
386
+ /* Use rem for font-size so user agent text scaling works */
387
+ body {
388
+ font-size: 1rem; /* defaults to 16px, scales with user prefs */
389
+ }
390
+
391
+ h1 {
392
+ font-size: 2.5rem; /* scales proportionally */
393
+ }
394
+ ```
395
+
396
+ Avoid `px` for font sizes. Use `rem` everywhere except where pixel-perfect rendering matters (logos, single-line UI labels).
397
+
398
+ ## Reduced Motion
399
+
400
+ Respect `prefers-reduced-motion`. See [motion.md](motion.md).
401
+
402
+ ## Reduced Data
403
+
404
+ ```css
405
+ @media (prefers-reduced-data: reduce) {
406
+ /* Skip non-essential animations, replace videos with posters, lower image quality */
407
+ }
408
+ ```
409
+
410
+ Not yet widely supported, but adding the rule costs nothing.
411
+
412
+ ## High Contrast
413
+
414
+ ```css
415
+ @media (prefers-contrast: more) {
416
+ /* Bump border weights, increase contrast, simplify backgrounds */
417
+ :root {
418
+ --color-border: var(--color-foreground);
419
+ }
420
+ }
421
+ ```
422
+
423
+ ## Color Scheme
424
+
425
+ ```css
426
+ @media (prefers-color-scheme: dark) {
427
+ :root {
428
+ /* Dark mode tokens */
429
+ }
430
+ }
431
+ ```
432
+
433
+ Or explicitly with a `[data-theme="dark"]` selector for user-controlled toggle.
434
+
435
+ Use `color-scheme` to opt in to native UA dark mode controls (scrollbars, form controls):
436
+
437
+ ```css
438
+ :root { color-scheme: light dark; }
439
+ [data-theme="light"] { color-scheme: light; }
440
+ [data-theme="dark"] { color-scheme: dark; }
441
+ ```
442
+
443
+ ## Print
444
+
445
+ Content-rich surfaces may be printed. Add a minimal print stylesheet:
446
+
447
+ ```css
448
+ @media print {
449
+ nav, footer, .no-print { display: none; }
450
+ body { font-size: 12pt; color: black; background: white; }
451
+ a { color: black; text-decoration: underline; }
452
+ a[href]::after { content: " (" attr(href) ")"; }
453
+ }
454
+ ```
455
+
456
+ ## Touch and Pointer
457
+
458
+ Different input modalities have different needs:
459
+
460
+ ```css
461
+ /* Hover-capable pointer */
462
+ @media (hover: hover) and (pointer: fine) {
463
+ .card:hover { transform: translateY(-2px); }
464
+ }
465
+
466
+ /* Touch-only */
467
+ @media (hover: none) and (pointer: coarse) {
468
+ /* Larger hit targets, no hover-only affordances */
469
+ .icon-btn { padding: 12px; }
470
+ }
471
+ ```
472
+
473
+ Pointer types:
474
+
475
+ - `fine`: mouse, trackpad, stylus.
476
+ - `coarse`: touch.
477
+ - `hover` capability indicates the device can hover (hover-on-hold for touch is not real hover).
478
+
479
+ ## Common Responsive Mistakes
480
+
481
+ - One desktop-first stylesheet with `max-width` overrides that pile up.
482
+ - Fixed-pixel widths (`width: 1200px`) on containers.
483
+ - Images without `max-width: 100%`.
484
+ - Tables without horizontal scroll.
485
+ - Sidebars that don't collapse.
486
+ - Hero text that's readable on desktop and a wall of words on mobile.
487
+ - Buttons that are tiny on mobile.
488
+ - Inputs that auto-zoom on iOS because text size is below 16px.
489
+ - A `100vh` hero that overflows on iOS Safari.
490
+ - A surface that requires scrolling 12 screens on mobile while only 2 screens on desktop.
491
+ - Padding that's too tight on mobile (cramping content) or too generous on mobile (wasting space).
492
+ - Font that's too big on mobile (5 words per line) or too small (10pt body).
493
+
494
+ ## Self-Healing for Responsive
495
+
496
+ Before declaring work complete:
497
+
498
+ - [ ] Tested at 320px, 375px, 768px, 1024px, 1280px, 1920px
499
+ - [ ] Tested in portrait and landscape on a phone
500
+ - [ ] No horizontal scroll at any width
501
+ - [ ] Body text >= 16px on mobile
502
+ - [ ] Touch targets >= 44x44 with 8px gaps
503
+ - [ ] All images, videos, iframes have max-width: 100%
504
+ - [ ] Long tables have horizontal scroll
505
+ - [ ] Sidebar collapses on small screens
506
+ - [ ] Modals fit smallest viewport
507
+ - [ ] 200% browser zoom: layout intact
508
+ - [ ] System text size at largest: layout intact
509
+ - [ ] Safe area respected on notched/island devices
510
+ - [ ] `100dvh` instead of `100vh` for full-height mobile
511
+ - [ ] Works at `prefers-reduced-motion: reduce`
512
+ - [ ] Works at `prefers-color-scheme: dark`
513
+ - [ ] Tested with keyboard only
514
+
515
+ ## See Also
516
+
517
+ - [ui-ux.md](ui-ux.md) for the functional spacing/layout rules
518
+ - [design.md](design.md) for visual rhythm and grids
519
+ - [accessibility.md](accessibility.md) for accessible scaling