Cette page n'est pas encore traduite en allemand. La version française est affichée.

Thèmes

Documentation Tailwind CSS v4

SmartMaker utilise TailwindCSS v4 pour la stylisation. Cette version introduit une nouvelle syntaxe CSS-first avec @theme et @layer.

Configuration TailwindCSS 4

Installation

TailwindCSS 4 est intégré via le plugin Vite :

// vite.config.js

import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";
import tailwindcss from "@tailwindcss/vite";

export default defineConfig({
  plugins: [
    react(),
    tailwindcss(),
  ],
});

Point d'entrée CSS

/* src/assets/styles/style.css */

@import "tailwindcss";

@layer theme, base, components;

@import "./theme.css" layer(theme);
@import "./base.css" layer(base);

Définir un thème

Variables CSS avec @theme

/* src/assets/styles/theme.css */

@theme {
  /* Couleurs principales */
  --color-primary: #5fbabf;
  --color-primary-light: #8cd4d8;
  --color-primary-dark: #4a9599;

  --color-secondary: #fc8c8c;
  --color-secondary-light: #fdb5b5;
  --color-secondary-dark: #e67070;

  /* Couleurs sémantiques */
  --color-success: #22c55e;
  --color-warning: #f59e0b;
  --color-error: #ef4444;
  --color-info: #3b82f6;

  /* Couleurs de fond */
  --color-background: #ffffff;
  --color-surface: #f8fafc;
  --color-muted: #f1f5f9;

  /* Couleurs de texte */
  --color-foreground: #0f172a;
  --color-foreground-muted: #64748b;

  /* Espacements customs */
  --spacing-page: 1rem;
  --spacing-card: 1.5rem;

  /* Rayons de bordure */
  --radius-sm: 0.375rem;
  --radius-md: 0.5rem;
  --radius-lg: 1rem;
  --radius-full: 9999px;

  /* Ombres */
  --shadow-sm: 0 1px 2px 0 rgb(0 0 0 / 0.05);
  --shadow-md: 0 4px 6px -1px rgb(0 0 0 / 0.1);
  --shadow-lg: 0 10px 15px -3px rgb(0 0 0 / 0.1);

  /* Typographie */
  --font-sans: 'Inter', system-ui, sans-serif;
  --font-mono: 'Fira Code', monospace;
}

Utilisation dans les composants

Les variables @theme génèrent automatiquement des classes Tailwind :

<div className="bg-primary text-white">
  Fond primaire
</div>

<div className="text-foreground-muted">
  Texte atténué
</div>

<div className="rounded-lg shadow-md p-card">
  Carte avec espacement
</div>

Styles de base

Reset et règles globales

/* src/assets/styles/base.css */

@layer base {
  * {
    @apply box-border p-0 m-0;
    -webkit-tap-highlight-color: transparent;
  }

  *::-webkit-scrollbar {
    display: none;
  }

  html {
    @apply scroll-smooth antialiased;
  }

  body {
    @apply bg-background text-foreground font-sans;
  }

  /* Focus visible pour accessibilité */
  :focus-visible {
    @apply outline-2 outline-offset-2 outline-primary;
  }

  /* Liens */
  a {
    @apply text-primary hover:text-primary-dark transition-colors;
  }

  /* Inputs */
  input, textarea, select {
    @apply bg-surface border border-gray-200 rounded-md;
    @apply focus:border-primary focus:ring-1 focus:ring-primary;
  }
}

Créer des thèmes multiples

Structure des fichiers

src/assets/
├── styles/
│   ├── style.css           # Point d'entrée
│   ├── theme.css           # Variables par défaut
│   └── base.css            # Règles globales
└── themes/
    ├── light.css           # Thème clair
    └── dark.css            # Thème sombre

Thème clair (défaut)

/* src/assets/themes/light.css */

@theme {
  --color-background: #ffffff;
  --color-surface: #f8fafc;
  --color-foreground: #0f172a;
  --color-foreground-muted: #64748b;
}

Thème sombre

/* src/assets/themes/dark.css */

@theme {
  --color-background: #0f172a;
  --color-surface: #1e293b;
  --color-foreground: #f8fafc;
  --color-foreground-muted: #94a3b8;
}

Chargement dynamique

// src/main.jsx

import { StrictMode } from "react";
import { createRoot } from "react-dom/client";

import "./assets/styles/style.css";

// Charger le thème depuis les settings
const theme = localStorage.getItem('theme') || 'light';
import(`./assets/themes/${theme}.css`);

import { App } from "./App";

createRoot(document.getElementById("root")).render(
  <StrictMode>
    <App />
  </StrictMode>
);

Hook useTheme

import { useGlobalStates } from '@cap-rel/smartcommon';
import { useEffect } from 'react';

export const useTheme = () => {
  const [theme, setTheme] = useGlobalStates('settings.theme');

  useEffect(() => {
    // Appliquer le thème au document
    document.documentElement.setAttribute('data-theme', theme);

    // Charger le CSS du thème
    const link = document.createElement('link');
    link.rel = 'stylesheet';
    link.href = `/themes/${theme}.css`;
    document.head.appendChild(link);

    return () => {
      document.head.removeChild(link);
    };
  }, [theme]);

  const toggleTheme = () => {
    setTheme(theme === 'light' ? 'dark' : 'light');
  };

  return { theme, setTheme, toggleTheme };
};

Composant ThemeToggle

import { useTheme } from '../hooks/useTheme';

const ThemeToggle = () => {
  const { theme, toggleTheme } = useTheme();

  return (
    <button
      onClick={toggleTheme}
      className="p-2 rounded-full bg-surface"
    >
      {theme === 'light' ? '🌙' : '☀️'}
    </button>
  );
};

Variantes de composants

Avec SmartCommon

SmartCommon permet de définir des variantes pour les composants :

// appConfig.js

export const config = {
  components: {
    variants: {
      Button: {
        default: "bg-primary text-white rounded-md px-4 py-2",
        secondary: "bg-secondary text-white rounded-md px-4 py-2",
        outline: "border-2 border-primary text-primary rounded-md px-4 py-2",
        ghost: "text-primary hover:bg-primary/10 rounded-md px-4 py-2",
      },
      Input: {
        default: "bg-surface border border-gray-200 rounded-md p-3",
        filled: "bg-muted border-0 rounded-md p-3",
        underline: "border-b-2 border-gray-200 rounded-none p-3",
      },
    },
  },
};

Utilisation

import { Button, Input } from '@cap-rel/smartcommon';

const MyForm = () => {
  return (
    <form>
      <Input name="email" variant="filled" />
      <Button variant="secondary">Envoyer</Button>
      <Button variant="ghost">Annuler</Button>
    </form>
  );
};

Classes utilitaires personnalisées

Avec @layer components

/* src/assets/styles/components.css */

@layer components {
  .card {
    @apply bg-surface rounded-lg shadow-md p-card;
  }

  .btn {
    @apply inline-flex items-center justify-center;
    @apply px-4 py-2 rounded-md font-medium;
    @apply transition-all duration-200;
    @apply focus:outline-none focus:ring-2 focus:ring-offset-2;
  }

  .btn-primary {
    @apply btn bg-primary text-white;
    @apply hover:bg-primary-dark;
    @apply focus:ring-primary;
  }

  .btn-secondary {
    @apply btn bg-secondary text-white;
    @apply hover:bg-secondary-dark;
    @apply focus:ring-secondary;
  }

  .input {
    @apply w-full px-4 py-3 rounded-md;
    @apply bg-surface border border-gray-200;
    @apply focus:border-primary focus:ring-1 focus:ring-primary;
    @apply placeholder:text-foreground-muted;
  }
}

Dark mode automatique

Avec prefers-color-scheme

/* src/assets/styles/theme.css */

@theme {
  --color-background: #ffffff;
  --color-foreground: #0f172a;
}

@media (prefers-color-scheme: dark) {
  @theme {
    --color-background: #0f172a;
    --color-foreground: #f8fafc;
  }
}

Avec classe CSS

/* Thème par défaut (clair) */
@theme {
  --color-background: #ffffff;
  --color-foreground: #0f172a;
}

/* Thème sombre via classe */
.dark {
  --color-background: #0f172a;
  --color-foreground: #f8fafc;
}
// Toggle dark mode
document.documentElement.classList.toggle('dark');

Conseils

Performance

  • Utilisez @theme pour les variables réutilisées
  • Évitez les classes Tailwind dynamiques (bg-${color})
  • Préférez les variantes de composants

Organisation

  • Un fichier pour les variables (theme.css)
  • Un fichier pour les règles de base (base.css)
  • Un fichier par thème additionnel

Accessibilité

  • Respectez les contrastes WCAG
  • Testez avec prefers-reduced-motion
  • Utilisez focus-visible pour le focus

Voir aussi