🎯 Projet final 📍 Chapitre 21 / 21

Mini-projet final SCSS

Ce mini-projet final a un objectif clair : vous faire pratiquer tout ce que vous avez appris. Vous allez construire une petite interface "pro" (landing + composants) avec une architecture SCSS propre, des design tokens (maps), des mixins, du responsive, et une compilation dev/prod avec sourcemaps. L'idée n'est pas de faire "grand", mais de faire bien.

Objectifs du chapitre

  • Mettre en place une architecture SCSS professionnelle (dossiers + @use/@forward).
  • Créer des design tokens (couleurs, spacing, radius, typo, breakpoints).
  • Construire 3 composants réutilisables (bouton, carte, badge).
  • Rendre l'interface responsive (mobile-first).
  • Compiler en mode dev (sourcemaps) et en mode prod (CSS minifié).
À retenir

Le livrable attendu n'est pas seulement un rendu visuel, mais un code SCSS propre et maintenable.

Explications théoriques détaillées

Ce que vous allez construire

Une page simple mais réaliste :

  • Un "hero" (titre, texte, boutons)
  • Une section "features" avec des cartes
  • Des badges (états) et des boutons (variantes)
Conseil terrain

Un mini-projet réussi, c'est un projet où vous pouvez revenir dans 3 mois et comprendre votre code en 2 minutes.

Approche recommandée

  1. Architecture + tokens
  2. Fonctions/mixins (API interne)
  3. Composants (réutilisables)
  4. Layout + responsive
  5. Compilation dev/prod

Exemples simples

Arborescence de départ (simple mais pro)

Voici une structure de dossiers professionnelle pour organiser votre projet SCSS :

STRUCTURE
project/
├─ scss/
│  ├─ abstracts/
│  │  ├─ _tokens.scss
│  │  ├─ _functions.scss
│  │  ├─ _mixins.scss
│  │  └─ _index.scss
│  ├─ base/
│  │  ├─ _reset.scss
│  │  └─ _typography.scss
│  ├─ components/
│  │  ├─ _button.scss
│  │  ├─ _badge.scss
│  │  └─ _card.scss
│  ├─ layout/
│  │  ├─ _container.scss
│  │  ├─ _header.scss
│  │  └─ _sections.scss
│  └─ main.scss
├─ css/
│  ├─ style.css
│  ├─ style.css.map
│  └─ style.min.css
└─ index.html
À retenir

Vous éditez uniquement le dossier scss/. Le dossier css/ est généré par la compilation.

Fichier abstracts/_tokens.scss (design tokens)

Les design tokens centralisent toutes les valeurs de design (couleurs, espacements, etc.) :

SCSS
$tokens: (
  colors: (
    bg: #0b1020,
    text: #ffffff,
    surface: rgba(255,255,255,.08),
    border: rgba(255,255,255,.14),
    primary: #7aa7ff,
    success: #22c55e,
    warning: #f59e0b,
    danger:  #ef4444
  ),
  radius: (
    sm: 8px,
    md: 12px,
    lg: 20px
  ),
  space: (
    1: 8px,
    2: 16px,
    3: 24px,
    4: 40px
  ),
  typography: (
    base: (size: 16px, line: 1.6, family: ("Inter", system-ui, sans-serif)),
    heading: (family: ("Montserrat", system-ui, sans-serif))
  ),
  breakpoints: (
    md: 768px,
    lg: 1024px
  )
);

Fichier abstracts/_functions.scss (accès aux tokens)

Des fonctions utilitaires pour accéder facilement aux tokens :

SCSS
@use "tokens" as t;

@function token($group, $key, $subkey: null) {
  $group-map: map-get(t.$tokens, $group);

  @if $group-map == null {
    @error "Groupe de tokens inconnu : #{$group}";
  }

  $value: map-get($group-map, $key);

  @if $value == null {
    @error "Token inconnu : #{$group}.#{$key}";
  }

  // Permet de gérer des tokens imbriqués (ex: typography.base.size)
  @if $subkey != null {
    @if type-of($value) != "map" {
      @error "Le token #{$group}.#{$key} n'est pas une map, impossible d'accéder à #{$subkey}.";
    }
    $nested: map-get($value, $subkey);
    @if $nested == null {
      @error "Sous-clé inconnue : #{$group}.#{$key}.#{$subkey}";
    }
    @return $nested;
  }

  @return $value;
}

@function color($name) {
  @return token(colors, $name);
}

@function space($level) {
  @return token(space, $level);
}

@function radius($size) {
  @return token(radius, $size);
}

Fichier abstracts/_mixins.scss (version avec erreur intentionnelle)

Voici un exemple d'erreur courante à éviter :

SCSS
@use "functions" as f;

@mixin mq($bp) {
  $breakpoints: token(breakpoints, md); // ⚠️ ceci est un exemple d'erreur volontaire
  // (On corrigera plus bas avec la bonne façon)
}
Important

Dans un vrai projet, on évite les erreurs "volontaires". Ici je vous montre justement un cas typique : mal accéder à une map. Juste après, nous allons corriger cette erreur.

Exemples concrets et professionnels

Étape 1 : corriger et finaliser la mixin mq()

Nous voulons accéder à breakpoints (map) puis récupérer la clé demandée. Voici la version incorrecte :

SCSS
@use "functions" as f;

@mixin mq($key) {
  $bps: f.token(breakpoints, md); // ❌ mauvais : md n'est pas un token unique, c'est une clé d'une map
}

Version correcte : on récupère d'abord la map breakpoints, puis on vérifie la clé.

SCSS
@use "tokens" as t;

@mixin mq($key) {
  $bps: map-get(t.$tokens, breakpoints);

  @if $bps == null {
    @error "Map breakpoints introuvable dans les tokens.";
  }

  @if map-has-key($bps, $key) {
    @media (min-width: map-get($bps, $key)) {
      @content;
    }
  } @else {
    @error "Breakpoint inconnu : #{$key}";
  }
}
Conseil terrain

Vous pouvez aussi écrire une fonction bp() qui renvoie la valeur d'un breakpoint. L'idée : éviter de répéter la logique.

Étape 2 : base typographique (base/_typography.scss)

Appliquer les tokens à la typographie de base :

SCSS
@use "../abstracts/functions" as f;

body {
  font-family: f.token(typography, base, family);
  font-size: f.token(typography, base, size);
  line-height: f.token(typography, base, line);
  background: f.color(bg);
  color: f.color(text);
}

h1, h2, h3 {
  font-family: f.token(typography, heading, family);
  line-height: 1.2;
  margin: 0 0 f.space(2);
}

Étape 3 : composants (button, badge, card)

components/_button.scss

SCSS
@use "../abstracts/functions" as f;

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  padding: 10px 16px;
  border-radius: f.radius(md);
  font-weight: 700;
  border: 1px solid transparent;
  cursor: pointer;
  text-decoration: none;

  &--primary {
    background: f.color(primary);
    color: f.color(text);

    &:hover { background: darken(f.color(primary), 8%); }
    &:active { background: darken(f.color(primary), 14%); }
  }

  &--ghost {
    background: transparent;
    color: f.color(primary);
    border-color: rgba(f.color(primary), .35);

    &:hover { background: rgba(f.color(primary), .10); }
  }
}

components/_badge.scss

SCSS
@use "../abstracts/functions" as f;

.badge {
  display: inline-flex;
  align-items: center;
  padding: 6px 10px;
  border-radius: 999px;
  font-weight: 700;
  font-size: 14px;
  border: 1px solid transparent;

  &--success { background: rgba(f.color(success), .14); color: f.color(success); border-color: rgba(f.color(success), .35); }
  &--warning { background: rgba(f.color(warning), .14); color: f.color(warning); border-color: rgba(f.color(warning), .35); }
  &--danger  { background: rgba(f.color(danger),  .14); color: f.color(danger);  border-color: rgba(f.color(danger),  .35); }
}

components/_card.scss

SCSS
@use "../abstracts/functions" as f;

.card {
  background: f.color(surface);
  border: 1px solid f.color(border);
  border-radius: f.radius(lg);
  padding: f.space(3);

  &__title {
    font-weight: 900;
    margin-bottom: f.space(1);
  }

  &__text {
    color: rgba(f.color(text), .82);
    margin: 0;
  }
}

Étape 4 : layout + responsive (layout/_sections.scss)

Création d'une section "hero" et d'une section "features" avec une grille responsive :

SCSS
@use "../abstracts/functions" as f;
@use "../abstracts/mixins" as m;

.hero {
  padding: f.space(4) 0;

  &__title {
    font-size: 32px;
    margin-bottom: f.space(2);
  }

  &__actions {
    display: flex;
    flex-wrap: wrap;
    gap: f.space(1);
  }

  @include m.mq(md) {
    &__title { font-size: 40px; }
  }
}

.features {
  padding: f.space(4) 0;

  &__grid {
    display: grid;
    grid-template-columns: 1fr;
    gap: f.space(2);

    @include m.mq(md) {
      grid-template-columns: repeat(2, 1fr);
    }

    @include m.mq(lg) {
      grid-template-columns: repeat(3, 1fr);
    }
  }
}

Étape 5 : fichier main.scss (point d'entrée)

Le point d'entrée qui assemble tous les partiels :

SCSS
@use "abstracts";
@use "base/reset";
@use "base/typography";
@use "layout/container";
@use "layout/sections";
@use "components/button";
@use "components/badge";
@use "components/card";

Et l'index du dossier abstracts :

SCSS
/* abstracts/_index.scss */
@forward "tokens";
@forward "functions";
@forward "mixins";

Étape 6 : index.html (structure de la page)

Structure HTML complète utilisant tous nos composants SCSS :

HTML
<!doctype html>
<html lang="fr">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>Mini-projet SCSS</title>
  <link rel="stylesheet" href="css/style.css">
</head>
<body>

  <main class="container">
    <section class="hero">
      <span class="badge badge--success">SCSS Ready</span>
      <h1 class="hero__title">Interface propre, SCSS pro</h1>
      <p>Architecture, tokens, composants, responsive et compilation : tout est en place.</p>

      <div class="hero__actions">
        <a class="btn btn--primary" href="#">Démarrer</a>
        <a class="btn btn--ghost" href="#">Documentation</a>
      </div>
    </section>

    <section class="features">
      <h2>Features</h2>

      <div class="features__grid">
        <article class="card">
          <h3 class="card__title">Tokens</h3>
          <p class="card__text">Couleurs, spacing, radius et breakpoints centralisés.</p>
        </article>

        <article class="card">
          <h3 class="card__title">Composants</h3>
          <p class="card__text">Boutons, badges et cartes réutilisables.</p>
        </article>

        <article class="card">
          <h3 class="card__title">Responsive</h3>
          <p class="card__text">Grille et typographie adaptées aux écrans.</p>
        </article>
      </div>
    </section>
  </main>

</body>
</html>

Bonnes pratiques

  • Ne mettez pas de valeurs "magiques" dans les composants : utilisez les tokens.
  • Relisez régulièrement le CSS généré pour vérifier qu'il reste raisonnable.
  • Gardez une convention de nommage stable (ex. BEM).
  • Évitez le nesting profond, même dans un mini-projet.
  • Automatisez la compilation (scripts npm) pour dev/prod.
Conseil terrain

Un mini-projet est l'occasion idéale de prendre de bons réflexes : ce sont eux qui font la différence ensuite.

Erreurs courantes

Erreur 1 : coder le design directement dans les composants

Si vous mettez les hex colors partout, vous perdez immédiatement l'intérêt de SCSS dans un projet pro.

Erreur 2 : mélanger architecture et "quick fixes"

Ne rajoutez pas une règle dans un fichier au hasard "juste pour corriger". Respectez votre structure.

Erreur 3 : oublier dev/prod

Sourcemaps en dev, minification en prod : c'est un réflexe de base.

Résumé du chapitre

Ce que vous avez appris
Vous avez mis en place une architecture SCSS claire et réutilisable.
Vous avez créé des tokens et des fonctions pour piloter le design.
Vous avez construit des composants + un layout responsive.
Vous savez compiler en dev (sourcemaps) et en prod (minifié).

Fin du cours SCSS. Si vous le souhaitez, nous pouvons maintenant améliorer ce mini-projet (thème light/dark, variantes, pages supplémentaires), ou transformer ce mini-projet en template réutilisable pour vos futurs projets.