design-system-patterns

Par wshobson · agents

Construisez des systèmes de design évolutifs avec des tokens de design, une infrastructure de thématisation et des patterns d'architecture de composants. À utiliser lors de la création de tokens de design, de l'implémentation du changement de thème, de la construction de bibliothèques de composants ou de l'établissement des fondations d'un système de design.

npx skills add https://github.com/wshobson/agents --skill design-system-patterns

Patterns de Système de Design

Maîtrisez l'architecture des systèmes de design pour créer des fondations UI cohérentes, maintenables et scalables sur les applications web et mobiles.

Quand utiliser cette compétence

  • Créer des design tokens pour les couleurs, typographie, espacement et ombres
  • Implémenter le changement de thème clair/sombre avec les propriétés personnalisées CSS
  • Construire des systèmes de thématisation multi-marques
  • Architecturer des bibliothèques de composants avec des APIs cohérentes
  • Établir des workflows design-to-code avec les tokens Figma
  • Créer des hiérarchies de tokens sémantiques (primitive, sémantique, composant)
  • Configurer la documentation et les directives du système de design

Capacités principales

1. Design Tokens

  • Tokens primitifs (valeurs brutes : couleurs, tailles, polices)
  • Tokens sémantiques (sens contextuel : text-primary, surface-elevated)
  • Tokens de composant (usage spécifique : button-bg, card-border)
  • Conventions et organisation des noms de tokens
  • Génération de tokens multi-plateforme (CSS, iOS, Android)

2. Infrastructure de thématisation

  • Architecture des propriétés personnalisées CSS
  • Fournisseurs de contexte de thème en React
  • Changement de thème dynamique
  • Détection des préférences système (prefers-color-scheme)
  • Stockage persistant du thème
  • Modes mouvement réduit et contraste élevé

3. Architecture des composants

  • Patterns de composants composés
  • Composants polymorphes (prop as)
  • Systèmes de variante et taille
  • Composition basée sur les slots
  • Patterns d'UI headless
  • Props de style et variantes réactives

4. Pipeline de tokens

  • Synchronisation Figma vers code
  • Configuration de Style Dictionary
  • Transformation et formatage des tokens
  • Intégration CI/CD pour les mises à jour de tokens

Démarrage rapide

// Design tokens avec propriétés personnalisées CSS
const tokens = {
  colors: {
    // Tokens primitifs
    gray: {
      50: "#fafafa",
      100: "#f5f5f5",
      900: "#171717",
    },
    blue: {
      500: "#3b82f6",
      600: "#2563eb",
    },
  },
  // Tokens sémantiques (références aux primitifs)
  semantic: {
    light: {
      "text-primary": "var(--color-gray-900)",
      "text-secondary": "var(--color-gray-600)",
      "surface-default": "var(--color-white)",
      "surface-elevated": "var(--color-gray-50)",
      "border-default": "var(--color-gray-200)",
      "interactive-primary": "var(--color-blue-500)",
    },
    dark: {
      "text-primary": "var(--color-gray-50)",
      "text-secondary": "var(--color-gray-400)",
      "surface-default": "var(--color-gray-900)",
      "surface-elevated": "var(--color-gray-800)",
      "border-default": "var(--color-gray-700)",
      "interactive-primary": "var(--color-blue-400)",
    },
  },
};

Patterns clés

Pattern 1 : Hiérarchie de tokens

/* Couche 1 : Tokens primitifs (valeurs brutes) */
:root {
  --color-blue-500: #3b82f6;
  --color-blue-600: #2563eb;
  --color-gray-50: #fafafa;
  --color-gray-900: #171717;

  --space-1: 0.25rem;
  --space-2: 0.5rem;
  --space-4: 1rem;

  --font-size-sm: 0.875rem;
  --font-size-base: 1rem;
  --font-size-lg: 1.125rem;

  --radius-sm: 0.25rem;
  --radius-md: 0.5rem;
  --radius-lg: 1rem;
}

/* Couche 2 : Tokens sémantiques (sens) */
:root {
  --text-primary: var(--color-gray-900);
  --text-secondary: var(--color-gray-600);
  --surface-default: white;
  --interactive-primary: var(--color-blue-500);
  --interactive-primary-hover: var(--color-blue-600);
}

/* Couche 3 : Tokens de composant (usage spécifique) */
:root {
  --button-bg: var(--interactive-primary);
  --button-bg-hover: var(--interactive-primary-hover);
  --button-text: white;
  --button-radius: var(--radius-md);
  --button-padding-x: var(--space-4);
  --button-padding-y: var(--space-2);
}

Pattern 2 : Changement de thème avec React

import { createContext, useContext, useEffect, useState } from "react";

type Theme = "light" | "dark" | "system";

interface ThemeContextValue {
  theme: Theme;
  resolvedTheme: "light" | "dark";
  setTheme: (theme: Theme) => void;
}

const ThemeContext = createContext<ThemeContextValue | null>(null);

export function ThemeProvider({ children }: { children: React.ReactNode }) {
  const [theme, setTheme] = useState<Theme>(() => {
    if (typeof window !== "undefined") {
      return (localStorage.getItem("theme") as Theme) || "system";
    }
    return "system";
  });

  const [resolvedTheme, setResolvedTheme] = useState<"light" | "dark">("light");

  useEffect(() => {
    const root = document.documentElement;

    const applyTheme = (isDark: boolean) => {
      root.classList.remove("light", "dark");
      root.classList.add(isDark ? "dark" : "light");
      setResolvedTheme(isDark ? "dark" : "light");
    };

    if (theme === "system") {
      const mediaQuery = window.matchMedia("(prefers-color-scheme: dark)");
      applyTheme(mediaQuery.matches);

      const handler = (e: MediaQueryListEvent) => applyTheme(e.matches);
      mediaQuery.addEventListener("change", handler);
      return () => mediaQuery.removeEventListener("change", handler);
    } else {
      applyTheme(theme === "dark");
    }
  }, [theme]);

  useEffect(() => {
    localStorage.setItem("theme", theme);
  }, [theme]);

  return (
    <ThemeContext.Provider value={{ theme, resolvedTheme, setTheme }}>
      {children}
    </ThemeContext.Provider>
  );
}

export const useTheme = () => {
  const context = useContext(ThemeContext);
  if (!context) throw new Error("useTheme must be used within ThemeProvider");
  return context;
};

Pattern 3 : Système de variantes avec CVA

import { cva, type VariantProps } from "class-variance-authority";
import { cn } from "@/lib/utils";

const buttonVariants = cva(
  // Styles de base
  "inline-flex items-center justify-center rounded-md font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 disabled:pointer-events-none disabled:opacity-50",
  {
    variants: {
      variant: {
        default: "bg-primary text-primary-foreground hover:bg-primary/90",
        destructive:
          "bg-destructive text-destructive-foreground hover:bg-destructive/90",
        outline:
          "border border-input bg-background hover:bg-accent hover:text-accent-foreground",
        secondary:
          "bg-secondary text-secondary-foreground hover:bg-secondary/80",
        ghost: "hover:bg-accent hover:text-accent-foreground",
        link: "text-primary underline-offset-4 hover:underline",
      },
      size: {
        sm: "h-9 px-3 text-sm",
        md: "h-10 px-4 text-sm",
        lg: "h-11 px-8 text-base",
        icon: "h-10 w-10",
      },
    },
    defaultVariants: {
      variant: "default",
      size: "md",
    },
  },
);

interface ButtonProps
  extends
    React.ButtonHTMLAttributes<HTMLButtonElement>,
    VariantProps<typeof buttonVariants> {
  asChild?: boolean;
}

export function Button({ className, variant, size, ...props }: ButtonProps) {
  return (
    <button
      className={cn(buttonVariants({ variant, size, className }))}
      {...props}
    />
  );
}

Pattern 4 : Configuration de Style Dictionary

// style-dictionary.config.js
module.exports = {
  source: ["tokens/**/*.json"],
  platforms: {
    css: {
      transformGroup: "css",
      buildPath: "dist/css/",
      files: [
        {
          destination: "variables.css",
          format: "css/variables",
          options: {
            outputReferences: true, // Préserver les références de tokens
          },
        },
      ],
    },
    scss: {
      transformGroup: "scss",
      buildPath: "dist/scss/",
      files: [
        {
          destination: "_variables.scss",
          format: "scss/variables",
        },
      ],
    },
    ios: {
      transformGroup: "ios-swift",
      buildPath: "dist/ios/",
      files: [
        {
          destination: "DesignTokens.swift",
          format: "ios-swift/class.swift",
          className: "DesignTokens",
        },
      ],
    },
    android: {
      transformGroup: "android",
      buildPath: "dist/android/",
      files: [
        {
          destination: "colors.xml",
          format: "android/colors",
          filter: { attributes: { category: "color" } },
        },
      ],
    },
  },
};

Bonnes pratiques

  1. Nommer les tokens par leur but : Utiliser des noms sémantiques (text-primary) et non des descriptions visuelles (dark-gray)
  2. Maintenir la hiérarchie des tokens : Primitifs > Sémantiques > Tokens de composant
  3. Documenter l'usage des tokens : Inclure des directives d'usage avec les définitions de tokens
  4. Versionner les tokens : Traiter les changements de tokens comme des changements d'API avec semver
  5. Tester les combinaisons de thèmes : Vérifier que tous les thèmes fonctionnent avec tous les composants
  6. Automatiser le pipeline de tokens : CI/CD pour la synchronisation Figma-vers-code
  7. Fournir des chemins de migration : Déprécier progressivement les tokens avec des alternatives claires

Problèmes courants

  • Prolifération de tokens : Trop de tokens sans hiérarchie claire
  • Nommage incohérent : Conventions mixtes (camelCase vs kebab-case)
  • Mode sombre manquant : Tokens qui ne s'adaptent pas aux changements de thème
  • Valeurs en dur : Utiliser des valeurs brutes au lieu de tokens
  • Références circulaires : Tokens se référençant les uns aux autres en boucles
  • Écarts entre plateformes : Tokens manquants pour certaines plateformes (web mais pas mobile)

Skills similaires