Claude Code Plugins

Community-maintained marketplace

Feedback

Mobile-First Responsive Design

@RomualdP/hoki
0
0

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".

Install Skill

1Download skill
2Enable skills in Claude

Open claude.ai/settings/capabilities and find the "Skills" section

3Upload to Claude

Click "Upload skill" and select the downloaded ZIP file

Note: Please verify skill by going through its instructions before using it.

SKILL.md

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 ?

  1. Performance: Charge le minimum nécessaire pour mobile
  2. Priorité au contenu: Force à identifier l'essentiel
  3. Progressive Enhancement: Ajoute des fonctionnalités pour écrans plus grands
  4. 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

  1. F12 → Toggle device toolbar
  2. Tester chaque breakpoint
  3. Vérifier touch targets (Show rulers)
  4. 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 sizes attribute
  • 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.