Responsive
Patterns responsive utilises dans Speedcube Master : approche mobile-first, breakpoints, layouts adaptatifs, scaling du texte et navigation conditionnelle.
Approche mobile-first
Tous les styles sont ecrits pour mobile en priorite (base), puis enrichis avec des prefixes de breakpoint pour les ecrans plus grands. On ne doit jamais ecrire de style desktop en base puis le surcharger pour mobile.
// Correct : mobile-first (base = mobile, sm/md/lg = enrichissements)
<div className="px-4 sm:px-6 lg:px-8">
<h1 className="text-xl sm:text-2xl lg:text-3xl">
Titre
</h1>
</div>
// Incorrect : desktop-first (surcouche mobile)
// Ne PAS faire ceci :
<div className="px-8 max-sm:px-4"> {/* Anti-pattern */}
<h1 className="text-3xl max-lg:text-xl"> {/* Anti-pattern */}
Titre
</h1>
</div>Reference des breakpoints
Tailwind CSS utilise des breakpoints min-width. Le style sans prefixe s'applique a toutes les tailles, et chaque prefixe s'active a partir de la largeur indiquee.
(base)0pxTous les ecrans (mobile en premier)xs375pxPetits mobiles (iPhone SE, etc.)sm640pxGrands mobiles / petites tablettesmd768pxTablettes (bascule sidebar/bottom nav)lg1024pxDesktop (3 colonnes, layout elargi)xl1280pxGrand desktop2xl1536pxTres grand ecran / ultra-wideStack vers grille
Le pattern le plus courant : les elements sont empiles verticalement sur mobile puis disposes en grille sur les ecrans plus larges.
// 1 colonne → 2 colonnes → 3 colonnes
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4">
<Card>Carte 1</Card>
<Card>Carte 2</Card>
<Card>Carte 3</Card>
</div>
// Flex column → row
<div className="flex flex-col sm:flex-row gap-4">
<div className="flex-1">Element 1</div>
<div className="flex-1">Element 2</div>
</div>
// Stats : 2 colonnes → 4 colonnes
<div className="grid grid-cols-2 lg:grid-cols-4 gap-4">
<StatCard label="PB" value="8.42" />
<StatCard label="Ao5" value="12.67" />
<StatCard label="Ao12" value="13.45" />
<StatCard label="Total" value="1,247" />
</div>
// Form layout : pleine largeur → 2 colonnes
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
<FormField name="firstName" />
<FormField name="lastName" />
<FormField name="email" className="md:col-span-2" />
</div>Afficher / Masquer
Certains elements sont affiches ou masques selon la taille de l'ecran. On utilise hidden pour masquer et le prefixe du breakpoint pour afficher.
// Visible uniquement sur mobile
<MobileBottomNav className="md:hidden" />
// Visible uniquement sur desktop
<Sidebar className="hidden md:flex" />
// Texte abrege sur mobile, complet sur desktop
<span className="sm:hidden">PB</span>
<span className="hidden sm:inline">Personal Best</span>
// Header simplifie sur mobile
<header className="flex items-center justify-between">
<Logo />
{/* Navigation complete sur desktop */}
<nav className="hidden md:flex items-center gap-4">
<NavLink href="/timer">Timer</NavLink>
<NavLink href="/algos">Algorithmes</NavLink>
<NavLink href="/learning">Apprentissage</NavLink>
<NavLink href="/dashboard">Dashboard</NavLink>
</nav>
{/* Menu hamburger sur mobile */}
<Button variant="ghost" size="icon" className="md:hidden">
<Menu className="h-5 w-5" />
</Button>
</header>
// Informations secondaires masquees sur mobile
<div className="flex items-center gap-4">
<span className="font-mono text-lg">{time}</span>
<span className="hidden sm:inline text-sm text-muted-foreground">
{scramble}
</span>
<span className="hidden lg:inline text-xs text-muted-foreground">
{date}
</span>
</div>Scaling du texte
Les tailles de texte augmentent progressivement sur les ecrans plus grands pour occuper l'espace de maniere proportionnelle.
// Titres de page
<h1 className="text-2xl sm:text-3xl lg:text-4xl font-bold">
Titre de la page
</h1>
// Sous-titres
<h2 className="text-lg sm:text-xl lg:text-2xl font-semibold">
Section
</h2>
// Timer (tres grand sur desktop)
<div className="text-4xl sm:text-5xl md:text-6xl lg:text-7xl font-mono font-bold">
12.34
</div>
// Texte de corps
<p className="text-sm sm:text-base">
Description du contenu
</p>
// Texte muted (labels, hints)
<span className="text-xs sm:text-sm text-muted-foreground">
Information secondaire
</span>Scaling du padding
Le padding augmente avec la taille de l'ecran pour eviter un contenu trop colle aux bords sur mobile tout en utilisant l'espace sur desktop.
// Container principal
<main className="max-w-6xl mx-auto px-4 sm:px-6 lg:px-8 py-6 sm:py-10">
{children}
</main>
// Cartes
<Card>
<CardContent className="p-4 sm:p-6">
{/* Contenu */}
</CardContent>
</Card>
// Sections de page
<section className="py-8 sm:py-12 lg:py-16">
{/* Contenu */}
</section>
// Modales
<DialogContent className="p-4 sm:p-6 max-w-[calc(100vw-2rem)] sm:max-w-lg">
{/* Contenu */}
</DialogContent>
// Hero section
<div className="px-4 py-12 sm:px-6 sm:py-16 lg:px-8 lg:py-24">
<h1 className="text-3xl sm:text-4xl lg:text-5xl">
Speedcube Master
</h1>
</div>Container
Le container principal utilise max-w-6xl (1152px) avec un centrage automatique et un padding horizontal responsive. Ce pattern est utilise sur toutes les pages publiques.
// Container standard
<div className="max-w-6xl mx-auto px-4 sm:px-6">
{children}
</div>
// Container etroit (formulaires, articles)
<div className="max-w-2xl mx-auto px-4 sm:px-6">
{children}
</div>
// Container pleine largeur avec padding (landing pages)
<div className="w-full px-4 sm:px-6 lg:px-8">
{children}
</div>
// Container responsive composant reutilisable
function Container({
children,
size = "default",
}: {
children: React.ReactNode;
size?: "narrow" | "default" | "wide" | "full";
}) {
const maxWidths = {
narrow: "max-w-2xl",
default: "max-w-6xl",
wide: "max-w-7xl",
full: "w-full",
};
return (
<div className={cn(
maxWidths[size],
"mx-auto px-4 sm:px-6",
size === "full" && "lg:px-8"
)}>
{children}
</div>
);
}Navigation adaptative
La navigation change de forme selon la taille de l'ecran. Sur mobile, c'est une barre en bas de l'ecran. Sur desktop, c'est un header ou une sidebar fixe.
// Pattern : bottom nav mobile ↔ sidebar desktop
export default function AppLayout({ children }: { children: React.ReactNode }) {
return (
<div className="min-h-screen">
{/* Header (toujours visible) */}
<header className="h-16 border-b border-border bg-card/95 backdrop-blur-md">
<div className="max-w-6xl mx-auto px-4 sm:px-6 h-full flex items-center">
<Logo />
{/* Nav desktop dans le header */}
<nav className="hidden md:flex items-center gap-6 ml-8">
<NavLink href="/timer">Timer</NavLink>
<NavLink href="/algos">Algos</NavLink>
</nav>
</div>
</header>
{/* Layout principal */}
<div className="flex">
{/* Sidebar desktop (dashboard uniquement) */}
<aside className="hidden md:flex w-64 flex-col border-r border-border
bg-card min-h-[calc(100vh-4rem)]">
<nav className="p-4 space-y-1">
<SidebarLink href="/dashboard" icon={Home}>Accueil</SidebarLink>
<SidebarLink href="/dashboard/stats" icon={BarChart}>Stats</SidebarLink>
</nav>
</aside>
{/* Contenu */}
<main className="flex-1 pb-20 md:pb-0"> {/* pb-20 pour la bottom nav */}
{children}
</main>
</div>
{/* Bottom nav mobile */}
<MobileBottomNav className="md:hidden" />
</div>
);
}Recapitulatif des patterns
Grillegrid-cols-1 sm:grid-cols-2 lg:grid-cols-3Containermax-w-6xl mx-auto px-4 sm:px-6Textetext-sm sm:text-base lg:text-lgPaddingp-4 sm:p-6 lg:p-8Masquerhidden sm:block ou md:hiddenDirectionflex-col sm:flex-rowNavBottom nav mobile (md:hidden) + sidebar desktop (hidden md:flex)