Aller au contenu

Chapitre 4 : Les Variables en Sass/SCSS

Objectifs pédagogiques

À l'issue de ce chapitre, vous serez capable de : - Déclarer et utiliser des variables dans vos fichiers Sass/SCSS - Comprendre les différents types de données supportés - Maîtriser l'interpolation des variables - Distinguer les variables globales et locales - Utiliser les valeurs par défaut avec !default


Introduction

Les variables sont l'un des fondamentaux de tout langage de programmation, et Sass ne fait pas exception. Une variable est un conteneur nommé qui stocke une valeur que vous pouvez réutiliser tout au long de votre feuille de style. Elles permettent d'éviter la répétition, de centraliser les valeurs et de rendre le code plus facile à maintenir.

En Sass/SCSS, les variables sont précédées du symbole $.


1. Déclaration simple d'une variable

Syntaxe de base

// Déclaration d'une variable
$ma-variable: valeur;

Exemple concret

$primary-color: #3498db;
$font-size-base: 16px;
$border-radius: 8px;

h1 {
  color: $primary-color;
  font-size: $font-size-base;
}

.card {
  border: 1px solid darken($primary-color, 10%);
  border-radius: $border-radius;
  padding: 1rem;
}

Résultat CSS compilé :

h1 {
  color: #3498db;
  font-size: 16px;
}

.card {
  border: 1px solid #2980b9;
  border-radius: 8px;
  padding: 1rem;
}

Note importante : En Sass, une variable est définie lorsqu'elle est assignée, pas simplement déclarée. L'assignation crée la variable si elle n'existe pas déjà.


2. Les différents types de données

Sass supporte plusieurs types de données pour les variables. Voyons chacun d'entre eux en détail.

2.1 Les couleurs

Les couleurs peuvent être exprimées en différentes notations.

// Couleurs nommées
$text-color: black;
$highlight-color: tomato;
$transparent-overlay: rgba(0, 0, 0, 0.5);

// Notation hexadécimale
$brand-primary: #2ecc71;
$brand-secondary: #1abc9c;
$brand-short-hex: #fff;

// Notation rgb()
$primary-rgb: rgb(52, 152, 219);
$primary-rgb-alpha: rgba(52, 152, 219, 0.8);

// Notation hsl()
$primary-hsl: hsl(204, 70%, 53%);
$primary-hsl-alpha: hsla(204, 70%, 53%, 0.9);

// Utilisation
body {
  background-color: $highlight-color;
  color: $text-color;
}

.overlay {
  background-color: $transparent-overlay;
}

.card {
  border-color: $primary-rgb;
  box-shadow: 0 2px 4px $primary-rgb-alpha;
}

2.2 Les nombres

Les nombres peuvent être entiers, décimaux, avec ou sans unité.

// Entiers
$spacing-small: 8px;
$spacing-medium: 16px;
$spacing-large: 32px;
$spacing-xl: 48px;

// Décimaux
$font-size-small: 0.875rem;
$line-height-tight: 1.2;
$opacity-full: 1;
$opacity-half: 0.5;

// Sans unité (unitless)
$z-index-dropdown: 100;
$grid-columns: 12;
$multiplier: 2;

// Avec différentes unités
$width-percentage: 50%;
$width-viewport: 80vw;
$font-size-pixels: 14px;
$font-size-rem: 1.125rem;

.container {
  width: 50%;
  max-width: 1200px;
  margin: 0 auto;
}

.grid-item {
  width: calc(100% / $grid-columns);
  padding: $spacing-small;
  font-size: $font-size-small;
  line-height: $line-height-tight;
}

2.3 Les chaînes de caractères (Strings)

// Chaînes avec guillemets
$font-family-heading: "Montserrat", sans-serif;
$font-family-body: "Open Sans", Arial, sans-serif;
$content-before: "→";

// Chaînes sans guillemets
$font-stack: Helvetica, Arial, sans-serif;
$separator: -;

// Chaînes vides
$empty-string: "";

// Utilisation
.heading {
  font-family: $font-family-heading;
}

.body-text {
  font-family: $font-family-body;
}

.nav-item::after {
  content: $content-before;
}

.separator {
  content: $separator;
}

2.4 Les listes

Les listes peuvent être séparées par des espaces ou par des virgules.

// Liste séparée par des espaces
$box-shadow-list: 0 2px 4px rgba(0, 0, 0, 0.1), 0 4px 8px rgba(0, 0, 0, 0.1);

// Liste séparée par des virgules
$font-stacks: "Georgia", "Times New Roman", serif;
$gradient-stops: #1a1a2e, #16213e, #0f3460;

// Liste de sizes (nombres)
$sizes: 8px, 16px, 24px, 32px, 48px;

// Utilisation
.box {
  box-shadow: $box-shadow-list;
}

.text-serif {
  font-family: $font-stacks;
}

.gradient-bg {
  background: linear-gradient(135deg, $gradient-stops);
}

// Accès aux éléments individuels de la liste
.first-size {
  padding: nth($sizes, 1);
}

.second-size {
  padding: nth($sizes, 2);
}

.third-size {
  padding: nth($sizes, 3);
}

2.5 Les maps (associations)

Les maps sont des paires clé : valeur.

// Déclaration d'une map
$colors: (
  "primary": #3498db,
  "secondary": #2ecc71,
  "danger": #e74c3c,
  "warning": #f39c12,
  "info": #17a2b8,
  "light": #f8f9fa,
  "dark": #343a40
);

// Map de breakpoints
$breakpoints: (
  "small": 576px,
  "medium": 768px,
  "large": 992px,
  "xlarge": 1200px,
  "xxlarge": 1400px
);

// Map de typographie
$typography: (
  "h1": (font-size: 2.5rem, font-weight: 700, line-height: 1.2),
  "h2": (font-size: 2rem, font-weight: 600, line-height: 1.3),
  "h3": (font-size: 1.5rem, font-weight: 600, line-height: 1.4),
  "body": (font-size: 1rem, font-weight: 400, line-height: 1.6)
);

// Accès aux valeurs
.btn-primary {
  background-color: map-get($colors, "primary");
  border: 1px solid map-get($colors, "primary");
}

.btn-danger {
  background-color: map-get($colors, "danger");
  border: 1px solid map-get($colors, "danger");
}

// Itération sur une map
@each $name, $color in $colors {
  .text-#{$name} {
    color: $color;
  }

  .bg-#{$name} {
    background-color: $color;
  }
}

2.6 Les booléens

// Valeurs booléens
$enable-shadows: true;
$enable-gradients: false;
$enable-rounded: true;
$is-responsive: true;

// Utilisation conditionnelle
$border-radius-value: 0;

@if $enable-rounded {
  $border-radius-value: 4px;
}

.card {
  border-radius: $border-radius-value;

  @if $enable-shadows {
    box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
  }

  @if $enable-gradients {
    background: linear-gradient(135deg, #667eea, #764ba2);
  } @else {
    background-color: white;
  }
}

// Booléens pour des fonctionnalités
$use-transitions: true;
$transition-speed: 0.3s;

.button {
  transition: all $transition-speed ease;

  &:hover {
    @if $use-transitions {
      transform: translateY(-2px);
      box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
    }
  }
}

2.7 La valeur null

null代表「aucune valeur」(aucune valeur définie).

$secondary-color: null;
$extra-padding: null;
$custom-border: null;

// null est ignoré lors de la compilation
.element {
  color: $secondary-color;  // Cette ligne sera ignorée
  padding: 10px;
  margin: $extra-padding;   // Cette ligne sera ignorée
  border: $custom-border;   // Cette ligne sera ignorée
}

// Résultat CSS :
// .element {
//   padding: 10px;
// }

// Comparaison avec null
$variable-a: null;
$variable-b: false;

.test {
  // null est falsy
  @if $variable-a {
    display: block;  // PAS exécuté
  }

  // false est aussi falsy
  @if $variable-b {
    display: none;   // PAS exécuté
  }

  // Seules les valeurs "truthy" sont exécutées
  $variable-c: true;
  @if $variable-c {
    display: flex;   // Exécuté
  }
}

// Vérification si une variable n'est pas null
$custom-bg: null;

.box {
  background-color: if($custom-bg, $custom-bg, #fff);
  // Equivalent : si $custom-bg n'est pas null, utilise $custom-bg, sinon #fff
}

3. L'interpolation des variables (#{})

L'interpolation permet d'insérer la valeur d'une variable à l'intérieur d'un nom de propriété, d'un sélecteur, d'une valeur, ou d'une chaîne de caractères.

3.1 Interpolation dans les valeurs

$color-primary: #3498db;
$unit: px;

.button {
  // L'interpolation n'est pas toujours nécessaire dans les valeurs
  padding: 10px + 5px;
  color: $color-primary;  // Utilisation directe
}

// Mais elle est nécessaire pour construire des chaînes
$prefix: btn;
$size: lg;

// Ceci ne fonctionne PAS comme on pourrait le penser :
// .#{$prefix}-#{$size} { }  // Ceci crée le sélecteur .btn-lg

3.2 Interpolation dans les noms de propriétés

$property: margin;
$side: top;
$amount: 20px;

.element {
  // Nom de propriété dynamique
  #{$property}-#{$side}: $amount;
}

// Résultat : .element { margin-top: 20px; }

3.3 Interpolation dans les sélecteurs

$component: card;
$modifier: primary;

// Création dynamique de classes BEM
.#{$component} {
  padding: 1rem;
  border-radius: 8px;

  &__title {
    font-size: 1.5rem;
    font-weight: bold;
  }

  &__content {
    padding: 0.5rem;
  }

  &--#{$modifier} {
    background-color: #3498db;
    color: white;
  }

  &--danger {
    background-color: #e74c3c;
    color: white;
  }
}

// Résultat CSS :
// .card { ... }
// .card__title { ... }
// .card__content { ... }
// .card--primary { ... }
// .card--danger { ... }

3.4 Interpolation dans les chaînes de caractères

$icon-prefix: "icon";
$icon-name: "arrow-right";

.icon {
  // Concaténation de chaînes
  content: "#{$icon-prefix}-#{$icon-name}";
  // Résultat : content: "icon-arrow-right";
}

$base-url: "https://example.com/assets";
$image-name: "logo";

.logo {
  // Construction d'URL
  background-image: url("#{$base-url}/images/#{$image-name}.png");
}

$animation-name: "fadeIn";

.fade-element {
  animation-name: $animation-name;
  // Utilisation dans @keyframes via interpolation
}

3.5 Interpolation dans les arguments de fonctions

$size-base: 16px;

@function calculate-rem($size) {
  @return $size / $size-base * 1rem;
}

h1 {
  font-size: calculate-rem(32px);    // 2rem
}

h2 {
  font-size: calculate-rem(24px);    // 1.5rem
}

p {
  font-size: calculate-rem(14px);    // 0.875rem
}

4. Variables globales vs locales

4.1 Portée globale

Une variable définie en dehors de tout bloc est globale.

// Ceci est une variable globale
$primary-color: #3498db;
$font-size-base: 16px;
$spacing-unit: 8px;

// Elle peut être utilisée partout dans le fichier
h1 {
  color: $primary-color;
}

.sidebar {
  font-size: $font-size-base;
}

4.2 Portée locale

Une variable définie à l'intérieur d'un sélecteur ou d'un bloc est locale à ce bloc.

$global-color: blue;

.element {
  $local-color: red;  // Variable locale
  color: $local-color;      // Fonctionne
  background: $global-color; // Fonctionne (accessible depuis l'extérieur)
}

.another-element {
  // color: $local-color;  // ERREUR ! $local-color n'existe pas ici
  color: $global-color;     // Fonctionne
}

4.3 Scope imbriqué et portée des variables

$color: red;

.parent {
  $parent-color: green;  // Locale au parent

  .child {
    color: $parent-color;  // Fonctionne : inherited scope
    // $parent-color est accessible dans les enfants
  }

  .another-child {
    background: $parent-color;  // Fonctionne aussi
  }
}

.sibling {
  // color: $parent-color;  // ERREUR ! Pas accessible ici
  color: $color;  // Fonctionne : variable globale
}

4.4 Écrasement de variables (Shadowing)

Une variable locale peut masquer une variable globale du même nom.

$color: blue;  // Globale

.element {
  $color: red;  // Locale, masque la globale dans ce scope
  color: $color;  // red (pas blue)

  .child {
    color: $color;  // red (hérité du scope parent)
  }
}

.another {
  color: $color;  // blue (la globale, pas affectée)
}

// Exemple plus complexe
$margin: 10px;

.container {
  $margin: 20px;  // Masque la globale
  margin: $margin;  // 20px

  .inner {
    $margin: 30px;  // Masque à nouveau
    margin: $margin;  // 30px

    .deep {
      margin: $margin;  // 30px (hérité du scope le plus proche)
    }
  }
}

5. Valeurs par défaut avec !default

Le drapeau !default assigne une valeur à une variable uniquement si elle n'a pas déjà été définie. C'est un concept crucial pour la création de bibliothèques et de thèmes.

5.1 Principe de fonctionnement

// Si $primary-color n'a PAS été défini avant cette ligne...
$primary-color: #3498db !default;

// Si $primary-color A ÉTÉ défini avant, la valeur !default est ignorée

5.2 Exemple concret : Système de thème

// ============================================
// Fichier : _variables.scss (défauts du thème)
// ============================================

// Couleurs
$primary-color: #007bff !default;
$secondary-color: #6c757d !default;
$success-color: #28a745 !default;
$danger-color: #dc3545 !default;
$warning-color: #ffc107 !default;
$info-color: #17a2b8 !default;

// Typographie
$font-family-base: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto !default;
$font-size-base: 1rem !default;
$font-weight-normal: 400 !default;
$font-weight-bold: 700 !default;
$line-height-base: 1.5 !default;

// Espacement
$spacers: (
  0: 0,
  1: 0.25rem,
  2: 0.5rem,
  3: 1rem,
  4: 1.5rem,
  5: 3rem
) !default;

// Bordures
$border-color: #dee2e6 !default;
$border-width: 1px !default;
$border-radius: 0.25rem !default;

// Ombres
$box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1) !default;

// ============================================
// Fichier : main.scss (personnalisation du thème)
// ============================================

// AVANT l'import de _variables.scss, on redéfinit les valeurs
$primary-color: #e74c3c;      // Rouge au lieu de bleu
$font-family-base: "Roboto", sans-serif;
$border-radius: 0;

@import 'variables';

// Résultat :
// $primary-color = #e74c3c (personnalisé)
// $secondary-color = #6c757d (défaut)
// $font-family-base = "Roboto", sans-serif (personnalisé)
// $border-radius = 0 (personnalisé)

5.3 Hiérarchie des priorités

// Ordre de résolution :

// 1. Variable déjà définie dans le scope
$color: red;

.element {
  $color: blue !default;  // Ignoré, $color est déjà red
  color: $color;  // red
}

// 2. Variable non définie => !default prend effet
$size: null;  // Définie mais null

.box {
  $size: 20px !default;  // Utilisé car $size est null
  padding: $size;  // 20px
}

// 3. Variable jamais définie => !default crée la variable
$width: 100px !default;

.container {
  width: $width;  // 100px
}

5.4 Utilisation pratique avec des fichiers modulaires

// ============================================
// Fichier : _config.scss (configurations par défaut)
// ============================================

// Toutes les valeurs par défaut ici
$grid-columns: 12 !default;
$grid-gutter: 30px !default;
$grid-breakpoints: (
  xs: 0,
  sm: 576px,
  md: 768px,
  lg: 992px,
  xl: 1200px
) !default;

$enable-responsive: true !default;
$enable-dark-mode: false !default;

// ============================================
// Fichier : _custom-config.scss (personnalisations)
// ============================================

// On personnalise AVANT l'import
$grid-columns: 16;
$grid-gutter: 20px;
$enable-dark-mode: true;

// ============================================
// Fichier : _theme.scss
// ============================================

// Import des deux fichiers
@import 'custom-config';
@import 'config';

// $grid-columns = 16 (personnalisé)
// $grid-gutter = 20px (personnalisé)
// $enable-dark-mode = true (personnalisé)
// $enable-responsive = true (défaut)

6. Bonnes pratiques pour les variables

6.1 Conventions de nommage

// ✅ BON : Noms descriptifs et cohérents
$color-primary: #3498db;
$spacing-lg: 24px;
$font-size-heading: 2rem;
$border-radius-sm: 4px;

// ❌ MAUVAIS : Noms vagues ou inconsistants
$blue: #3498db;
$big: 24px;
$size: 2rem;
$r: 4px;

// ✅ BON : Préfixes par catégorie
$color-primary: #3498db;
$color-secondary: #2ecc71;
$color-danger: #e74c3c;

$shadow-sm: 0 1px 2px rgba(0,0,0,0.05);
$shadow-md: 0 4px 6px rgba(0,0,0,0.1);
$shadow-lg: 0 10px 15px rgba(0,0,0,0.1);

6.2 Organisation des fichiers

// ============================================
// Fichier : _variables-base.scss
// ============================================
// Variables fondamentales du projet
$primary-color: #3498db;
$font-family-base: "Helvetica Neue", sans-serif;

// ============================================
// Fichier : _variables-components.scss
// ============================================
// Variables spécifiques aux composants
$button-padding: 10px 20px;
$card-padding: 20px;

// ============================================
// Fichier : _variables-responsive.scss
// ============================================
// Breakpoints et variables responsive
$breakpoint-sm: 576px;
$breakpoint-md: 768px;

7. Exercices pratiques

Exercice 1 : Variables de base

Créez les variables suivantes et utilisez-les dans des sélecteurs :

// Créez ces variables :
// - $bg-color: #f5f5f5
// - $text-color: #333
// - $heading-color: #1a1a2e
// - $font-primary: "Arial", sans-serif
// - $spacing-base: 16px

// Puis utilisez-les :
// - body utilise $bg-color, $text-color, $font-primary
// - h1, h2, h3 utilisent $heading-color
// - .section utilise padding: $spacing-base

Exercice 2 : Types de données

// Créez une map de couleurs pour un site e-commerce
// avec au moins 6 couleurs (primary, secondary, success, danger, warning, info)

// Créez une liste de 5 tailles de police
// Utilisez nth() pour appliquer chaque taille à un heading (h1 à h5)

// Créez un booléen $enable-animations: true
// Créez une variable $transition-duration: 0.3s
// Appliquez une transition aux boutons si $enable-animations est true

Exercice 3 : Interpolation

// Créez des variables :
// - $prefix: "btn"
// - $sizes: ("sm", "md", "lg")
// - $paddings: (8px 16px, 12px 24px, 16px 32px)

// Utilisez une boucle @each avec l'interpolation pour créer :
// .btn-sm, .btn-md, .btn-lg avec les paddings correspondants

Exercice 4 : Variables globales et locales

// Créez une variable globale $theme-color: blue
// Dans un sélecteur .parent, créez une variable locale $theme-color: green
// Dans .parent .child, utilisez $theme-color
// Dans .sibling (en dehors de .parent), utilisez $theme-color
// Quel est le résultat pour chaque cas ?

Exercice 5 : Valeurs par défaut

// Créez un fichier _defaults.scss avec les variables suivantes (toutes en !default) :
// - $border-width: 1px
// - $border-color: #ccc
// - $border-radius: 4px
// - $font-size-base: 16px
// - $line-height-base: 1.5

// Dans votre fichier principal, redéfinissez $border-radius: 0
// puis importez _defaults.scss
// Vérifiez que $border-radius vaut 0 et que les autres gardent leur valeur par défaut

Exercice 6 : Projet complet

Créez un système de variables complet pour un portfolio :

// Variables couleurs (6 minimum)
// Variables typographie (4 minimum)
// Variables espacement (5 minimum)
// Variables breakpoints (4 minimum)
// Variables ombres (3 niveaux)
// Variables transitions (2 minimum)
// Un booléen $dark-mode: false
// Utilisez !default pour toutes les variables
// Créez un sélecteur .portfolio-item qui utilise au moins 8 variables différentes

Résumé

Concept Syntaxe Exemple
Variable simple $nom: valeur; $color: blue;
Interpolation #{$variable} #{$prefix}-btn
Scope local { $var: val; } Bloc imbriqué
Scope global En dehors des blocs $global: val;
Valeur par défaut !default $x: 10px !default;
Null null $x: null;
Booléen true / false $flag: true;

Chapitre suivant

Dans le Chapitre 5 - Imbrication (Nesting), nous verrons comment structurer notre code CSS de manière hiérarchique grâce à l'imbrication Sass.