Les Custom Properties transforment le CSS en un vrai langage de programmation. Gérez vos thèmes, créez des design systems et dynamisez vos styles avec JavaScript.
Les variables CSS sont au CSS ce que les variables sont à tout langage de programmation. Elles permettent de DRY (Don't Repeat Yourself) vos feuilles de style, de créer des design systems cohérents, et de rendre le CSS dynamique.
VARIABLES CSS vs PRÉPROCESSEURS (Sass, Less)
═══════════════════════════════════════════════
SASS ($variable) CSS (Custom Properties)
───────────────── ───────────────────────
• Compilées à la build • natives du navigateur
• Pas de portée dynamique • Scope réel (inheritance)
• Pas de modification JS • Modifiables via JS
• Pas de media queries • Utilisables dans media queries
• Fonctionnent en build only • Fonctionnent en runtime
$couleur: #2563eb; :root { --couleur: #2563eb; }
.el { color: $couleur; } .el { color: var(--couleur); }
❌ Impossible en Sass : ✅ Possible en CSS :
$couleur = getComputedStyle() var(--couleur) + JavaScript
Avantages des variables CSS native par rapport aux préprocesseurs
| Besoin | Sans variables | Avec variables |
|---|---|---|
| Changement de couleur | Modifier 50+ lignes manuellement | Changer 1 variable, tout se met à jour |
| Thème dark mode | Dupliquer tous les styles | Redéfinir les variables, même HTML |
| Design system | Valeurs magiques partout | Tokens nommés et réutilisables |
| Espacement cohérent | 4px, 8px, 12px, 16px… hardcodés | --space-xs, --space-sm, --space-md… |
| Dynamisme JS | JavaScript qui modifie les styles inline | JS qui change une variable CSS, tout s'actualise |
| Responsive | Media queries avec valeurs en dur | Variables redéfinies par breakpoint |
-- et s'utilisent avec la fonction var(). Elles doivent respecter la casse (sensible à la casse).
/* Déclaration — toujours dans un sélecteur */
:root {
--ma-variable: valeur;
}
/* Exemples */
:root {
--couleur-primaire: #2563eb;
--taille-texte: 1rem;
--espacement: 1.5rem;
--rayon-bordure: 8px;
--police-titre: 'Segoe UI', sans-serif;
--ombre: 0 2px 10px rgba(0, 0, 0, 0.1);
--transition: 0.3s ease;
}
/* Avec la fonction var() */
.titre {
color: var(--couleur-primaire);
font-size: var(--taille-texte);
font-family: var(--police-titre);
padding: var(--espacement);
border-radius: var(--rayon-bordure);
box-shadow: var(--ombre);
transition: all var(--transition);
}
var() est une valeur de secours utilisée si la variable n'est pas définie.
/* Fallback simple */
.titre {
color: var(--couleur-primaire, #2563eb);
}
/* Fallback complexe */
.titre {
font-size: var(--taille-titre, var(--taille-base, 1rem));
/* Essaie --taille-titre, puis --taille-base, puis 1rem */
}
/* Fallback vide = valeur initiale */
.titre {
color: var(--couleur-personnalise, );
/* Si la variable n'existe pas, le navigateur utilise la valeur initiale */
}
/* ✅ Bonnes conventions */
:root {
--color-primary: #2563eb;
--color-secondary: #7c3aed;
--space-sm: 0.5rem;
--space-md: 1rem;
--space-lg: 1.5rem;
--shadow-sm: 0 1px 3px rgba(0,0,0,0.1);
--radius-md: 8px;
--font-heading: 'Segoe UI', sans-serif;
}
/* ❌ Mauvaises conventions */
:root {
--primaryColor: #2563eb; /* camelCase (non CSS-like) */
--BLUE: #2563eb; /* MAJUSCULES (confus) */
--1: 1rem; /* commence par un chiffre */
--my color: #2563eb; /* espace interdit */
--couleur-primäire: #2563eb; /* caractères spéciaux */
}
/* Les variables CSS sont SENSIBLES À LA CASSE */
:root {
--couleur: blue;
--Couleur: red;
--COULEUR: green;
}
.el1 { color: var(--couleur); } /* blue */
.el2 { color: var(--Couleur); } /* red */
.el3 { color: var(--COULEUR); } /* green */
:root, elles sont accessibles partout. Déclarées sur un élément, elles sont accessibles à cet élément et à tous ses descendants.
/* Scope global — :root (équivalent à html) */
:root {
--couleur: blue;
}
/* Scope local — un élément spécifique */
.card {
--couleur-card: #f8fafc;
background: var(--couleur-card); /* ✅ Visible ici */
}
.card .titre {
color: var(--couleur); /* ✅ Héritée de :root */
background: var(--couleur-card); /* ✅ Héritée de .card */
}
/* Scope imbriqué — enfant d'un autre scope */
.card.sombre {
--couleur-card: #1e293b; /* Redéfinition locale */
/* Tous les enfants de .card.sombre utiliseront cette nouvelle valeur */
}
Démonstration : Portée des variables
--couleur-primaire: #2563eb
Cette variable est accessible partout dans le document.
--couleur-secondaire: #7c3aed
Définie localement, héritée par les descendants.
--couleur-succes: #16a34a
On peut redéfinir une variable dans un scope enfant.
/* Raccourci pour définir toutes les variables d'un thème */
:root {
/* Couleurs */
--color-primary: #2563eb;
--color-primary-hover: #1d4ed8;
--color-secondary: #7c3aed;
--color-success: #16a34a;
--color-warning: #f59e0b;
--color-error: #dc2626;
--color-text: #1e293b;
--color-text-secondary: #64748b;
--color-bg: #ffffff;
--color-bg-secondary: #f8fafc;
--color-border: #e2e8f0;
/* Typographie */
--font-sans: 'Segoe UI', system-ui, sans-serif;
--font-mono: 'Cascadia Code', 'Fira Code', monospace;
--text-xs: 0.75rem;
--text-sm: 0.875rem;
--text-base: 1rem;
--text-lg: 1.125rem;
--text-xl: 1.25rem;
--text-2xl: 1.5rem;
--text-3xl: 2rem;
/* Espacement */
--space-1: 0.25rem;
--space-2: 0.5rem;
--space-3: 0.75rem;
--space-4: 1rem;
--space-6: 1.5rem;
--space-8: 2rem;
--space-12: 3rem;
/* Ombres */
--shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.05);
--shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
--shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.1);
/* Bordures */
--radius-sm: 4px;
--radius-md: 8px;
--radius-lg: 12px;
--radius-full: 9999px;
/* Transitions */
--transition-fast: 0.15s ease;
--transition-base: 0.3s ease;
--transition-slow: 0.5s ease;
}
[data-theme] ou prefers-color-scheme).
/* Thème clair (défaut) */
[data-theme="light"] {
--theme-bg: #ffffff;
--theme-text: #1e293b;
--theme-card-bg: #f8fafc;
--theme-border: #e2e8f0;
--theme-primary: #2563eb;
}
/* Thème sombre */
[data-theme="dark"] {
--theme-bg: #0f172a;
--theme-text: #f1f5f9;
--theme-card-bg: #1e293b;
--theme-border: #334155;
--theme-primary: #60a5fa;
}
/* Utilisation — le même code CSS fonctionne pour les deux thèmes */
body {
background: var(--theme-bg);
color: var(--theme-text);
}
.card {
background: var(--theme-card-bg);
border: 1px solid var(--theme-border);
}
/* Support automatique selon les préférences système */
@media (prefers-color-scheme: dark) {
:root:not([data-theme="light"]) {
--theme-bg: #0f172a;
--theme-text: #f1f5f9;
/* ... */
}
}
Démonstration : Thème Light/Dark avec variables
Cette carte utilise var(--theme-card-bg) pour son fond.
Cliquez sur le bouton pour changer de thème. Les variables se mettent à jour instantanément.
Le thème respecte aussi les préférences système avec prefers-color-scheme.
╔═══════════════════════════════════════════════════════════════╗
║ CUSTOM PROPERTIES — RÉSUMÉ ║
╠═══════════════════════════════════════════════════════════════╣
║ DÉCLARATION: ║
║ :root { --nom: valeur; } ║
║ element { --nom: valeur; } /* scope local */ ║
║ ║
║ UTILISATION: ║
║ property: var(--nom); ║
║ property: var(--nom, fallback); /* avec fallback */ ║
║ property: var(--nom, var(--autre, 1rem)); /* chaîné */ ║
║ ║
║ MODIFICATION JS: ║
║ element.style.setProperty('--nom', 'valeur'); ║
║ getComputedStyle(element).getPropertyValue('--nom'); ║
║ ║
║ PORTÉE: ║
║ :root → global (tout le document) ║
║ .classe → local (cet élément + descendants) ║
║ Héritage CSS classique ║
╚═══════════════════════════════════════════════════════════════╝
╔═══════════════════════════════════════════════════════════════╗
║ ARCHITECTURE DE NOMMAGE ║
╠═══════════════════════════════════════════════════════════════╣
║ --color-primary: #2563eb Couleurs ║
║ --color-primary-hover: #1d4ed8 États des couleurs ║
║ --space-sm: 0.5rem Espacement ║
║ --space-md: 1rem Escalier d'espacement ║
║ --text-lg: 1.125rem Taille de police ║
║ --shadow-md: 0 4px ... Ombres ║
║ --radius-md: 8px Bordures arrondies ║
║ --transition-base: 0.3s ease Transitions ║
║ --font-heading: sans-serif Polices ║
║ --z-dropdown: 100 Z-index ║
╚═══════════════════════════════════════════════════════════════╝
Résumé visuel des variables CSS et leur architecture
HÉRITAGE DES VARIABLES CSS
═══════════════════════════
:root { --couleur: blue; }
│
├── body { --couleur: blue; } ← hérite
│ │
│ ├── .header { --couleur: blue; } ← hérite
│ │ └── .nav { color: var(--couleur); } → blue
│ │
│ └── .content { --couleur: red; } ← REDÉFINI localement
│ ├── .title { color: var(--couleur); } → red
│ └── .text { color: var(--couleur); } → red
│
└── .sidebar { color: var(--couleur); } → blue (pas dans .content)
La variable suit l'arbre DOM comme n'importe quelle propriété CSS.
L'héritage des variables suit l'arbre DOM
Palette de couleurs avec tokens
/* ========== DESIGN TOKENS ========== */
:root {
/* Couleurs */
--color-primary: #2563eb;
--color-primary-hover: #1d4ed8;
--color-secondary: #7c3aed;
--color-success: #16a34a;
--color-warning: #f59e0b;
--color-error: #dc2626;
--color-text: #1e293b;
--color-text-secondary: #64748b;
--color-bg: #ffffff;
--color-bg-secondary: #f8fafc;
--color-border: #e2e8f0;
/* Typographie */
--font-sans: 'Segoe UI', system-ui, sans-serif;
--font-mono: 'Cascadia Code', monospace;
--text-xs: 0.75rem;
--text-sm: 0.875rem;
--text-base: 1rem;
--text-lg: 1.125rem;
--text-xl: 1.25rem;
--text-2xl: 1.5rem;
--text-3xl: 2rem;
/* Espacement */
--space-1: 0.25rem;
--space-2: 0.5rem;
--space-3: 0.75rem;
--space-4: 1rem;
--space-6: 1.5rem;
--space-8: 2rem;
/* Ombres */
--shadow-sm: 0 1px 2px rgba(0,0,0,0.05);
--shadow-md: 0 4px 6px -1px rgba(0,0,0,0.1);
--shadow-lg: 0 10px 15px -3px rgba(0,0,0,0.1);
/* Bordures */
--radius-sm: 4px;
--radius-md: 8px;
--radius-lg: 12px;
--radius-full: 9999px;
}
/* ========== UTILISATION ========== */
.bouton {
background: var(--color-primary);
color: white;
padding: var(--space-3) var(--space-6);
border-radius: var(--radius-md);
font-size: var(--text-base);
font-family: var(--font-sans);
box-shadow: var(--shadow-sm);
transition: all var(--transition-base);
}
.bouton:hover {
background: var(--color-primary-hover);
box-shadow: var(--shadow-md);
}
.carte {
background: var(--color-bg);
border: 1px solid var(--color-border);
border-radius: var(--radius-lg);
padding: var(--space-6);
box-shadow: var(--shadow-sm);
}
.titre {
font-family: var(--font-sans);
font-size: var(--text-2xl);
color: var(--color-text);
margin-bottom: var(--space-4);
}
.sous-titre {
font-family: var(--font-sans);
font-size: var(--text-sm);
color: var(--color-text-secondary);
}
Rendu : Design system avec tokens
/* Définir les deux thèmes */
:root, [data-theme="light"] {
--bg: #ffffff;
--text: #1e293b;
--card: #f8fafc;
--border: #e2e8f0;
--primary: #2563eb;
}
[data-theme="dark"] {
--bg: #0f172a;
--text: #f1f5f9;
--card: #1e293b;
--border: #334155;
--primary: #60a5fa;
}
/* Utilisation */
body {
background: var(--bg);
color: var(--text);
transition: background 0.3s, color 0.3s;
}
/* JavaScript pour switcher */
function toggleTheme() {
const current = document.documentElement.getAttribute('data-theme');
const next = current === 'dark' ? 'light' : 'dark';
document.documentElement.setAttribute('data-theme', next);
localStorage.setItem('theme', next); // Persistance
}
// Restaurer au chargement
const savedTheme = localStorage.getItem('theme') || 'light';
document.documentElement.setAttribute('data-theme', savedTheme);
Toggle CSS pur (sans JavaScript pour le rendu)
/* Composants avec variables pour le thème */
.bouton {
--btn-bg: var(--color-primary);
--btn-text: white;
--btn-hover: var(--color-primary-hover);
background: var(--btn-bg);
color: var(--btn-text);
padding: 0.6rem 1.25rem;
border: none;
border-radius: var(--radius-md);
cursor: pointer;
transition: background 0.2s;
}
.bouton:hover { background: var(--btn-hover); }
/* Variantes via re-définition de variables */
.bouton-secondaire {
--btn-bg: var(--color-bg-secondary);
--btn-text: var(--color-text);
--btn-hover: var(--color-border);
border: 1px solid var(--color-border);
}
.bouton-succes {
--btn-bg: var(--color-success);
--btn-hover: #15803d;
}
/* Composant externe qui peut tout personnaliser */
.custom-theme {
--btn-radius: 20px; /* Personnalise le rayon */
--btn-primary-bg: #ec4899; /* Change la couleur */
--btn-primary-hover: #db2777;
}
Rendu : Composants thématisés
// Modifier une variable CSS via JavaScript
document.documentElement.style.setProperty('--couleur', '#7c3aed');
// Lire la valeur actuelle
const couleur = getComputedStyle(document.documentElement)
.getPropertyValue('--couleur');
// Exemple : slider qui change la taille d'un élément
const slider = document.getElementById('size-slider');
slider.addEventListener('input', (e) => {
document.documentElement.style.setProperty(
'--dyn-size', `${e.target.value}px`
);
});
// Exemple : changement de couleur en temps réel
const colorPicker = document.getElementById('color');
colorPicker.addEventListener('input', (e) => {
document.documentElement.style.setProperty('--dyn-color', e.target.value);
});
Démonstration : Variables dynamiques avec sliders
/* Échelle basée sur la ratio 1.25 (Major Third) */
:root {
--text-xs: clamp(0.64rem, 0.58rem + 0.28vw, 0.8rem);
--text-sm: clamp(0.8rem, 0.73rem + 0.35vw, 1rem);
--text-base: clamp(1rem, 0.91rem + 0.43vw, 1.25rem);
--text-lg: clamp(1.25rem, 1.14rem + 0.54vw, 1.563rem);
--text-xl: clamp(1.563rem, 1.43rem + 0.67vw, 1.953rem);
--text-2xl: clamp(1.953rem, 1.78rem + 0.84vw, 2.441rem);
--text-3xl: clamp(2.441rem, 2.23rem + 1.05vw, 3.052rem);
--text-4xl: clamp(3.052rem, 2.79rem + 1.32vw, 3.815rem);
}
/* Utilisation */
h1 { font-size: var(--text-4xl); }
h2 { font-size: var(--text-3xl); }
h3 { font-size: var(--text-2xl); }
h4 { font-size: var(--text-xl); }
p { font-size: var(--text-base); }
small { font-size: var(--text-sm); }
Rendu : Échelle typographique
/* Escalier d'espacement (base 4px) */
:root {
--space-0: 0;
--space-1: 0.25rem; /* 4px */
--space-2: 0.5rem; /* 8px */
--space-3: 0.75rem; /* 12px */
--space-4: 1rem; /* 16px */
--space-5: 1.25rem; /* 20px */
--space-6: 1.5rem; /* 24px */
--space-8: 2rem; /* 32px */
--space-10: 2.5rem; /* 40px */
--space-12: 3rem; /* 48px */
--space-16: 4rem; /* 64px */
--space-20: 5rem; /* 80px */
--space-24: 6rem; /* 96px */
}
/* Utilisation cohérente */
.card { padding: var(--space-6); }
.card-title { margin-bottom: var(--space-4); }
.card-text { margin-bottom: var(--space-2); }
.section { padding: var(--space-12) var(--space-6); }
Rendu : Échelle d'espacement
/* Breakpoints en variables (utilisation en JS ou media queries) */
:root {
--bp-sm: 640px;
--bp-md: 768px;
--bp-lg: 1024px;
--bp-xl: 1280px;
--bp-2xl: 1536px;
}
/* Les variables ne fonctionnent PAS dans les media queries CSS nativement */
/* MAIS on peut les utiliser en JS */
@media (min-width: 768px) {
/* Il faut utiliser la valeur en dur ici */
.grid { grid-template-columns: repeat(3, 1fr); }
}
/* En JavaScript, on peut utiliser les variables */
const bp = getComputedStyle(document.documentElement)
.getPropertyValue('--bp-md').trim();
window.matchMedia(`(min-width: ${bp})`);
/* Palette basée sur une couleur primaire */
:root {
--color-primary-50: #eff6ff;
--color-primary-100: #dbeafe;
--color-primary-200: #bfdbfe;
--color-primary-300: #93c5fd;
--color-primary-400: #60a5fa;
--color-primary-500: #2563eb;
--color-primary-600: #1d4ed8;
--color-primary-700: #1e40af;
--color-primary-800: #1e3a8a;
--color-primary-900: #1e3a5f;
}
/* Utilisation selon le contexte */
.bouton { background: var(--color-primary-500); } /* Fond principal */
.bouton:hover { background: var(--color-primary-600); } /* Plus foncé */
.alerte { background: var(--color-primary-50); } /* Très clair */
.bordure { border-color: var(--color-primary-200); } /* Subtil */
.texte { color: var(--color-primary-700); } /* Foncé */
Rendu : Palette de couleurs
/* Système d'ombres progressif */
:root {
--shadow-xs: 0 1px 2px rgba(0, 0, 0, 0.05);
--shadow-sm: 0 1px 3px rgba(0, 0, 0, 0.1), 0 1px 2px rgba(0, 0, 0, 0.06);
--shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -2px rgba(0, 0, 0, 0.1);
--shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -4px rgba(0, 0, 0, 0.1);
--shadow-xl: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 8px 10px -6px rgba(0, 0, 0, 0.1);
--shadow-2xl: 0 25px 50px -12px rgba(0, 0, 0, 0.25);
--shadow-inner: inset 0 2px 4px rgba(0, 0, 0, 0.06);
/* Ombres colorées */
--shadow-primary: 0 4px 14px rgba(37, 99, 235, 0.25);
--shadow-success: 0 4px 14px rgba(22, 163, 74, 0.25);
--shadow-error: 0 4px 14px rgba(220, 38, 38, 0.25);
}
/* Utilisation */
.carte { box-shadow: var(--shadow-sm); }
.carte:hover { box-shadow: var(--shadow-lg); }
.bouton { box-shadow: var(--shadow-primary); }
.input:focus { box-shadow: 0 0 0 3px rgba(37, 99, 235, 0.1); }
Rendu : Système d'ombres
Convention de nommage recommandée
/* FAUX — le préfixe -- est OBLIGATOIRE à la déclaration */
:root {
couleur: #2563eb; /* C'est une propriété CSS normale, pas une variable ! */
}
/* CORRECT */
:root {
--couleur: #2563eb; /* Préfixe -- obligatoire */
}
/* Utilisation : var(--couleur) */
/* FAUX — var() cherche -- dans la variable, pas besoin de double -- */
.el { color: var(--couleur); } /* OK */
.el { color: var(----couleur); } /* FAUX — double -- */
/* CORRECT — var() ajoute automatiquement le -- */
.el { color: var(--couleur); }
/* FAUX — var() ne peut pas être utilisé à gauche de : */
.el {
var(--couleur): #2563eb; /* IMPOSSIBLE */
}
/* CORRECT — déclarer dans :root, utiliser dans le sélecteur */
:root { --couleur: #2563eb; }
.el { color: var(--couleur); }
/* RISQUÉ — si la variable n'existe pas, la propriété est invalide */
.el {
color: var(--couleur-personnalise);
}
/* SÉCURISÉ — toujours un fallback */
.el {
color: var(--couleur-personnalise, #2563eb);
}
/* OU avec fallback chaîné */
.el {
color: var(--couleur-personnalise, var(--couleur-primaire, #2563eb));
}
/* FAUX — les variables ne fonctionnent PAS dans les media queries */
/* Les media queries sont évaluées AVANT les variables */
@media (min-width: --bp-md) { /* NE FONCTIONNE PAS */
.grid { grid-template-columns: repeat(3, 1fr); }
}
/* CORRECT — utiliser les valeurs en dur */
@media (min-width: 768px) {
.grid { grid-template-columns: repeat(3, 1fr); }
}
/* Astuce : utiliser CSS nesting ou des classes */
:root { --mobile: 0; --desktop: 1; }
.grid { --cols: 1; }
@media (min-width: 768px) { .grid { --cols: 3; } }
.grid { grid-template-columns: repeat(var(--cols), 1fr); }
/* PAS EFFICACE — over-engineering */
:root {
--padding-top: 1rem;
--padding-right: 1rem;
--padding-bottom: 1rem;
--padding-left: 1rem;
}
.el { padding: var(--padding-top) var(--padding-right) var(--padding-bottom) var(--padding-left); }
/* EFFICACE — garder les variables pour les valeurs réellement réutilisées */
:root {
--space-4: 1rem;
}
.el { padding: var(--space-4); }
/* Ou simplement : .el { padding: 1rem; } si c'est unique */
/* INCOHÉRENT — mélange de conventions */
:root {
--couleur-primaire: #2563eb; /* kebab-case */
--ColorSecondary: #7c3aed; /* PascalCase */
--SPACE_MD: 1rem; /* SCREAMING_SNAKE */
}
/* CORRECT — adopter une convention unique */
:root {
--color-primary: #2563eb; /* kebab-case (recommandé) */
--color-secondary: #7c3aed;
--space-md: 1rem;
}
/* SURPRISE — la variable d'un parent affecte les enfants */
.parent {
--text-size: 2rem; /* Déclarée ici */
}
.parent .enfant {
/* --text-size est aussi 2rem ! Peut être inattendu */
font-size: var(--text-size); /* 2rem — hérité du parent */
}
/* CORRECT — être conscient de la portée ou redéfinir */
.enfant {
--text-size: 1rem; /* Redéfinition locale */
font-size: var(--text-size); /* 1rem — valeur locale */
}
--color-primary).var() pour éviter les surprises.[data-theme].style.setProperty() pour modifier en runtime./* 1. tokens.css — Définition de toutes les variables */
@import url('./tokens.css');
/* tokens.css contient : */
:root {
/* === COULEURS === */
--color-primary-50: #eff6ff;
/* ... palette complète ... */
/* === ESPACEMENT === */
--space-1: 0.25rem;
/* ... escalier complet ... */
/* === TYPOGRAPHIE === */
--font-sans: 'Segoe UI', system-ui, sans-serif;
/* ... échelle complète ... */
/* === OMBRES === */
--shadow-sm: 0 1px 2px rgba(0,0,0,0.05);
/* ... système complet ... */
/* === TRANSITIONS === */
--ease-in: cubic-bezier(0.4, 0, 1, 1);
--ease-out: cubic-bezier(0, 0, 0.2, 1);
--duration-fast: 150ms;
--duration-base: 300ms;
}
/* 2. dark.css — Override pour le thème sombre */
[data-theme="dark"] {
--color-primary-500: #60a5fa;
/* ... overrides ... */
}
/* 3. Les composants utilisent uniquement les tokens */
.bouton {
background: var(--color-primary-500);
padding: var(--space-3) var(--space-6);
font-family: var(--font-sans);
box-shadow: var(--shadow-sm);
transition: box-shadow var(--duration-base) var(--ease-out);
}
| Utiliser une variable quand… | Exemple |
|---|---|
| La valeur est utilisée 2+ fois | --color-primary |
| La valeur peut changer (thème) | --color-bg |
| La valeur est modifiable par JS | --dyn-size |
| C'est un token de design system | --space-4 |
| C'est une constante documentée | --nav-height: 64px |
Défi 1 : Thème clair/sombre
Créez un système de thème avec data-theme="light" et data-theme="dark". Le thème sombre doit avoir un fond #0f172a et un texte #f1f5f9.
:root, [data-theme="light"] {
--bg: #ffffff;
--text: #1e293b;
--card: #f8fafc;
}
[data-theme="dark"] {
--bg: #0f172a;
--text: #f1f5f9;
--card: #1e293b;
}
body {
background: var(--bg);
color: var(--text);
transition: background 0.3s, color 0.3s;
}
Défi 2 : Composant bouton avec variants
Créez un bouton qui utilise des variables pour ses couleurs, et 3 variantes (primaire, secondaire, danger) en redéfinissant les variables.
.btn {
--btn-bg: var(--color-primary);
--btn-text: white;
background: var(--btn-bg);
color: var(--btn-text);
padding: var(--space-3) var(--space-6);
border-radius: var(--radius-md);
}
.btn-secondary {
--btn-bg: var(--color-gray-100);
--btn-text: var(--color-gray-900);
}
.btn-danger {
--btn-bg: var(--color-error);
--btn-text: white;
}
Défi 3 : Palette dynamique
Créez un color picker qui modifie en temps réel la couleur d'un élément via style.setProperty().
const picker = document.getElementById('color');
picker.addEventListener('input', (e) => {
document.documentElement.style.setProperty('--color', e.target.value);
});
/* HTML */
/* <input type="color" id="color" value="#2563eb"> */
/* <div class="preview" style="background: var(--color)"> */
/* CSS */
/* .preview { transition: background 0.2s; } */
Défi 4 : Design system minimal
Créez un fichier de tokens avec : 5 couleurs, 5 espacements, 3 tailles de texte, 3 ombres, 3 rayons de bordure.
:root {
/* Couleurs */
--c-1: #2563eb;
--c-2: #7c3aed;
--c-3: #16a34a;
--c-4: #f59e0b;
--c-5: #dc2626;
/* Espacement */
--s-1: 0.25rem;
--s-2: 0.5rem;
--s-3: 1rem;
--s-4: 1.5rem;
--s-5: 2rem;
/* Texte */
--t-sm: 0.875rem;
--t-md: 1rem;
--t-lg: 1.25rem;
/* Ombres */
--sh-sm: 0 1px 2px rgba(0,0,0,0.05);
--sh-md: 0 4px 6px rgba(0,0,0,0.1);
--sh-lg: 0 10px 15px rgba(0,0,0,0.1);
/* Rayons */
--r-sm: 4px;
--r-md: 8px;
--r-lg: 16px;
}
Défi 5 : Typographie responsive
Créez une échelle typographique avec clamp() qui s'adapte à la taille de l'écran, en utilisant des variables.
:root {
--text-xs: clamp(0.7rem, 0.65rem + 0.25vw, 0.8rem);
--text-sm: clamp(0.8rem, 0.75rem + 0.3vw, 0.9rem);
--text-base: clamp(0.95rem, 0.9rem + 0.35vw, 1.1rem);
--text-lg: clamp(1.15rem, 1.05rem + 0.45vw, 1.35rem);
--text-xl: clamp(1.4rem, 1.2rem + 0.8vw, 1.8rem);
--text-2xl: clamp(1.7rem, 1.4rem + 1.2vw, 2.4rem);
--text-3xl: clamp(2rem, 1.5rem + 2vw, 3.2rem);
}
h1 { font-size: var(--text-3xl); }
h2 { font-size: var(--text-2xl); }
p { font-size: var(--text-base); }
Projet 1 : Design system complet
Créez un design system de A à Z avec :
data-themelocalStorageProjet 2 : Color palette generator
Créez un générateur de palette avec :
Projet 3 : Thème switcher avancé
Créez un système de thème multi-thèmes avec :
prefers-color-scheme comme valeur par défautlocalStorage| Concept | Syntaxe | Description |
|---|---|---|
| Déclaration | --nom: valeur; |
Crée une variable dans un sélecteur |
| Utilisation | var(--nom) |
Référence la variable |
| Fallback | var(--nom, défaut) |
Valeur par défaut si non définie |
| Scope global | :root { --x: val; } |
Accessible partout |
| Scope local | .el { --x: val; } |
Accessible dans .el et ses descendants |
| Modification JS | style.setProperty('--x', val) |
Modifier une variable en runtime |
| Lecture JS | getComputedStyle(el).getPropertyValue('--x') |
Lire la valeur actuelle |
| Thème | [data-theme="dark"] { --x: val; } |
Redéfinition par thème |
| Héritage | Naturel (comme les props CSS) | Les enfants héritent des variables parent |
| Casse | Sensible à la casse | --Color ≠ --color |
:root et .classe ?var() ?:root { --ma-variable: valeur; } — :root est l'équivalent de html, scope global.var(--ma-variable, valeur-par-defaut) — le 2e argument est le fallback.--color et --Color sont deux variables différentes.element.style.setProperty('--nom', 'valeur'):root = global (tout le document hérite), .classe = local (cet élément et ses enfants).[data-theme="dark"] avec des valeurs adaptées au fond sombre.kebab-case : --color-primary, --space-md, --shadow-lg./* Déclarer */
:root {
--couleur: #2563eb;
--espace: 1rem;
--rayon: 8px;
}
/* Utiliser */
.el {
color: var(--couleur);
padding: var(--espace);
border-radius: var(--rayon);
}
/* Avec fallback */
.el { color: var(--couleur, #2563eb); }
/* Scope local */
.card {
--card-bg: #f8fafc;
background: var(--card-bg);
}
/* Thème sombre */
[data-theme="dark"] {
--couleur: #60a5fa;
}
/* Modifier via JS */
document.documentElement.style.setProperty('--couleur', '#7c3aed');
/* Lire via JS */
const val = getComputedStyle(document.documentElement)
.getPropertyValue('--couleur').trim();
/* Animation avec variables */
.el {
transition: color var(--duration, 0.3s) var(--ease, ease);
}
/* Avec clamp pour responsive */
:root {
--text-lg: clamp(1rem, 0.9rem + 0.5vw, 1.2rem);
}