Tailwind CSS v4 : construire un design system SaaS en 1 journée
Tokens CSS, dark mode, composants réutilisables — comment structurer un design system professionnel avec Tailwind v4 et shadcn/ui.
Pourquoi un design system dès le jour 1 ?
Un SaaS sans design system finit toujours pareil : des couleurs hardcodées partout, un dark mode cassé, et des composants qu'on copie-colle au lieu de réutiliser.
Avec Tailwind CSS v4 et shadcn/ui, vous pouvez mettre en place un système solide en moins d'une journée. Voici comment on l'a fait pour HeartCo.
Étape 1 — Les tokens CSS
Tailwind v4 introduit les tokens CSS natifs. Plus besoin de tailwind.config.js pour les couleurs — tout passe par des variables CSS :
/* globals.css */
@theme {
--color-brand: #4f46e5;
--color-deep-navy: #0d1b2a;
--color-ai-blue: #3b82f6;
--color-ai-cyan: #06b6d4;
}L'avantage ? Le dark mode devient trivial
:root {
--color-background: oklch(0.984 0.003 247.858);
--color-foreground: oklch(0.141 0.005 285.823);
}
.dark {
--color-background: oklch(0.141 0.005 285.823);
--color-foreground: oklch(0.984 0.003 247.858);
}Vos composants utilisent bg-background et text-foreground — le thème switch automatiquement. Zéro dark: prefix à gérer manuellement.
Étape 2 — La librairie de composants
shadcn/ui n'est pas une dépendance npm — c'est un générateur de code. Vous copiez les composants dans votre projet et les personnalisez :
npx shadcn@latest add button card dialogLe secret : ne jamais modifier les primitives directement. Créez des wrappers métier :
// src/components/ui/heart-button.tsx
import { Button, type ButtonProps } from "~/components/ui/button";
import { cn } from "~/lib/utils";
export function HeartButton({
variant = "default",
className,
...props
}: ButtonProps) {
return (
<Button
className={cn(
"transition-all duration-200",
"hover:scale-[1.02] active:scale-[0.98]",
variant === "primary" &&
"bg-brand text-white shadow-[0_0_20px_rgba(99,102,241,0.3)]",
className,
)}
{...props}
/>
);
}Étape 3 — La fonction cn() — votre meilleur ami
// src/lib/utils.ts
import { clsx, type ClassValue } from "clsx";
import { twMerge } from "tailwind-merge";
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs));
}cn() résout le problème classique de Tailwind : quand deux classes conflictuelles se rencontrent (p-4 + p-6), twMerge garde la dernière. Sans ça, les overrides de styles sont imprévisibles.
Étape 4 — Les règles du design system
Quelques règles qu'on applique strictement dans HeartCo :
- Jamais de couleurs hardcodées — Toujours des tokens (
text-foreground, pastext-gray-900) - Jamais de
dark:prefix — Les tokens gèrent le thème - Hover states obligatoires — Chaque élément interactif a un feedback visuel
- Cards avec ombres, pas de bordures — Plus moderne, plus élégant
- Titres en display font — Séparer visuellement heading et body
Résultat
Avec cette structure, ajouter un nouveau composant prend 5 minutes au lieu de 30. Le dark mode fonctionne partout. Et votre SaaS a un look professionnel dès le premier jour.
Dans le prochain article, on parlera sécurité multi-tenant avec Prisma — le vrai sujet critique quand on construit un SaaS B2B.