DS

Dark Mode

Speedcube Master adopte un design dark-first. Le theme sombre est le theme par defaut, et le mode clair est une alternative activable.

Configuration avec next-themes

Le theming est gere par next-themes qui ajoute une classe sur l'element <html>. Le theme par defaut est dark.

// app/layout.tsx
import { ThemeProvider } from "next-themes";

export default function RootLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  return (
    <html lang="fr" suppressHydrationMismatch>
      <body>
        <ThemeProvider
          attribute="class"
          defaultTheme="dark"
          enableSystem={false}
          disableTransitionOnChange
        >
          {children}
        </ThemeProvider>
      </body>
    </html>
  );
}

Variables CSS (tokens)

Les couleurs sont definies comme variables CSS dans le fichier de tokens. Le theme dark est defini sur :root (par defaut), et le theme light est applique via la classe .light.

/* tokens.css - Dark-first approach */

/* Theme sombre = defaut (pas besoin de .dark) */
:root {
  --background: oklch(0.141 0.024 259.1);   /* #0B0F1A */
  --foreground: oklch(0.906 0.011 252.9);   /* #E5E7EB */
  --card: oklch(0.168 0.026 261.7);         /* #0F1524 */
  --card-foreground: oklch(0.906 0.011 252.9);
  --primary: oklch(0.546 0.215 264.1);      /* #2563EB */
  --primary-foreground: oklch(1 0 0);       /* #FFFFFF */
  --border: oklch(0.269 0.015 252.9);       /* #1F2937 */
  --muted: oklch(0.21 0.017 255.9);         /* #111827 */
  --muted-foreground: oklch(0.656 0.014 253.8); /* #9CA3AF */
  --accent: oklch(0.696 0.17 149.6);        /* #22C55E */
  --destructive: oklch(0.577 0.215 27.3);   /* #EF4444 */
  --warning: oklch(0.728 0.161 72.7);       /* #F59E0B */
  --ring: oklch(0.546 0.215 264.1);
}

/* Theme clair = override via .light */
.light {
  --background: oklch(0.967 0.006 264.5);   /* #F4F6FA */
  --foreground: oklch(0.145 0 0);           /* #171717 */
  --card: oklch(0.978 0.004 264.4);         /* #F8FAFC */
  --card-foreground: oklch(0.145 0 0);
  --border: oklch(0.906 0.013 255.5);       /* #E2E8F0 */
  --muted: oklch(0.962 0.009 255.5);        /* #F1F5F9 */
  --muted-foreground: oklch(0.554 0.023 255.7); /* #64748B */
  /* primary, accent, destructive, warning restent identiques */
}

Pattern dark-first

Contrairement a la plupart des applications qui definissent le theme clair par defaut et ajoutent une classe .dark, Speedcube Master fait l'inverse. Le theme sombre est sur :root et le theme clair est active via la classe .light.

Bonne pratiqueLes couleurs sombres sont sur :root
Bonne pratiqueLes couleurs claires sont sous .light
A eviterNe pas utiliser .dark comme selecteur

Utilisation des tokens semantiques

Les composants ne doivent jamais utiliser de couleurs hardcodees. Toujours utiliser les classes Tailwind basees sur les tokens semantiques. Cela garantit que le theme fonctionne automatiquement.

// Correct - utilise les tokens semantiques
<div className="bg-background text-foreground">
  <div className="bg-card border border-border rounded-xl p-6">
    <h2 className="text-foreground">Titre</h2>
    <p className="text-muted-foreground">Description</p>
    <button className="bg-primary text-primary-foreground">
      Action
    </button>
  </div>
</div>

// Incorrect - couleurs hardcodees
<div className="bg-[#0B0F1A] text-[#E5E7EB]">
  <div className="bg-[#0F1524] border border-[#1F2937]">
    <h2 className="text-white">Titre</h2>
    <p className="text-gray-400">Description</p>
  </div>
</div>

Basculer le theme

Le composant de basculement de theme utilise le hook useTheme() de next-themes pour changer entre les modes sombre et clair.

"use client";

import { useTheme } from "next-themes";
import { Moon, Sun } from "lucide-react";
import { Button } from "@/components/ui/button";

export function ThemeToggle() {
  const { theme, setTheme } = useTheme();

  return (
    <Button
      variant="ghost"
      size="icon"
      onClick={() => setTheme(theme === "dark" ? "light" : "dark")}
      aria-label="Basculer le theme"
    >
      <Sun className="h-5 w-5 rotate-0 scale-100 transition-all dark:-rotate-90 dark:scale-0" />
      <Moon className="absolute h-5 w-5 rotate-90 scale-0 transition-all dark:rotate-0 dark:scale-100" />
    </Button>
  );
}

Exemples dans les composants

Voici comment les tokens sont utilises dans les composants courants pour garantir la compatibilite avec les deux themes.

// Card avec support dark/light automatique
<Card className="bg-card/95 backdrop-blur-sm border-border">
  <CardHeader>
    <CardTitle className="text-foreground">Session</CardTitle>
    <CardDescription className="text-muted-foreground">
      12 solves effectuees
    </CardDescription>
  </CardHeader>
</Card>

// Badge avec variantes
<Badge variant="default">  {/* bg-primary text-primary-foreground */}
<Badge variant="secondary"> {/* bg-secondary text-secondary-foreground */}
<Badge variant="outline">   {/* border-border text-foreground */}
<Badge variant="destructive"> {/* bg-destructive text-destructive-foreground */}

// Input avec bordure adaptative
<Input className="bg-background border-input text-foreground
  placeholder:text-muted-foreground
  focus:ring-ring focus:border-ring" />

// Etats hover et focus
<button className="bg-muted hover:bg-muted/80
  text-foreground
  focus-visible:ring-2 focus-visible:ring-ring
  focus-visible:ring-offset-2 focus-visible:ring-offset-background" />