BaliseTonSite

Patterns Avancés

@apply, arbitrary values, animations et composition.

8.1@apply - Extraire des classes

Quand tu répètes le même groupe de classes, @apply permet de créer des classes CSS custom :

CSScss
/* styles/globals.css */
@import "tailwindcss";

@layer components {
  .btn {
    @apply inline-flex items-center justify-center gap-2
           px-5 py-2.5 rounded-lg font-medium
           transition-colors focus-visible:outline-none
           focus-visible:ring-2 focus-visible:ring-offset-2;
  }

  .btn-primary {
    @apply btn bg-blue-600 text-white
           hover:bg-blue-700 active:bg-blue-800
           focus-visible:ring-blue-500;
  }

  .btn-secondary {
    @apply btn border-2 border-slate-300 text-slate-700
           hover:bg-slate-50 active:bg-slate-100
           focus-visible:ring-slate-500;
  }

  .input {
    @apply w-full px-4 py-2.5 rounded-lg border border-slate-300
           text-slate-900 placeholder:text-slate-400
           focus:outline-none focus:ring-2 focus:ring-blue-500
           focus:border-blue-500 transition-colors;
  }

  .card {
    @apply bg-white rounded-xl border border-slate-200
           shadow-sm hover:shadow-md transition-shadow;
  }
}
HTMLhtml
<!-- Avant @apply (répétitif) -->
<button class="inline-flex items-center justify-center gap-2
               px-5 py-2.5 rounded-lg font-medium bg-blue-600
               text-white hover:bg-blue-700 ...">
  Sauvegarder
</button>

<!-- Après @apply (propre) -->
<button class="btn-primary">Sauvegarder</button>
<button class="btn-secondary">Annuler</button>
<input class="input" placeholder="Email" />

8.2Valeurs arbitraires

Quand l'échelle prédéfinie ne suffit pas, utilise les crochets [] :

HTMLhtml
<!-- Valeurs arbitraires avec [] -->
<div class="w-[350px]">        Largeur exacte de 350px
<div class="h-[calc(100vh-64px)]"> Hauteur viewport moins header
<div class="top-[117px]">      Position top custom
<div class="bg-[#1da1f2]">     Couleur Twitter exacte
<div class="text-[13px]">      Taille de texte custom
<div class="grid-cols-[200px_1fr_200px]"> Grille custom
<div class="p-[clamp(1rem,3vw,2rem)]">   Padding fluide

<!-- Propriétés CSS arbitraires -->
<div class="[mask-image:linear-gradient(to_bottom,black,transparent)]">
  Masque CSS avancé
</div>

<!-- Combiné avec des modificateurs -->
<div class="hover:bg-[#1da1f2] md:w-[600px]">
  Arbitraire + hover + responsive
</div>

8.3Composants React + Tailwind

En React/Next.js, on crée des composants réutilisables plutôt que d'utiliser @apply :

TSXtsx
// components/ui/button.tsx
import { cn } from "@/lib/utils";

interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
  variant?: "primary" | "secondary" | "ghost";
  size?: "sm" | "md" | "lg";
}

export function Button({
  variant = "primary",
  size = "md",
  className,
  children,
  ...props
}: ButtonProps) {
  return (
    <button
      className={cn(
        // Base
        "inline-flex items-center justify-center gap-2 rounded-lg",
        "font-medium transition-colors focus-visible:outline-none",
        "focus-visible:ring-2 focus-visible:ring-offset-2",
        // Variants
        variant === "primary" && "bg-blue-600 text-white hover:bg-blue-700",
        variant === "secondary" && "border-2 border-slate-300 text-slate-700 hover:bg-slate-50",
        variant === "ghost" && "text-slate-600 hover:bg-slate-100",
        // Sizes
        size === "sm" && "px-3 py-1.5 text-sm",
        size === "md" && "px-5 py-2.5 text-sm",
        size === "lg" && "px-7 py-3 text-base",
        // Custom classes
        className
      )}
      {...props}
    >
      {children}
    </button>
  );
}
TSXtsx
// Utilisation dans un composant
import { Button } from "@/components/ui/button";

export function Hero() {
  return (
    <div className="flex gap-3">
      <Button variant="primary" size="lg">
        Commencer
      </Button>
      <Button variant="secondary" size="lg">
        En savoir plus
      </Button>
    </div>
  );
}

8.4Animations custom

CSScss
/* Animations custom dans globals.css (v4) */
@import "tailwindcss";

@theme {
  --animate-slide-up: slide-up 0.3s ease-out;
  --animate-slide-down: slide-down 0.3s ease-out;
  --animate-scale-in: scale-in 0.2s ease-out;
  --animate-fade-in-up: fade-in-up 0.5s ease-out;
}

@keyframes slide-up {
  from { transform: translateY(100%); }
  to { transform: translateY(0); }
}

@keyframes slide-down {
  from { transform: translateY(-10px); opacity: 0; }
  to { transform: translateY(0); opacity: 1; }
}

@keyframes scale-in {
  from { transform: scale(0.95); opacity: 0; }
  to { transform: scale(1); opacity: 1; }
}

@keyframes fade-in-up {
  from { transform: translateY(20px); opacity: 0; }
  to { transform: translateY(0); opacity: 1; }
}
HTMLhtml
<!-- Utilisation -->
<div class="animate-slide-up">Modal qui glisse</div>
<div class="animate-scale-in">Popup qui scale</div>
<div class="animate-fade-in-up">Contenu qui apparaît</div>

<!-- Animation avec délai -->
<div class="animate-fade-in-up [animation-delay:200ms]">
  Apparaît après 200ms
</div>

<!-- Contrôle du mouvement (accessibilité) -->
<div class="motion-safe:animate-fade-in-up motion-reduce:opacity-100">
  Respecte les préférences de mouvement réduit
</div>

8.5Composition de variantes

HTMLhtml
<!-- Empiler les modificateurs -->
<div class="dark:md:hover:bg-blue-600">
  <!--  ↑ Dark mode + desktop + hover  -->
  Bleu au hover sur desktop en dark mode
</div>

<!-- Group avec variantes nommées -->
<div class="group/card hover:shadow-lg">
  <div class="group/icon inline-block">
    <i class="group-hover/icon:text-blue-500 fa-solid fa-star"></i>
  </div>
  <h3 class="group-hover/card:text-blue-600">Titre</h3>
</div>

<!-- Data attributes -->
<div data-state="open" class="data-[state=open]:bg-blue-50
                               data-[state=closed]:hidden">
  Contenu conditionnel par data attribute
</div>

<!-- Aria states -->
<button aria-expanded="true"
        class="aria-expanded:bg-blue-50 aria-expanded:text-blue-700">
  Menu
</button>

Verifie tes acquis

3 questions pour valider ce chapitre

1. Que fait @apply en Tailwind ?

Valide et sauvegarde ce chapitre

Ne perds pas le fil de ton apprentissage. Chaque QCM terminé sauvegarde ton score. Crée ton profil gratuitement pour débloquer toutes les évaluations du site et retrouver tes résultats plus tard.

Commencer l'aventure
Déjà membre ?Connecte-toi