omgkit 2.2.0 → 2.3.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.
Files changed (60) hide show
  1. package/README.md +3 -3
  2. package/package.json +1 -1
  3. package/plugin/skills/databases/database-management/SKILL.md +288 -0
  4. package/plugin/skills/databases/database-migration/SKILL.md +285 -0
  5. package/plugin/skills/databases/database-schema-design/SKILL.md +195 -0
  6. package/plugin/skills/databases/mongodb/SKILL.md +60 -776
  7. package/plugin/skills/databases/prisma/SKILL.md +53 -744
  8. package/plugin/skills/databases/redis/SKILL.md +53 -860
  9. package/plugin/skills/databases/supabase/SKILL.md +283 -0
  10. package/plugin/skills/devops/aws/SKILL.md +68 -672
  11. package/plugin/skills/devops/github-actions/SKILL.md +54 -657
  12. package/plugin/skills/devops/kubernetes/SKILL.md +67 -602
  13. package/plugin/skills/devops/performance-profiling/SKILL.md +59 -863
  14. package/plugin/skills/frameworks/django/SKILL.md +87 -853
  15. package/plugin/skills/frameworks/express/SKILL.md +95 -1301
  16. package/plugin/skills/frameworks/fastapi/SKILL.md +90 -1198
  17. package/plugin/skills/frameworks/laravel/SKILL.md +87 -1187
  18. package/plugin/skills/frameworks/nestjs/SKILL.md +106 -973
  19. package/plugin/skills/frameworks/react/SKILL.md +94 -962
  20. package/plugin/skills/frameworks/vue/SKILL.md +95 -1242
  21. package/plugin/skills/frontend/accessibility/SKILL.md +91 -1056
  22. package/plugin/skills/frontend/frontend-design/SKILL.md +69 -1262
  23. package/plugin/skills/frontend/responsive/SKILL.md +76 -799
  24. package/plugin/skills/frontend/shadcn-ui/SKILL.md +73 -921
  25. package/plugin/skills/frontend/tailwindcss/SKILL.md +60 -788
  26. package/plugin/skills/frontend/threejs/SKILL.md +72 -1266
  27. package/plugin/skills/languages/javascript/SKILL.md +106 -849
  28. package/plugin/skills/methodology/brainstorming/SKILL.md +70 -576
  29. package/plugin/skills/methodology/defense-in-depth/SKILL.md +79 -831
  30. package/plugin/skills/methodology/dispatching-parallel-agents/SKILL.md +81 -654
  31. package/plugin/skills/methodology/executing-plans/SKILL.md +86 -529
  32. package/plugin/skills/methodology/finishing-development-branch/SKILL.md +95 -586
  33. package/plugin/skills/methodology/problem-solving/SKILL.md +67 -681
  34. package/plugin/skills/methodology/receiving-code-review/SKILL.md +70 -533
  35. package/plugin/skills/methodology/requesting-code-review/SKILL.md +70 -610
  36. package/plugin/skills/methodology/root-cause-tracing/SKILL.md +70 -646
  37. package/plugin/skills/methodology/sequential-thinking/SKILL.md +70 -478
  38. package/plugin/skills/methodology/systematic-debugging/SKILL.md +66 -559
  39. package/plugin/skills/methodology/test-driven-development/SKILL.md +91 -752
  40. package/plugin/skills/methodology/testing-anti-patterns/SKILL.md +78 -687
  41. package/plugin/skills/methodology/token-optimization/SKILL.md +72 -602
  42. package/plugin/skills/methodology/verification-before-completion/SKILL.md +108 -529
  43. package/plugin/skills/methodology/writing-plans/SKILL.md +79 -566
  44. package/plugin/skills/omega/omega-architecture/SKILL.md +91 -752
  45. package/plugin/skills/omega/omega-coding/SKILL.md +161 -552
  46. package/plugin/skills/omega/omega-sprint/SKILL.md +132 -777
  47. package/plugin/skills/omega/omega-testing/SKILL.md +157 -845
  48. package/plugin/skills/omega/omega-thinking/SKILL.md +165 -606
  49. package/plugin/skills/security/better-auth/SKILL.md +46 -1034
  50. package/plugin/skills/security/oauth/SKILL.md +80 -934
  51. package/plugin/skills/security/owasp/SKILL.md +78 -862
  52. package/plugin/skills/testing/playwright/SKILL.md +77 -700
  53. package/plugin/skills/testing/pytest/SKILL.md +73 -811
  54. package/plugin/skills/testing/vitest/SKILL.md +60 -920
  55. package/plugin/skills/tools/document-processing/SKILL.md +111 -838
  56. package/plugin/skills/tools/image-processing/SKILL.md +126 -659
  57. package/plugin/skills/tools/mcp-development/SKILL.md +85 -758
  58. package/plugin/skills/tools/media-processing/SKILL.md +118 -735
  59. package/plugin/stdrules/SKILL_STANDARDS.md +490 -0
  60. package/plugin/skills/SKILL_STANDARDS.md +0 -743
@@ -1,872 +1,149 @@
1
1
  ---
2
- name: responsive
3
- description: Responsive web design with mobile-first approach, fluid layouts, container queries, and cross-device testing
4
- category: frontend
5
- triggers:
6
- - responsive
7
- - responsive design
8
- - mobile first
9
- - adaptive layout
10
- - breakpoints
11
- - media queries
2
+ name: building-responsive-layouts
3
+ description: Claude builds responsive web layouts with mobile-first CSS, fluid typography, and container queries. Use when creating adaptive UIs that work across all device sizes.
12
4
  ---
13
5
 
14
- # Responsive Design
6
+ # Building Responsive Layouts
15
7
 
16
- Enterprise-grade **responsive web design** following industry best practices. This skill covers mobile-first development, fluid layouts, container queries, responsive images, and cross-device optimization patterns used by top engineering teams.
8
+ ## Quick Start
17
9
 
18
- ## Purpose
19
-
20
- Build seamless cross-device experiences:
21
-
22
- - Implement mobile-first design approach
23
- - Create fluid, flexible layouts
24
- - Use modern CSS layout techniques
25
- - Optimize images for all devices
26
- - Implement container queries
27
- - Test across devices and viewports
28
- - Handle touch and mouse interactions
29
-
30
- ## Features
31
-
32
- ### 1. Mobile-First Breakpoints
33
-
34
- ```css
35
- /* Base styles (mobile-first) */
36
- :root {
37
- /* Breakpoint values */
38
- --breakpoint-sm: 640px;
39
- --breakpoint-md: 768px;
40
- --breakpoint-lg: 1024px;
41
- --breakpoint-xl: 1280px;
42
- --breakpoint-2xl: 1536px;
43
- }
44
-
45
- /* Mobile base styles */
46
- .container {
47
- width: 100%;
48
- padding-left: 1rem;
49
- padding-right: 1rem;
50
- margin-left: auto;
51
- margin-right: auto;
52
- }
53
-
54
- /* Small devices (640px and up) */
55
- @media (min-width: 640px) {
56
- .container {
57
- max-width: 640px;
58
- padding-left: 1.5rem;
59
- padding-right: 1.5rem;
60
- }
61
- }
62
-
63
- /* Medium devices (768px and up) */
64
- @media (min-width: 768px) {
65
- .container {
66
- max-width: 768px;
67
- }
68
- }
69
-
70
- /* Large devices (1024px and up) */
71
- @media (min-width: 1024px) {
72
- .container {
73
- max-width: 1024px;
74
- padding-left: 2rem;
75
- padding-right: 2rem;
76
- }
77
- }
78
-
79
- /* Extra large devices (1280px and up) */
80
- @media (min-width: 1280px) {
81
- .container {
82
- max-width: 1280px;
83
- }
10
+ ```tsx
11
+ // Responsive grid with auto-fit
12
+ export function ResponsiveGrid({ children, minWidth = '300px' }: GridProps) {
13
+ return (
14
+ <div style={{
15
+ display: 'grid',
16
+ gridTemplateColumns: `repeat(auto-fit, minmax(min(${minWidth}, 100%), 1fr))`,
17
+ gap: '1.5rem'
18
+ }}>
19
+ {children}
20
+ </div>
21
+ );
84
22
  }
85
23
 
86
- /* 2XL devices (1536px and up) */
87
- @media (min-width: 1536px) {
88
- .container {
89
- max-width: 1536px;
90
- }
91
- }
24
+ // With Tailwind
25
+ <div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-4 md:gap-6">
26
+ {items.map(item => <Card key={item.id} {...item} />)}
27
+ </div>
92
28
  ```
93
29
 
94
- ```typescript
95
- // hooks/useBreakpoint.ts
96
- import { useState, useEffect } from "react";
97
-
98
- const breakpoints = {
99
- sm: 640,
100
- md: 768,
101
- lg: 1024,
102
- xl: 1280,
103
- "2xl": 1536,
104
- } as const;
105
-
106
- type Breakpoint = keyof typeof breakpoints;
107
-
108
- export function useBreakpoint() {
109
- const [breakpoint, setBreakpoint] = useState<Breakpoint | null>(null);
110
- const [width, setWidth] = useState(0);
111
-
112
- useEffect(() => {
113
- const handleResize = () => {
114
- const w = window.innerWidth;
115
- setWidth(w);
116
-
117
- if (w >= breakpoints["2xl"]) setBreakpoint("2xl");
118
- else if (w >= breakpoints.xl) setBreakpoint("xl");
119
- else if (w >= breakpoints.lg) setBreakpoint("lg");
120
- else if (w >= breakpoints.md) setBreakpoint("md");
121
- else if (w >= breakpoints.sm) setBreakpoint("sm");
122
- else setBreakpoint(null);
123
- };
124
-
125
- handleResize();
126
- window.addEventListener("resize", handleResize);
127
- return () => window.removeEventListener("resize", handleResize);
128
- }, []);
30
+ ## Features
129
31
 
130
- const isAbove = (bp: Breakpoint) => width >= breakpoints[bp];
131
- const isBelow = (bp: Breakpoint) => width < breakpoints[bp];
32
+ | Feature | Description | Guide |
33
+ |---------|-------------|-------|
34
+ | Mobile-First Breakpoints | sm(640), md(768), lg(1024), xl(1280), 2xl(1536) | `ref/breakpoints.md` |
35
+ | Fluid Typography | `clamp()` for responsive font sizes | `ref/fluid-type.md` |
36
+ | Container Queries | Component-level responsive design | `ref/container-queries.md` |
37
+ | Responsive Images | srcset, sizes, and art direction | `ref/images.md` |
38
+ | Touch-Friendly | 44px minimum targets, hover vs touch handling | `ref/touch.md` |
39
+ | Safe Areas | Handle notched devices and dynamic viewports | `ref/safe-areas.md` |
132
40
 
133
- return { breakpoint, width, isAbove, isBelow };
134
- }
41
+ ## Common Patterns
135
42
 
136
- // hooks/useMediaQuery.ts
137
- import { useState, useEffect } from "react";
43
+ ### useMediaQuery Hook
138
44
 
45
+ ```tsx
139
46
  export function useMediaQuery(query: string): boolean {
140
47
  const [matches, setMatches] = useState(false);
141
48
 
142
49
  useEffect(() => {
143
50
  const media = window.matchMedia(query);
144
- if (media.matches !== matches) {
145
- setMatches(media.matches);
146
- }
147
-
51
+ setMatches(media.matches);
148
52
  const listener = (e: MediaQueryListEvent) => setMatches(e.matches);
149
- media.addEventListener("change", listener);
150
- return () => media.removeEventListener("change", listener);
151
- }, [matches, query]);
53
+ media.addEventListener('change', listener);
54
+ return () => media.removeEventListener('change', listener);
55
+ }, [query]);
152
56
 
153
57
  return matches;
154
58
  }
155
59
 
156
60
  // Usage
157
- function Component() {
158
- const isMobile = useMediaQuery("(max-width: 639px)");
159
- const isTablet = useMediaQuery("(min-width: 640px) and (max-width: 1023px)");
160
- const isDesktop = useMediaQuery("(min-width: 1024px)");
161
- const prefersDark = useMediaQuery("(prefers-color-scheme: dark)");
162
- const prefersReducedMotion = useMediaQuery("(prefers-reduced-motion: reduce)");
163
-
164
- return (
165
- <div>
166
- {isMobile && <MobileLayout />}
167
- {isTablet && <TabletLayout />}
168
- {isDesktop && <DesktopLayout />}
169
- </div>
170
- );
171
- }
172
- ```
173
-
174
- ### 2. Fluid Typography
175
-
176
- ```css
177
- /* Fluid typography using clamp() */
178
- :root {
179
- /* Base font sizes */
180
- --font-size-xs: clamp(0.75rem, 0.7rem + 0.25vw, 0.875rem);
181
- --font-size-sm: clamp(0.875rem, 0.8rem + 0.35vw, 1rem);
182
- --font-size-base: clamp(1rem, 0.9rem + 0.5vw, 1.125rem);
183
- --font-size-lg: clamp(1.125rem, 1rem + 0.6vw, 1.25rem);
184
- --font-size-xl: clamp(1.25rem, 1.1rem + 0.75vw, 1.5rem);
185
- --font-size-2xl: clamp(1.5rem, 1.25rem + 1.25vw, 2rem);
186
- --font-size-3xl: clamp(1.875rem, 1.5rem + 1.875vw, 2.5rem);
187
- --font-size-4xl: clamp(2.25rem, 1.75rem + 2.5vw, 3rem);
188
- --font-size-5xl: clamp(3rem, 2rem + 5vw, 4rem);
189
-
190
- /* Fluid spacing */
191
- --space-xs: clamp(0.25rem, 0.2rem + 0.25vw, 0.5rem);
192
- --space-sm: clamp(0.5rem, 0.4rem + 0.5vw, 0.75rem);
193
- --space-md: clamp(1rem, 0.8rem + 1vw, 1.5rem);
194
- --space-lg: clamp(1.5rem, 1.2rem + 1.5vw, 2.5rem);
195
- --space-xl: clamp(2rem, 1.5rem + 2.5vw, 4rem);
196
- --space-2xl: clamp(3rem, 2rem + 5vw, 6rem);
197
- }
198
-
199
- /* Typography scale */
200
- h1 {
201
- font-size: var(--font-size-4xl);
202
- line-height: 1.1;
203
- letter-spacing: -0.02em;
204
- }
205
-
206
- h2 {
207
- font-size: var(--font-size-3xl);
208
- line-height: 1.2;
209
- letter-spacing: -0.01em;
210
- }
211
-
212
- h3 {
213
- font-size: var(--font-size-2xl);
214
- line-height: 1.3;
215
- }
216
-
217
- h4 {
218
- font-size: var(--font-size-xl);
219
- line-height: 1.4;
220
- }
221
-
222
- p {
223
- font-size: var(--font-size-base);
224
- line-height: 1.6;
225
- }
226
-
227
- /* Responsive text utilities */
228
- .text-balance {
229
- text-wrap: balance;
230
- }
231
-
232
- .text-pretty {
233
- text-wrap: pretty;
234
- }
235
- ```
236
-
237
- ### 3. Flexible Grid Layouts
238
-
239
- ```css
240
- /* Auto-fit responsive grid */
241
- .auto-grid {
242
- display: grid;
243
- grid-template-columns: repeat(auto-fit, minmax(min(300px, 100%), 1fr));
244
- gap: var(--space-md);
245
- }
246
-
247
- /* Auto-fill variant */
248
- .auto-fill-grid {
249
- display: grid;
250
- grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
251
- gap: var(--space-md);
252
- }
253
-
254
- /* Sidebar layout */
255
- .sidebar-layout {
256
- display: grid;
257
- grid-template-columns: 1fr;
258
- gap: var(--space-lg);
259
- }
260
-
261
- @media (min-width: 768px) {
262
- .sidebar-layout {
263
- grid-template-columns: minmax(200px, 25%) 1fr;
264
- }
265
- }
266
-
267
- /* Holy grail layout */
268
- .holy-grail {
269
- display: grid;
270
- grid-template-rows: auto 1fr auto;
271
- min-height: 100vh;
272
- }
273
-
274
- .holy-grail-main {
275
- display: grid;
276
- grid-template-columns: 1fr;
277
- gap: var(--space-md);
278
- }
279
-
280
- @media (min-width: 768px) {
281
- .holy-grail-main {
282
- grid-template-columns: minmax(150px, 20%) 1fr;
283
- }
284
- }
285
-
286
- @media (min-width: 1024px) {
287
- .holy-grail-main {
288
- grid-template-columns: minmax(200px, 20%) 1fr minmax(200px, 20%);
289
- }
290
- }
291
-
292
- /* Masonry-like layout with CSS Grid */
293
- .masonry-grid {
294
- display: grid;
295
- grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
296
- grid-auto-rows: 10px;
297
- gap: var(--space-md);
298
- }
299
-
300
- /* Card spans based on content */
301
- .masonry-item--small {
302
- grid-row: span 20;
303
- }
304
-
305
- .masonry-item--medium {
306
- grid-row: span 30;
307
- }
308
-
309
- .masonry-item--large {
310
- grid-row: span 40;
311
- }
312
- ```
313
-
314
- ```tsx
315
- // Responsive grid component
316
- interface ResponsiveGridProps {
317
- children: React.ReactNode;
318
- minWidth?: string;
319
- gap?: string;
320
- className?: string;
321
- }
322
-
323
- export function ResponsiveGrid({
324
- children,
325
- minWidth = "300px",
326
- gap = "1rem",
327
- className = "",
328
- }: ResponsiveGridProps) {
329
- return (
330
- <div
331
- className={className}
332
- style={{
333
- display: "grid",
334
- gridTemplateColumns: `repeat(auto-fit, minmax(min(${minWidth}, 100%), 1fr))`,
335
- gap,
336
- }}
337
- >
338
- {children}
339
- </div>
340
- );
341
- }
342
-
343
- // Usage
344
- function ProductGrid() {
345
- return (
346
- <ResponsiveGrid minWidth="280px" gap="1.5rem">
347
- {products.map((product) => (
348
- <ProductCard key={product.id} product={product} />
349
- ))}
350
- </ResponsiveGrid>
351
- );
352
- }
61
+ const isMobile = useMediaQuery('(max-width: 639px)');
62
+ const prefersReducedMotion = useMediaQuery('(prefers-reduced-motion: reduce)');
353
63
  ```
354
64
 
355
- ### 4. Container Queries
65
+ ### Container Query Component
356
66
 
357
67
  ```css
358
- /* Container query setup */
359
68
  .card-container {
360
69
  container-type: inline-size;
361
- container-name: card;
362
70
  }
363
71
 
364
- /* Base card styles (smallest size) */
365
72
  .card {
366
73
  display: flex;
367
74
  flex-direction: column;
368
- padding: 1rem;
369
75
  }
370
76
 
371
- .card-image {
372
- width: 100%;
373
- aspect-ratio: 16/9;
374
- object-fit: cover;
375
- }
376
-
377
- .card-content {
378
- padding: 1rem 0;
379
- }
380
-
381
- /* Card layout changes based on container width */
382
- @container card (min-width: 400px) {
77
+ @container (min-width: 400px) {
383
78
  .card {
384
79
  flex-direction: row;
385
80
  gap: 1rem;
386
81
  }
387
-
388
- .card-image {
389
- width: 40%;
390
- aspect-ratio: 1;
391
- }
392
-
393
- .card-content {
394
- flex: 1;
395
- display: flex;
396
- flex-direction: column;
397
- justify-content: center;
398
- }
399
- }
400
-
401
- @container card (min-width: 600px) {
402
- .card {
403
- padding: 1.5rem;
404
- }
405
-
406
- .card-image {
407
- width: 35%;
408
- }
409
-
410
- .card-title {
411
- font-size: 1.5rem;
412
- }
413
- }
414
-
415
- /* Container query units */
416
- .responsive-text {
417
- font-size: clamp(1rem, 3cqi, 1.5rem);
418
- padding: 2cqi;
419
- }
420
-
421
- /* Style queries (experimental) */
422
- @container style(--theme: dark) {
423
- .card {
424
- background: #1a1a1a;
425
- color: white;
426
- }
82
+ .card-image { width: 40%; }
427
83
  }
428
84
  ```
429
85
 
430
86
  ```tsx
431
- // React component with container queries
432
- export function ContainerQueryCard({ title, description, image }: CardProps) {
87
+ export function ResponsiveCard({ image, title, content }: CardProps) {
433
88
  return (
434
89
  <div className="card-container">
435
90
  <article className="card">
436
91
  <img src={image} alt="" className="card-image" />
437
92
  <div className="card-content">
438
- <h3 className="card-title">{title}</h3>
439
- <p className="card-description">{description}</p>
93
+ <h3>{title}</h3>
94
+ <p>{content}</p>
440
95
  </div>
441
96
  </article>
442
97
  </div>
443
98
  );
444
99
  }
445
-
446
- // CSS Module with container queries
447
- // Card.module.css
448
- /*
449
- .container {
450
- container-type: inline-size;
451
- }
452
-
453
- .card {
454
- display: grid;
455
- gap: 1rem;
456
- }
457
-
458
- @container (min-width: 30rem) {
459
- .card {
460
- grid-template-columns: 1fr 2fr;
461
- }
462
- }
463
- */
464
- ```
465
-
466
- ### 5. Responsive Images
467
-
468
- ```tsx
469
- // Responsive image component
470
- interface ResponsiveImageProps {
471
- src: string;
472
- alt: string;
473
- sizes?: string;
474
- className?: string;
475
- priority?: boolean;
476
- }
477
-
478
- export function ResponsiveImage({
479
- src,
480
- alt,
481
- sizes = "100vw",
482
- className,
483
- priority = false,
484
- }: ResponsiveImageProps) {
485
- // Generate srcset for different sizes
486
- const widths = [320, 640, 768, 1024, 1280, 1536, 1920];
487
- const srcSet = widths
488
- .map((w) => `${src}?w=${w} ${w}w`)
489
- .join(", ");
490
-
491
- return (
492
- <img
493
- src={src}
494
- srcSet={srcSet}
495
- sizes={sizes}
496
- alt={alt}
497
- className={className}
498
- loading={priority ? "eager" : "lazy"}
499
- decoding={priority ? "sync" : "async"}
500
- />
501
- );
502
- }
503
-
504
- // Picture element for art direction
505
- export function ArtDirectedImage({
506
- mobileSrc,
507
- tabletSrc,
508
- desktopSrc,
509
- alt,
510
- }: {
511
- mobileSrc: string;
512
- tabletSrc: string;
513
- desktopSrc: string;
514
- alt: string;
515
- }) {
516
- return (
517
- <picture>
518
- {/* Desktop - landscape image */}
519
- <source media="(min-width: 1024px)" srcSet={desktopSrc} />
520
- {/* Tablet - square image */}
521
- <source media="(min-width: 640px)" srcSet={tabletSrc} />
522
- {/* Mobile - portrait image (default) */}
523
- <img src={mobileSrc} alt={alt} loading="lazy" />
524
- </picture>
525
- );
526
- }
527
-
528
- // Background image with responsive behavior
529
- export function ResponsiveHero({ children }: { children: React.ReactNode }) {
530
- return (
531
- <section
532
- className="
533
- relative min-h-[50vh] md:min-h-[60vh] lg:min-h-[70vh]
534
- bg-cover bg-center bg-no-repeat
535
- bg-[image:var(--mobile-bg)]
536
- md:bg-[image:var(--tablet-bg)]
537
- lg:bg-[image:var(--desktop-bg)]
538
- "
539
- style={{
540
- "--mobile-bg": "url('/hero-mobile.jpg')",
541
- "--tablet-bg": "url('/hero-tablet.jpg')",
542
- "--desktop-bg": "url('/hero-desktop.jpg')",
543
- } as React.CSSProperties}
544
- >
545
- <div className="absolute inset-0 bg-black/50" />
546
- <div className="relative z-10 flex items-center justify-center h-full">
547
- {children}
548
- </div>
549
- </section>
550
- );
551
- }
552
- ```
553
-
554
- ### 6. Touch-Friendly Design
555
-
556
- ```css
557
- /* Touch target sizes (minimum 44x44px) */
558
- .touch-target {
559
- min-width: 44px;
560
- min-height: 44px;
561
- display: flex;
562
- align-items: center;
563
- justify-content: center;
564
- }
565
-
566
- /* Larger touch targets on mobile */
567
- @media (pointer: coarse) {
568
- .button,
569
- .link,
570
- .interactive {
571
- min-height: 48px;
572
- padding: 12px 16px;
573
- }
574
-
575
- /* Increase spacing between interactive elements */
576
- .button-group {
577
- gap: 12px;
578
- }
579
-
580
- /* Larger form inputs */
581
- input,
582
- select,
583
- textarea {
584
- min-height: 48px;
585
- font-size: 16px; /* Prevents iOS zoom on focus */
586
- }
587
- }
588
-
589
- /* Hover only on devices that support it */
590
- @media (hover: hover) {
591
- .card:hover {
592
- transform: translateY(-4px);
593
- box-shadow: 0 12px 24px rgba(0, 0, 0, 0.15);
594
- }
595
-
596
- .button:hover {
597
- background-color: var(--color-primary-dark);
598
- }
599
- }
600
-
601
- /* Active states for touch devices */
602
- @media (hover: none) {
603
- .card:active {
604
- transform: scale(0.98);
605
- }
606
-
607
- .button:active {
608
- background-color: var(--color-primary-dark);
609
- }
610
- }
611
-
612
- /* Disable hover effects on touch */
613
- @media (hover: none) and (pointer: coarse) {
614
- .hover-effect {
615
- transform: none !important;
616
- }
617
- }
618
-
619
- /* Safe area insets for notched devices */
620
- .safe-area-padding {
621
- padding-left: env(safe-area-inset-left);
622
- padding-right: env(safe-area-inset-right);
623
- padding-bottom: env(safe-area-inset-bottom);
624
- }
625
-
626
- .fixed-bottom-nav {
627
- position: fixed;
628
- bottom: 0;
629
- left: 0;
630
- right: 0;
631
- padding-bottom: calc(1rem + env(safe-area-inset-bottom));
632
- }
633
- ```
634
-
635
- ```tsx
636
- // Touch-friendly button component
637
- export function TouchButton({
638
- children,
639
- onClick,
640
- ...props
641
- }: React.ButtonHTMLAttributes<HTMLButtonElement>) {
642
- return (
643
- <button
644
- onClick={onClick}
645
- className="
646
- min-h-[44px] min-w-[44px]
647
- px-4 py-2
648
- touch-manipulation
649
- select-none
650
- active:scale-95
651
- transition-transform duration-150
652
- "
653
- {...props}
654
- >
655
- {children}
656
- </button>
657
- );
658
- }
659
-
660
- // Swipe gesture hook
661
- import { useState, useRef } from "react";
662
-
663
- interface SwipeHandlers {
664
- onSwipeLeft?: () => void;
665
- onSwipeRight?: () => void;
666
- onSwipeUp?: () => void;
667
- onSwipeDown?: () => void;
668
- }
669
-
670
- export function useSwipe(handlers: SwipeHandlers, threshold = 50) {
671
- const touchStart = useRef<{ x: number; y: number } | null>(null);
672
-
673
- const onTouchStart = (e: React.TouchEvent) => {
674
- touchStart.current = {
675
- x: e.touches[0].clientX,
676
- y: e.touches[0].clientY,
677
- };
678
- };
679
-
680
- const onTouchEnd = (e: React.TouchEvent) => {
681
- if (!touchStart.current) return;
682
-
683
- const deltaX = e.changedTouches[0].clientX - touchStart.current.x;
684
- const deltaY = e.changedTouches[0].clientY - touchStart.current.y;
685
-
686
- if (Math.abs(deltaX) > Math.abs(deltaY)) {
687
- if (deltaX > threshold) handlers.onSwipeRight?.();
688
- if (deltaX < -threshold) handlers.onSwipeLeft?.();
689
- } else {
690
- if (deltaY > threshold) handlers.onSwipeDown?.();
691
- if (deltaY < -threshold) handlers.onSwipeUp?.();
692
- }
693
-
694
- touchStart.current = null;
695
- };
696
-
697
- return { onTouchStart, onTouchEnd };
698
- }
699
100
  ```
700
101
 
701
- ### 7. Responsive Navigation
102
+ ### Responsive Navigation
702
103
 
703
104
  ```tsx
704
- // Responsive navigation component
705
- import { useState } from "react";
706
-
707
105
  export function ResponsiveNav() {
708
106
  const [isOpen, setIsOpen] = useState(false);
709
107
 
710
108
  return (
711
- <nav className="relative bg-white shadow-sm">
712
- <div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
713
- <div className="flex justify-between h-16">
714
- {/* Logo */}
715
- <div className="flex-shrink-0 flex items-center">
716
- <Logo />
717
- </div>
718
-
719
- {/* Desktop navigation */}
720
- <div className="hidden md:flex md:items-center md:space-x-8">
721
- <NavLink href="/">Home</NavLink>
722
- <NavLink href="/products">Products</NavLink>
723
- <NavLink href="/about">About</NavLink>
724
- <NavLink href="/contact">Contact</NavLink>
725
- </div>
726
-
727
- {/* Mobile menu button */}
728
- <div className="flex items-center md:hidden">
729
- <button
730
- onClick={() => setIsOpen(!isOpen)}
731
- className="
732
- inline-flex items-center justify-center
733
- p-2 rounded-md
734
- text-gray-600 hover:text-gray-900 hover:bg-gray-100
735
- focus:outline-none focus:ring-2 focus:ring-inset focus:ring-blue-500
736
- "
737
- aria-expanded={isOpen}
738
- aria-controls="mobile-menu"
739
- >
740
- <span className="sr-only">
741
- {isOpen ? "Close menu" : "Open menu"}
742
- </span>
743
- {isOpen ? (
744
- <XIcon className="h-6 w-6" />
745
- ) : (
746
- <MenuIcon className="h-6 w-6" />
747
- )}
748
- </button>
749
- </div>
109
+ <nav className="relative">
110
+ <div className="flex justify-between items-center h-16 px-4">
111
+ <Logo />
112
+ {/* Desktop nav */}
113
+ <div className="hidden md:flex items-center space-x-8">
114
+ <NavLink href="/">Home</NavLink>
115
+ <NavLink href="/about">About</NavLink>
750
116
  </div>
117
+ {/* Mobile menu button */}
118
+ <button
119
+ className="md:hidden p-2 min-h-[44px] min-w-[44px]"
120
+ onClick={() => setIsOpen(!isOpen)}
121
+ aria-expanded={isOpen}
122
+ >
123
+ <span className="sr-only">{isOpen ? 'Close' : 'Open'} menu</span>
124
+ {isOpen ? <XIcon /> : <MenuIcon />}
125
+ </button>
751
126
  </div>
752
-
753
- {/* Mobile menu */}
754
- <div
755
- id="mobile-menu"
756
- className={`
757
- md:hidden
758
- ${isOpen ? "block" : "hidden"}
759
- `}
760
- >
761
- <div className="px-2 pt-2 pb-3 space-y-1 bg-white border-t">
762
- <MobileNavLink href="/" onClick={() => setIsOpen(false)}>
763
- Home
764
- </MobileNavLink>
765
- <MobileNavLink href="/products" onClick={() => setIsOpen(false)}>
766
- Products
767
- </MobileNavLink>
768
- <MobileNavLink href="/about" onClick={() => setIsOpen(false)}>
769
- About
770
- </MobileNavLink>
771
- <MobileNavLink href="/contact" onClick={() => setIsOpen(false)}>
772
- Contact
773
- </MobileNavLink>
127
+ {/* Mobile nav */}
128
+ {isOpen && (
129
+ <div className="md:hidden px-2 pb-3 space-y-1">
130
+ <MobileNavLink href="/" onClick={() => setIsOpen(false)}>Home</MobileNavLink>
131
+ <MobileNavLink href="/about" onClick={() => setIsOpen(false)}>About</MobileNavLink>
774
132
  </div>
775
- </div>
133
+ )}
776
134
  </nav>
777
135
  );
778
136
  }
779
-
780
- function MobileNavLink({
781
- href,
782
- children,
783
- onClick,
784
- }: {
785
- href: string;
786
- children: React.ReactNode;
787
- onClick: () => void;
788
- }) {
789
- return (
790
- <a
791
- href={href}
792
- onClick={onClick}
793
- className="
794
- block px-3 py-2 rounded-md
795
- text-base font-medium
796
- text-gray-700 hover:text-gray-900 hover:bg-gray-50
797
- min-h-[44px] flex items-center
798
- "
799
- >
800
- {children}
801
- </a>
802
- );
803
- }
804
- ```
805
-
806
- ## Use Cases
807
-
808
- ### Responsive Card Layout
809
-
810
- ```tsx
811
- export function ResponsiveCardGrid() {
812
- return (
813
- <div
814
- className="
815
- grid gap-4 sm:gap-6
816
- grid-cols-1
817
- sm:grid-cols-2
818
- lg:grid-cols-3
819
- xl:grid-cols-4
820
- "
821
- >
822
- {cards.map((card) => (
823
- <Card key={card.id} className="h-full">
824
- <CardImage src={card.image} alt={card.title} />
825
- <CardContent>
826
- <CardTitle className="line-clamp-2">{card.title}</CardTitle>
827
- <CardDescription className="line-clamp-3">
828
- {card.description}
829
- </CardDescription>
830
- </CardContent>
831
- </Card>
832
- ))}
833
- </div>
834
- );
835
- }
836
137
  ```
837
138
 
838
139
  ## Best Practices
839
140
 
840
- ### Do's
841
-
842
- - Start with mobile-first CSS
843
- - Use relative units (rem, em, %)
844
- - Test on real devices
845
- - Use semantic HTML
846
- - Implement touch-friendly targets
847
- - Consider reduced motion preferences
848
- - Use container queries for components
849
- - Optimize images for different sizes
850
- - Test across browsers
851
- - Use CSS logical properties
852
-
853
- ### Don'ts
854
-
855
- - Don't hide content on mobile unnecessarily
856
- - Don't use fixed widths
857
- - Don't rely only on hover states
858
- - Don't use small touch targets
859
- - Don't ignore landscape orientation
860
- - Don't skip accessibility testing
861
- - Don't use device-specific breakpoints
862
- - Don't forget keyboard navigation
863
- - Don't ignore safe area insets
864
- - Don't assume mouse input
865
-
866
- ## References
867
-
868
- - [CSS Media Queries](https://developer.mozilla.org/en-US/docs/Web/CSS/Media_Queries)
869
- - [Container Queries](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Container_Queries)
870
- - [Responsive Images](https://developer.mozilla.org/en-US/docs/Learn/HTML/Multimedia_and_embedding/Responsive_images)
871
- - [Touch Events](https://developer.mozilla.org/en-US/docs/Web/API/Touch_events)
872
- - [Viewport Concepts](https://developer.mozilla.org/en-US/docs/Web/CSS/Viewport_concepts)
141
+ | Do | Avoid |
142
+ |----|-------|
143
+ | Start with mobile-first CSS | Hiding essential content on mobile |
144
+ | Use relative units (rem, %, vw) | Fixed pixel widths |
145
+ | Test on real devices | Relying only on hover states |
146
+ | Use 44px minimum touch targets | Small touch targets on mobile |
147
+ | Consider reduced motion preferences | Ignoring landscape orientation |
148
+ | Use CSS logical properties (inline, block) | Device-specific breakpoints |
149
+ | Handle safe-area-inset for notched devices | Assuming mouse input |