shadcn/ui
Des composants pro, prêts à l'emploi, que tu possèdes.
10.1C'est quoi shadcn/ui ?
shadcn/ui n'est pas une librairie de composants classique. C'est une collection de composants que tu copies dans ton projet. Chaque composant est :
Lisible
Du Tailwind CSS que tu connais déjà. Pas de magie.
Modifiable
Le code est chez toi. Tu changes ce que tu veux.
Accessible
Basé sur Radix UI : clavier, lecteur d'écran, ARIA.
10.2Installation
Dans un projet Next.js avec Tailwind déjà configuré :
# Initialiser shadcn/ui dans ton projet
npx shadcn@latest init
# Il va te poser quelques questions :
# ✔ Style → Default (ou New York)
# ✔ Base color → Slate
# ✔ CSS variables → YesL'init crée deux fichiers importants :
// components.json - La config shadcn
{
"$schema": "https://ui.shadcn.com/schema.json",
"style": "default",
"tailwind": {
"config": "tailwind.config.ts",
"css": "app/globals.css",
"baseColor": "slate"
},
"aliases": {
"components": "@/components",
"utils": "@/lib/utils"
}
}// lib/utils.ts - La fameuse fonction cn()
import { clsx, type ClassValue } from "clsx";
import { twMerge } from "tailwind-merge";
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs));
}
// Tu la connais déjà du chapitre 8 !10.3Ajouter des composants
Chaque composant s'ajoute à la demande avec la CLI :
# Ajouter un composant
npx shadcn@latest add button
npx shadcn@latest add card
npx shadcn@latest add input
npx shadcn@latest add dialog
# Ajouter plusieurs d'un coup
npx shadcn@latest add button card input label
# Voir tous les composants disponibles
npx shadcn@latest addQuand tu ajoutes un Button, voici ce qui se passe :
components/
└── ui/
└── button.tsx ← Copié dans TON projet
Ce n'est pas un import npm.
Tu peux le modifier librement.Et voici le code généré, du Tailwind pur que tu reconnais :
// components/ui/button.tsx (simplifié)
import * as React from "react"
import { cva, type VariantProps } from "class-variance-authority"
import { cn } from "@/lib/utils"
const buttonVariants = cva(
// Classes de base communes à tous les boutons
"inline-flex items-center justify-center gap-2 rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring 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: {
default: "h-10 px-4 py-2",
sm: "h-9 rounded-md px-3",
lg: "h-11 rounded-md px-8",
icon: "h-10 w-10",
},
},
defaultVariants: {
variant: "default",
size: "default",
},
}
)
export interface ButtonProps
extends React.ButtonHTMLAttributes<HTMLButtonElement>,
VariantProps<typeof buttonVariants> {}
const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
({ className, variant, size, ...props }, ref) => (
<button
className={cn(buttonVariants({ variant, size, className }))}
ref={ref}
{...props}
/>
)
)
Button.displayName = "Button"
export { Button, buttonVariants }10.4Utilisation au quotidien
Une fois les composants ajoutés, l'utilisation est simple et élégante :
import { Button } from "@/components/ui/button"
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"
import { Input } from "@/components/ui/input"
import { Label } from "@/components/ui/label"
export function LoginForm() {
return (
<Card className="w-[400px]">
<CardHeader>
<CardTitle>Connexion</CardTitle>
<CardDescription>Entre tes identifiants pour continuer.</CardDescription>
</CardHeader>
<CardContent className="space-y-4">
<div className="space-y-2">
<Label htmlFor="email">Email</Label>
<Input id="email" type="email" placeholder="ton@email.com" />
</div>
<div className="space-y-2">
<Label htmlFor="password">Mot de passe</Label>
<Input id="password" type="password" />
</div>
<Button className="w-full">Se connecter</Button>
<Button variant="outline" className="w-full">
Continuer avec Google
</Button>
</CardContent>
</Card>
)
}Remarques-tu le className sur Button et Card ? Tu peux toujours ajouter tes propres classes Tailwind - elles se fusionnent grâce à cn().
// Personnaliser un composant shadcn avec tes propres classes
<Button
variant="default"
className="bg-gradient-to-r from-blue-600 to-cyan-500 rounded-xl"
>
Mon bouton custom
</Button>
// Ça marche parce que cn() fusionne tes classes
// avec celles de la variante "default"10.5Les composants les plus utiles
shadcn/ui propose plus de 40 composants. Voici ceux que tu utiliseras le plus souvent :
| Composant | Usage | Basé sur |
|---|---|---|
Button | Boutons avec variantes | Natif |
Card | Conteneurs structurés | Natif |
Dialog | Modales accessibles | Radix UI |
DropdownMenu | Menus déroulants | Radix UI |
Tabs | Navigation par onglets | Radix UI |
Input / Label | Champs de formulaire | Natif |
Select | Listes déroulantes stylées | Radix UI |
Toast | Notifications temporaires | Radix UI |
Sheet | Panneau latéral (menu mobile) | Radix UI |
Accordion | Sections pliables (FAQ) | Radix UI |
10.6Personnaliser les composants
Comme le code est dans ton projet, tu le modifies directement :
// components/ui/button.tsx - AVANT
const buttonVariants = cva(
"inline-flex items-center justify-center gap-2 rounded-md ...",
{
variants: {
variant: {
default: "bg-primary text-primary-foreground hover:bg-primary/90",
// ...
},
},
}
)
// components/ui/button.tsx - APRÈS (ta version)
const buttonVariants = cva(
"inline-flex items-center justify-center gap-2 rounded-xl font-semibold ...",
// ^^^^^^^^^^^^^^^^^^^
// Coins plus ronds + bold
{
variants: {
variant: {
default: "bg-blue-600 text-white hover:bg-blue-700 shadow-lg shadow-blue-500/25",
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
// Tes couleurs de marque + ombre
brand: "bg-gradient-to-r from-blue-600 to-cyan-500 text-white hover:shadow-lg",
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
// Nouvelle variante que tu ajoutes !
},
size: {
// ...
xl: "h-14 rounded-2xl px-10 text-lg",
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
// Nouvelle taille
},
},
}
)10.7Le système de thème
shadcn/ui utilise des CSS variables pour ses couleurs. Ça se configure dans ton globals.css :
/* app/globals.css */
@layer base {
:root {
--background: 0 0% 100%; /* Blanc */
--foreground: 222.2 84% 4.9%; /* Texte sombre */
--primary: 221.2 83.2% 53.3%; /* Bleu principal */
--primary-foreground: 210 40% 98%;
--secondary: 210 40% 96.1%;
--accent: 210 40% 96.1%;
--destructive: 0 84.2% 60.2%; /* Rouge erreur */
--border: 214.3 31.8% 91.4%;
--ring: 221.2 83.2% 53.3%;
--radius: 0.5rem; /* Rayon des coins */
}
.dark {
--background: 222.2 84% 4.9%; /* Fond sombre */
--foreground: 210 40% 98%; /* Texte clair */
--primary: 217.2 91.2% 59.8%;
/* ... mêmes variables, couleurs dark */
}
}/* Changer le thème = changer les variables */
:root {
/* Thème "BaliseTonSite" */
--primary: 221 83% 53%; /* blue-600 */
--primary-foreground: 0 0% 100%;
--accent: 189 94% 43%; /* cyan-500 */
--radius: 0.75rem; /* Coins plus ronds */
}
/* Tous les composants shadcn changent automatiquement ! */10.8Exemple complet : page de pricing
Construisons une vraie page avec plusieurs composants shadcn :
import { Button } from "@/components/ui/button"
import {
Card, CardContent, CardDescription,
CardFooter, CardHeader, CardTitle
} from "@/components/ui/card"
import { Badge } from "@/components/ui/badge"
const plans = [
{
name: "Gratuit",
price: "0€",
desc: "Les fondamentaux du web",
features: ["HTML complet", "CSS complet", "JavaScript Vanilla"],
cta: "Commencer",
popular: false
},
{
name: "Full-Stack Moderne",
price: "19€/mois",
desc: "Les technos pro",
features: ["Tout le gratuit", "React", "Next.js", "TypeScript", "Tailwind"],
cta: "Essayer gratuit",
popular: true
}
]
export function PricingPage() {
return (
<div className="grid md:grid-cols-2 gap-6 max-w-3xl mx-auto">
{plans.map((plan) => (
<Card
key={plan.name}
className={cn(
"relative",
plan.popular && "border-primary shadow-lg"
)}
>
{plan.popular && (
<Badge className="absolute -top-3 left-1/2 -translate-x-1/2">
Populaire
</Badge>
)}
<CardHeader>
<CardTitle>{plan.name}</CardTitle>
<CardDescription>{plan.desc}</CardDescription>
<p className="text-3xl font-bold mt-2">{plan.price}</p>
</CardHeader>
<CardContent>
<ul className="space-y-2">
{plan.features.map((f) => (
<li key={f} className="flex items-center gap-2 text-sm">
<i className="fa-solid fa-check text-green-500" />
{f}
</li>
))}
</ul>
</CardContent>
<CardFooter>
<Button
className="w-full"
variant={plan.popular ? "default" : "outline"}
>
{plan.cta}
</Button>
</CardFooter>
</Card>
))}
</div>
)
}10.9Quand utiliser (ou pas) shadcn/ui
Utilise shadcn quand...
- • Tu as besoin de composants accessibles (modale, menu, select)
- • Tu veux un design cohérent rapidement
- • Tu es sur un projet React / Next.js
- • Tu veux personnaliser le rendu sans te battre avec une librairie
- • Tu veux comprendre le code (tu peux le lire !)
Skip shadcn quand...
- • Tu n'utilises pas React (vanilla JS, Vue, etc.)
- • Tu veux un composant très custom qui n'existe pas
- • Tu apprends encore les bases de Tailwind
- • Ton projet est très petit (un composant suffit)
- • Tu veux 0 dépendance externe