| name | Mobile-First Responsive Design |
| description | Approche Mobile-First avec Tailwind CSS, breakpoints, et progressive enhancement. MANDATORY pour toutes les pages. À utiliser lors de responsive design, layouts, ou quand l'utilisateur mentionne "mobile", "responsive", "tablet", "desktop". |
| allowed-tools | Read, Write, Edit |
Mobile-First Responsive Design
🎯 Mission
Implémenter une approche Mobile-First avec Tailwind CSS pour une expérience optimale sur tous les écrans.
📱 Philosophie Mobile-First
Mobile-First = Concevoir d'abord pour mobile, puis améliorer progressivement pour écrans plus grands.
Pourquoi Mobile-First ?
- ✅ Performance: Charge le minimum nécessaire pour mobile
- ✅ Priorité au contenu: Force à identifier l'essentiel
- ✅ Progressive Enhancement: Ajoute des fonctionnalités pour écrans plus grands
- ✅ Touch-First: Conçu pour le tactile, fonctionne au clavier/souris
Workflow Mobile-First
1. Design mobile (320-768px)
↓
2. Test sur mobile (iPhone, Android)
↓
3. Ajouter breakpoints tablet (md:)
↓
4. Ajouter breakpoints desktop (lg:, xl:)
↓
5. Test sur tous écrans
📐 Breakpoints Tailwind
// tailwind.config.ts
export default {
theme: {
screens: {
// Mobile-first approach
'sm': '640px', // @media (min-width: 640px)
'md': '768px', // Tablet
'lg': '1024px', // Desktop
'xl': '1280px', // Large desktop
'2xl': '1536px', // Extra large
},
},
};
Utilisation :
{/* Mobile par défaut (320-639px) */}
<div className="text-sm p-4 flex-col">
{/* Tablet (640px+) */}
<div className="sm:text-base sm:p-6">
{/* Desktop (1024px+) */}
<div className="lg:text-lg lg:p-8 lg:flex-row">
🎨 Patterns Mobile-First
Layout: Stack → Grid
// Mobile: Stack vertical
// Desktop: Grid 2 colonnes
<div className="flex flex-col gap-4 lg:grid lg:grid-cols-2 lg:gap-8">
<div>Column 1</div>
<div>Column 2</div>
</div>
Navigation: Burger → Horizontal
// components/Navigation.tsx
'use client';
import { useState } from 'react';
import { Menu, X } from 'lucide-react';
export function Navigation() {
const [isOpen, setIsOpen] = useState(false);
return (
<nav className="relative">
{/* Mobile: Burger button */}
<button
onClick={() => setIsOpen(!isOpen)}
className="lg:hidden p-2"
aria-label="Toggle menu"
>
{isOpen ? <X /> : <Menu />}
</button>
{/* Mobile: Full-screen menu */}
{isOpen && (
<div className="fixed inset-0 z-50 bg-background lg:hidden">
<div className="flex flex-col gap-4 p-6">
<a href="/dashboard" className="text-lg">Dashboard</a>
<a href="/teams" className="text-lg">Teams</a>
<a href="/matches" className="text-lg">Matches</a>
</div>
</div>
)}
{/* Desktop: Horizontal menu (always visible) */}
<div className="hidden lg:flex lg:gap-6">
<a href="/dashboard">Dashboard</a>
<a href="/teams">Teams</a>
<a href="/matches">Matches</a>
</div>
</nav>
);
}
Typography: Responsive Scales
<h1 className="text-2xl font-bold md:text-3xl lg:text-4xl">
Responsive Heading
</h1>
<p className="text-sm md:text-base lg:text-lg">
Responsive paragraph text.
</p>
Spacing: Progressive Enhancement
<div className="p-4 md:p-6 lg:p-8">
{/* Padding: 16px mobile → 24px tablet → 32px desktop */}
</div>
<div className="gap-2 md:gap-4 lg:gap-6">
{/* Gap: 8px → 16px → 24px */}
</div>
Images: Responsive & Optimized
import Image from 'next/image';
<div className="relative aspect-video w-full">
<Image
src="/club-photo.jpg"
alt="Club photo"
fill
className="object-cover"
sizes="(max-width: 768px) 100vw, (max-width: 1200px) 50vw, 33vw"
/>
</div>
Cards: Stack → Grid
// Mobile: 1 card full width
// Tablet: 2 cards per row
// Desktop: 3 cards per row
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3">
<ClubCard />
<ClubCard />
<ClubCard />
</div>
Sidebar: Hidden → Visible
// Mobile: Sidebar hidden, toggle avec button
// Desktop: Sidebar always visible
<div className="flex">
{/* Sidebar */}
<aside className="hidden w-64 border-r lg:block">
<nav className="p-6">
<a href="/settings">Settings</a>
<a href="/profile">Profile</a>
</nav>
</aside>
{/* Main content */}
<main className="flex-1 p-4 md:p-6 lg:p-8">
{/* Content */}
</main>
</div>
🖱️ Touch-Friendly Design
Hit Areas: Min 44x44px
// ❌ MAUVAIS - Trop petit pour tactile
<button className="p-1 text-xs">
Click
</button>
// ✅ BON - Min 44x44px
<button className="min-h-[44px] min-w-[44px] p-3">
Click
</button>
Spacing: Éviter zones trop proches
// ❌ MAUVAIS - Boutons trop proches
<div className="flex gap-1">
<button>Edit</button>
<button>Delete</button>
</div>
// ✅ BON - Spacing confortable
<div className="flex gap-4">
<button>Edit</button>
<button>Delete</button>
</div>
Hover: Utiliser @media (hover: hover)
// CSS avec hover conditionnel
<button className="bg-primary text-white hover:bg-primary/90 lg:hover:bg-primary/80">
Click
</button>
/* globals.css */
/* Hover uniquement sur devices avec souris */
@media (hover: hover) {
.hover\:bg-primary\/90:hover {
background-color: hsl(var(--primary) / 0.9);
}
}
📋 Patterns Complets
Dashboard Layout
// app/(dashboard)/coach/page.tsx
export default function CoachDashboard() {
return (
<div className="min-h-screen">
{/* Header */}
<header className="border-b p-4 md:p-6">
<h1 className="text-xl font-bold md:text-2xl lg:text-3xl">
Dashboard Coach
</h1>
</header>
{/* Main content */}
<main className="p-4 md:p-6 lg:p-8">
{/* Stats grid: 1 col mobile → 2 cols tablet → 4 cols desktop */}
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-4">
<StatCard title="Membres" value={42} />
<StatCard title="Équipes" value={3} />
<StatCard title="Matchs" value={12} />
<StatCard title="Victoires" value={8} />
</div>
{/* Content sections: Stack mobile → Side-by-side desktop */}
<div className="mt-8 flex flex-col gap-6 lg:flex-row lg:gap-8">
{/* Recent activity */}
<section className="flex-1">
<h2 className="mb-4 text-lg font-semibold md:text-xl">
Activité récente
</h2>
<ActivityList />
</section>
{/* Quick actions */}
<aside className="lg:w-80">
<h2 className="mb-4 text-lg font-semibold md:text-xl">
Actions rapides
</h2>
<QuickActions />
</aside>
</div>
</main>
</div>
);
}
Form Layout
// features/club-management/components/ClubCreationForm.tsx
'use client';
export function ClubCreationForm() {
return (
<form className="space-y-6">
{/* Form sections: Stack mobile → 2 cols desktop */}
<div className="grid grid-cols-1 gap-6 lg:grid-cols-2">
{/* Club info */}
<div className="space-y-4">
<h3 className="text-base font-semibold md:text-lg">
Informations du club
</h3>
<div>
<label className="text-sm font-medium">Nom du club</label>
<input
type="text"
className="mt-1 w-full rounded-md border p-3"
/>
</div>
<div>
<label className="text-sm font-medium">Description</label>
<textarea
rows={4}
className="mt-1 w-full rounded-md border p-3"
/>
</div>
</div>
{/* Contact info */}
<div className="space-y-4">
<h3 className="text-base font-semibold md:text-lg">
Contact
</h3>
<div>
<label className="text-sm font-medium">Email</label>
<input
type="email"
className="mt-1 w-full rounded-md border p-3"
/>
</div>
<div>
<label className="text-sm font-medium">Téléphone</label>
<input
type="tel"
className="mt-1 w-full rounded-md border p-3"
/>
</div>
</div>
</div>
{/* Actions: Stack mobile → Row desktop */}
<div className="flex flex-col gap-3 sm:flex-row sm:justify-end">
<button
type="button"
className="min-h-[44px] rounded-md border px-6 py-2"
>
Annuler
</button>
<button
type="submit"
className="min-h-[44px] rounded-md bg-primary px-6 py-2 text-white"
>
Créer le club
</button>
</div>
</form>
);
}
Modal: Full-screen mobile → Centered desktop
// components/ui/modal.tsx
'use client';
import { X } from 'lucide-react';
export function Modal({ children, onClose }: Props) {
return (
<div className="fixed inset-0 z-50 flex items-end justify-center bg-black/50 sm:items-center">
{/* Modal content */}
<div className="relative w-full rounded-t-2xl bg-white sm:max-w-lg sm:rounded-2xl">
{/* Close button */}
<button
onClick={onClose}
className="absolute right-4 top-4 min-h-[44px] min-w-[44px] p-2"
aria-label="Close"
>
<X />
</button>
{/* Content with responsive padding */}
<div className="p-6 sm:p-8">
{children}
</div>
</div>
</div>
);
}
Table: Responsive avec horizontal scroll
// components/MembersTable.tsx
export function MembersTable({ members }: Props) {
return (
<div className="overflow-x-auto">
<table className="w-full min-w-[600px]">
<thead>
<tr className="border-b">
<th className="p-3 text-left text-sm font-medium">Nom</th>
<th className="p-3 text-left text-sm font-medium">Email</th>
<th className="p-3 text-left text-sm font-medium">Rôle</th>
<th className="p-3 text-right text-sm font-medium">Actions</th>
</tr>
</thead>
<tbody>
{members.map((member) => (
<tr key={member.id} className="border-b">
<td className="p-3 text-sm">{member.name}</td>
<td className="p-3 text-sm">{member.email}</td>
<td className="p-3 text-sm">{member.role}</td>
<td className="p-3 text-right">
<button className="min-h-[44px] min-w-[44px] p-2">
Edit
</button>
</td>
</tr>
))}
</tbody>
</table>
</div>
);
}
🔍 Testing Responsive
Breakpoints à tester
📱 Mobile: 320px (iPhone SE), 375px (iPhone 12), 414px (iPhone 14 Pro Max)
📱 Tablet: 768px (iPad), 820px (iPad Air), 1024px (iPad Pro)
💻 Desktop: 1280px, 1440px, 1920px
Chrome DevTools
- F12 → Toggle device toolbar
- Tester chaque breakpoint
- Vérifier touch targets (Show rulers)
- Tester orientation portrait/landscape
Browser Testing
# Mobile
- Safari iOS (iPhone)
- Chrome Android
# Desktop
- Chrome, Firefox, Safari, Edge
✅ Checklist Mobile-First
- Design mobile AVANT desktop
- Classes Tailwind sans préfixe = mobile (base)
- Breakpoints progressifs (
sm:,md:,lg:) - Typography responsive (text-sm → text-base → text-lg)
- Spacing responsive (p-4 → p-6 → p-8)
- Images avec
sizesattribute - Touch targets min 44x44px
- Navigation: Burger mobile → Horizontal desktop
- Modals: Full-screen mobile → Centered desktop
- Tables: Horizontal scroll ou cards sur mobile
- Forms: Stack mobile → Grid desktop
- Testé sur vrais devices (iPhone, Android)
🚨 Erreurs Courantes
1. Desktop-First (MAUVAIS)
// ❌ MAUVAIS - Desktop par défaut, puis mobile
<div className="grid grid-cols-3 gap-8 md:grid-cols-1 md:gap-4">
// ✅ BON - Mobile par défaut, puis desktop
<div className="grid grid-cols-1 gap-4 lg:grid-cols-3 lg:gap-8">
2. Touch Targets Trop Petits
// ❌ MAUVAIS - 24x24px (trop petit)
<button className="p-1">
<Icon size={16} />
</button>
// ✅ BON - Min 44x44px
<button className="min-h-[44px] min-w-[44px] p-3">
<Icon size={20} />
</button>
3. Texte Trop Petit sur Mobile
// ❌ MAUVAIS
<p className="text-xs">Long paragraph...</p>
// ✅ BON - Lisible sur mobile
<p className="text-sm md:text-base">Long paragraph...</p>
4. Hover sur Mobile (Inutile)
// ❌ MAUVAIS - Hover sur mobile = pas d'effet
<button className="hover:bg-blue-500">Click</button>
// ✅ BON - Active pour tactile, Hover pour souris
<button className="active:bg-blue-500 lg:hover:bg-blue-500">
Click
</button>
5. Layout Cassé sur Petits Écrans
// ❌ MAUVAIS - Fixed width
<div className="w-[600px]">Content</div>
// ✅ BON - Responsive width
<div className="w-full max-w-2xl">Content</div>
📚 Skills Complémentaires
- atomic-component : Composants responsive
- suspense-streaming : Skeleton responsive
- view-transitions : Transitions smooth sur mobile
Rappel : TOUJOURS design mobile AVANT desktop = Meilleure UX et performance.