Chapitre 11 : Structure professionnelle¶
Introduction¶
Dans ce chapitre final de la partie « Organisation », nous allons assembler tous les concepts précédents pour créer une structure de projet Sass professionnelle. Vous apprendrez à organiser vos fichiers, à créer un point d'entrée efficace et à adopter les conventions qui rendent votre projet maintenable à long terme.
11.1 La structure de dossiers¶
Architecture recommandée¶
Voici la structure complète d'un projet Sass professionnel :
src/scss/
├── abstracts/ → Outils (pas de CSS généré)
│ ├── _colors.scss
│ ├── _variables.scss
│ ├── _mixins.scss
│ ├── _functions.scss
│ ├── _tokens.scss
│ └── _index.scss
├── base/ → Styles de base
│ ├── _reset.scss
│ ├── _typography.scss
│ ├── _animations.scss
│ ├── _accessibility.scss
│ └── _index.scss
├── layout/ → Mise en page
│ ├── _grid.scss
│ ├── _header.scss
│ ├── _footer.scss
│ ├── _sidebar.scss
│ ├── _navigation.scss
│ └── _index.scss
├── components/ → Composants UI
│ ├── _buttons.scss
│ ├── _cards.scss
│ ├── _forms.scss
│ ├── _modals.scss
│ ├── _alerts.scss
│ ├── _badges.scss
│ ├── _breadcrumbs.scss
│ ├── _pagination.scss
│ ├── _tabs.scss
│ ├── _tooltips.scss
│ └── _index.scss
├── pages/ → Styles par page
│ ├── _home.scss
│ ├── _about.scss
│ ├── _contact.scss
│ ├── _blog.scss
│ └── _index.scss
├── themes/ → Thèmes
│ ├── _light.scss
│ ├── _dark.scss
│ ├── _high-contrast.scss
│ └── _index.scss
├── vendors/ → Bibliothèques externes
│ ├── _bootstrap-overrides.scss
│ ├── _font-awesome-overrides.scss
│ └── _index.scss
├── utilities/ → Classes utilitaires
│ ├── _spacing.scss
│ ├── _visibility.scss
│ ├── _text.scss
│ ├── _flex.scss
│ └── _index.scss
└── main.scss → POINT D'ENTRÉE UNIQUE
Chaque dossier a un rôle précis¶
| Dossier | Contenu | Génère du CSS ? |
|---|---|---|
abstracts/ |
Variables, mixins, fonctions, tokens | Non |
base/ |
Reset, typographie, animations | Oui |
layout/ |
Grid, header, footer, sidebar | Oui |
components/ |
Boutons, cartes, formulaires, etc. | Oui |
pages/ |
Styles spécifiques à chaque page | Oui |
themes/ |
Thèmes clair/sombre/contraste | Oui |
vendors/ |
Overrides de bibliothèques | Oui |
utilities/ |
Classes utilitaires | Oui |
Pourquoi cette structure fonctionne¶
- Clarté : Chaque dossier a un rôle bien défini.
- Scalabilité : Ajouter un nouveau composant est simple.
- Collaboration : Chaque développeur sait où chercher.
- Maintenance : Modifier un aspect du projet est rapide.
- Réutilisabilité : Les abstracts peuvent être partagés entre projets.
11.2 Créer les fichiers partiels de chaque dossier¶
abstracts/_colors.scss¶
// ============================================
// COULEURS DU PROJET
// ============================================
// --- Primaires ---
$color-primary: #3498db;
$color-primary-light: #5dade2;
$color-primary-dark: #2980b9;
$color-primary-ultra-dark: #1a5276;
// --- Secondaires ---
$color-secondary: #2ecc71;
$color-secondary-light: #58d68d;
$color-secondary-dark: #27ae60;
// --- Accent ---
$color-accent: #f39c12;
$color-accent-light: #f5b041;
$color-accent-dark: #d68910;
// --- Neutres ---
$color-white: #ffffff;
$color-black: #000000;
$color-gray-100: #f8f9fa;
$color-gray-200: #e9ecef;
$color-gray-300: #dee2e6;
$color-gray-400: #ced4da;
$color-gray-500: #adb5bd;
$color-gray-600: #6c757d;
$color-gray-700: #495057;
$color-gray-800: #343a40;
$color-gray-900: #212529;
// --- Sémantiques ---
$color-success: #27ae60;
$color-warning: #f39c12;
$color-danger: #e74c3c;
$color-info: #3498db;
// --- Maps ---
$colors: (
"primary": $color-primary,
"secondary": $color-secondary,
"accent": $color-accent,
"success": $color-success,
"warning": $color-warning,
"danger": $color-danger,
"info": $color-info
);
$grays: (
"100": $color-gray-100,
"200": $color-gray-200,
"300": $color-gray-300,
"400": $color-gray-400,
"500": $color-gray-500,
"600": $color-gray-600,
"700": $color-gray-700,
"800": $color-gray-800,
"900": $color-gray-900
);
abstracts/_variables.scss¶
// ============================================
// VARIABLES GLOBALES DU PROJET
// ============================================
// --- Breakpoints ---
$breakpoint-sm: 576px;
$breakpoint-md: 768px;
$breakpoint-lg: 992px;
$breakpoint-xl: 1200px;
$breakpoint-xxl: 1400px;
// --- Z-index ---
$z-dropdown: 1000;
$z-sticky: 1020;
$z-fixed: 1030;
$z-modal-backdrop: 1040;
$z-modal: 1050;
$z-popover: 1060;
$z-tooltip: 1070;
$z-toast: 1080;
// --- Transitions ---
$transition-speed: 0.3s;
$transition-easing: cubic-bezier(0.4, 0, 0.2, 1);
$transition-speed-fast: 0.15s;
$transition-speed-slow: 0.5s;
// --- Espacement ---
$spacing-unit: 8px;
$spacing-xs: $spacing-unit * 0.5;
$spacing-sm: $spacing-unit;
$spacing-md: $spacing-unit * 2;
$spacing-lg: $spacing-unit * 3;
$spacing-xl: $spacing-unit * 4;
$spacing-xxl: $spacing-unit * 6;
// --- Bordures ---
$border-radius-sm: 3px;
$border-radius: 5px;
$border-radius-lg: 10px;
$border-radius-xl: 15px;
$border-radius-pill: 50px;
$border-radius-circle: 50%;
// --- Ombres ---
$shadow-xs: 0 1px 2px rgba(0, 0, 0, 0.05);
$shadow-sm: 0 1px 3px rgba(0, 0, 0, 0.12);
$shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
$shadow-lg: 0 4px 16px rgba(0, 0, 0, 0.2);
$shadow-xl: 0 8px 32px rgba(0, 0, 0, 0.25);
// --- Container ---
$container-max-width: 1200px;
$container-padding: $spacing-md;
// --- Header ---
$header-height: 64px;
// --- Sidebar ---
$sidebar-width: 260px;
// --- Font ---
$font-family-base: 'Inter', -apple-system, BlinkMacSystemFont,
'Segoe UI', Roboto, Oxygen, Ubuntu, sans-serif;
$font-family-mono: 'Fira Code', 'Consolas', 'Monaco', monospace;
$font-size-base: 16px;
$font-size-sm: 14px;
$font-size-lg: 18px;
$font-size-xl: 20px;
$line-height-base: 1.6;
$font-weight-light: 300;
$font-weight-normal: 400;
$font-weight-medium: 500;
$font-weight-semibold: 600;
$font-weight-bold: 700;
abstracts/_mixins.scss¶
// ============================================
// MIXINS UTILITAIRES
// ============================================
@use 'variables' as *;
@use 'colors' as *;
// --- Responsive ---
@mixin respond-to($breakpoint) {
@if $breakpoint == "sm" {
@media (min-width: $breakpoint-sm) { @content; }
} @else if $breakpoint == "md" {
@media (min-width: $breakpoint-md) { @content; }
} @else if $breakpoint == "lg" {
@media (min-width: $breakpoint-lg) { @content; }
} @else if $breakpoint == "xl" {
@media (min-width: $breakpoint-xl) { @content; }
} @else if $breakpoint == "xxl" {
@media (min-width: $breakpoint-xxl) { @content; }
}
}
// --- Flexbox ---
@mixin flex-center {
display: flex;
justify-content: center;
align-items: center;
}
@mixin flex-between {
display: flex;
justify-content: space-between;
align-items: center;
}
@mixin flex-column-center {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
}
// --- Grid ---
@mixin auto-grid($min-width: 250px, $gap: $spacing-md) {
display: grid;
grid-template-columns: repeat(auto-fill, minmax($min-width, 1fr));
gap: $gap;
}
@mixin grid($columns, $gap: $spacing-md) {
display: grid;
grid-template-columns: repeat($columns, 1fr);
gap: $gap;
}
// --- Texte ---
@mixin truncate($lines: 1) {
@if $lines == 1 {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
} @else {
display: -webkit-box;
-webkit-line-clamp: $lines;
-webkit-box-orient: vertical;
overflow: hidden;
}
}
// --- Position ---
@mixin absolute-center {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
@mixin fixed-center {
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
// --- Transitions ---
@mixin transition($properties...) {
$result: ();
@each $prop in $properties {
$result: append($result, $prop $transition-speed $transition-easing, comma);
}
transition: $result;
}
@mixin transition-fast($properties...) {
$result: ();
@each $prop in $properties {
$result: append($result, $prop $transition-speed-fast $transition-easing, comma);
}
transition: $result;
}
// --- Accessibilité ---
@mixin visually-hidden {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip: rect(0, 0, 0, 0);
white-space: nowrap;
border: 0;
}
// --- Clearfix ---
@mixin clearfix {
&::after {
content: "";
display: table;
clear: both;
}
}
// --- Appear animation ---
@mixin fade-in($duration: 0.3s) {
animation: fadeIn $duration ease-in;
}
@mixin slide-up($duration: 0.3s) {
animation: slideUp $duration ease-out;
}
// --- Shadow avec couleur ---
@mixin colored-shadow($color, $blur: 10px, $spread: 0px, $opacity: 0.25) {
box-shadow: 0 $spread $blur rgba($color, $opacity);
}
// --- Aspect ratio ---
@mixin aspect-ratio($width: 16, $height: 9) {
aspect-ratio: #{$width} / #{$height};
}
abstracts/_functions.scss¶
// ============================================
// FONCTIONS UTILITAIRES
// ============================================
@use 'colors' as *;
@use 'variables' as *;
// --- Conversions ---
@function to-rem($px) {
@return ($px / 16px) * 1rem;
}
@function to-em($px, $context: 16px) {
@return ($px / $context) * 1em;
}
// --- Couleurs ---
@function color($key) {
@if not map-has-key($colors, $key) {
@warn "La couleur '#{$key}' n'existe pas dans $colors.";
@return null;
}
@return map-get($colors, $key);
}
@function gray($level) {
@if not map-has-key($grays, $level) {
@warn "Le gris '#{$level}' n'existe pas dans $grays.";
@return null;
}
@return map-get($grays, $level);
}
// --- Espacement ---
@function spacing($multiplier) {
@return $spacing-unit * $multiplier;
}
// --- Responsive ---
@function breakpoint($name) {
$breakpoints: (
"sm": $breakpoint-sm,
"md": $breakpoint-md,
"lg": $breakpoint-lg,
"xl": $breakpoint-xl,
"xxl": $breakpoint-xxl
);
@return map-get($breakpoints, $name);
}
abstracts/_index.scss¶
// ============================================
// INDEX DES ABSTRACTS
// Fichier barrel — ré-exporte tous les modules
// ============================================
@forward 'colors';
@forward 'variables';
@forward 'mixins' as *;
@forward 'functions' as *;
11.3 Créer les fichiers de base¶
base/_reset.scss¶
// ============================================
// RESET CSS MODERNISE
// ============================================
@use '../abstracts/variables' as *;
*,
*::before,
*::after {
box-sizing: border-box;
margin: 0;
padding: 0;
}
html {
font-size: $font-size-base;
scroll-behavior: smooth;
-webkit-text-size-adjust: 100%;
}
body {
font-family: $font-family-base;
font-size: 1rem;
line-height: $line-height-base;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
img,
picture,
video,
canvas,
svg {
display: block;
max-width: 100%;
}
input,
button,
textarea,
select {
font: inherit;
color: inherit;
}
a {
color: inherit;
text-decoration: none;
}
ul,
ol {
list-style: none;
}
h1, h2, h3, h4, h5, h6 {
font-size: inherit;
font-weight: inherit;
}
table {
border-collapse: collapse;
border-spacing: 0;
}
base/_typography.scss¶
// ============================================
// STYLES DE TYPOGRAPHIE
// ============================================
@use '../abstracts/variables' as *;
@use '../abstracts/colors' as *;
@use '../abstracts/mixins' as *;
@use '../abstracts/functions' as *;
body {
font-size: $font-size-base;
line-height: $line-height-base;
}
h1 {
font-size: to-rem(36px);
font-weight: $font-weight-bold;
line-height: 1.2;
margin-bottom: $spacing-md;
@include respond-to('md') {
font-size: to-rem(48px);
}
}
h2 {
font-size: to-rem(28px);
font-weight: $font-weight-bold;
line-height: 1.3;
margin-bottom: $spacing-md;
@include respond-to('md') {
font-size: to-rem(36px);
}
}
h3 {
font-size: to-rem(22px);
font-weight: $font-weight-semibold;
line-height: 1.4;
margin-bottom: $spacing-sm;
@include respond-to('md') {
font-size: to-rem(28px);
}
}
h4 {
font-size: to-rem(18px);
font-weight: $font-weight-semibold;
line-height: 1.4;
margin-bottom: $spacing-sm;
}
h5 {
font-size: $font-size-base;
font-weight: $font-weight-medium;
line-height: 1.5;
margin-bottom: $spacing-sm;
}
h6 {
font-size: $font-size-sm;
font-weight: $font-weight-medium;
line-height: 1.5;
margin-bottom: $spacing-xs;
text-transform: uppercase;
letter-spacing: 0.05em;
}
p {
margin-bottom: $spacing-md;
}
small {
font-size: $font-size-sm;
}
code,
pre {
font-family: $font-family-mono;
}
blockquote {
padding-left: $spacing-lg;
border-left: 4px solid $color-primary;
font-style: italic;
color: $color-text-secondary;
margin: $spacing-lg 0;
}
strong {
font-weight: $font-weight-bold;
}
base/_animations.scss¶
// ============================================
// ANIMATIONS DE BASE
// ============================================
@keyframes fadeIn {
from { opacity: 0; }
to { opacity: 1; }
}
@keyframes fadeOut {
from { opacity: 1; }
to { opacity: 0; }
}
@keyframes slideUp {
from {
opacity: 0;
transform: translateY(20px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
@keyframes slideDown {
from {
opacity: 0;
transform: translateY(-20px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
@keyframes slideInLeft {
from {
opacity: 0;
transform: translateX(-20px);
}
to {
opacity: 1;
transform: translateX(0);
}
}
@keyframes slideInRight {
from {
opacity: 0;
transform: translateX(20px);
}
to {
opacity: 1;
transform: translateX(0);
}
}
@keyframes scaleIn {
from {
opacity: 0;
transform: scale(0.9);
}
to {
opacity: 1;
transform: scale(1);
}
}
@keyframes spin {
from { transform: rotate(0deg); }
to { transform: rotate(360deg); }
}
@keyframes pulse {
0%, 100% { opacity: 1; }
50% { opacity: 0.5; }
}
@keyframes shake {
0%, 100% { transform: translateX(0); }
10%, 30%, 50%, 70%, 90% { transform: translateX(-4px); }
20%, 40%, 60%, 80% { transform: translateX(4px); }
}
11.4 Créer les fichiers de layout¶
layout/_grid.scss¶
// ============================================
// SYSTEME DE GRILLE
// ============================================
@use '../abstracts/variables' as *;
@use '../abstracts/mixins' as *;
$grid-columns: 12;
$grid-gutter: $spacing-md;
@mixin grid-container {
width: 100%;
max-width: $container-max-width;
margin-left: auto;
margin-right: auto;
padding-left: $grid-gutter;
padding-right: $grid-gutter;
}
@mixin grid-row {
display: flex;
flex-wrap: wrap;
margin-left: -$grid-gutter / 2;
margin-right: -$grid-gutter / 2;
}
@mixin grid-col($columns) {
flex: 0 0 percentage($columns / $grid-columns);
max-width: percentage($columns / $grid-columns);
padding-left: $grid-gutter / 2;
padding-right: $grid-gutter / 2;
}
@mixin grid-col-auto {
flex: 0 0 auto;
width: auto;
}
@mixin grid-col-fill {
flex: 1 1 0%;
}
// --- Utilitaires de grille ---
.container {
@include grid-container;
}
.row {
@include grid-row;
}
@for $i from 1 through $grid-columns {
.col-#{$i} {
@include grid-col($i);
}
}
@include respond-to('md') {
@for $i from 1 through $grid-columns {
.col-md-#{$i} {
@include grid-col($i);
}
}
}
@include respond-to('lg') {
@for $i from 1 through $grid-columns {
.col-lg-#{$i} {
@include grid-col($i);
}
}
}
@include respond-to('xl') {
@for $i from 1 through $grid-columns {
.col-xl-#{$i} {
@include grid-col($i);
}
}
}
layout/_header.scss¶
// ============================================
// HEADER / BARRE DE NAVIGATION
// ============================================
@use '../abstracts/variables' as *;
@use '../abstracts/colors' as *;
@use '../abstracts/mixins' as *;
.header {
position: sticky;
top: 0;
z-index: $z-sticky;
height: $header-height;
background-color: $color-white;
box-shadow: $shadow-sm;
&__container {
@include flex-between;
@include grid-container;
height: 100%;
}
&__logo {
display: flex;
align-items: center;
gap: $spacing-sm;
font-size: 1.25rem;
font-weight: $font-weight-bold;
color: $color-primary;
img {
height: 32px;
width: auto;
}
}
&__nav {
@include flex-center;
gap: $spacing-lg;
@include respond-to('md') {
gap: $spacing-xl;
}
}
&__link {
position: relative;
font-weight: $font-weight-medium;
color: $color-text-primary;
&:hover,
&--active {
color: $color-primary;
}
&--active::after {
content: "";
position: absolute;
bottom: -4px;
left: 0;
width: 100%;
height: 2px;
background-color: $color-primary;
}
}
&__actions {
@include flex-center;
gap: $spacing-sm;
}
&__menu-toggle {
display: block;
background: none;
border: none;
cursor: pointer;
padding: $spacing-sm;
@include respond-to('md') {
display: none;
}
}
}
layout/_footer.scss¶
// ============================================
// FOOTER
// ============================================
@use '../abstracts/variables' as *;
@use '../abstracts/colors' as *;
@use '../abstracts/mixins' as *;
.footer {
background-color: $color-gray-900;
color: $color-white;
padding: $spacing-xxl 0 $spacing-lg;
&__container {
@include grid-container;
}
&__grid {
@include auto-grid(250px, $spacing-xl);
margin-bottom: $spacing-xl;
}
&__title {
font-size: 1.125rem;
font-weight: $font-weight-semibold;
margin-bottom: $spacing-md;
}
&__text {
color: $color-gray-400;
line-height: 1.8;
}
&__link {
display: block;
color: $color-gray-400;
padding: $spacing-xs 0;
transition: color $transition-speed $transition-easing;
&:hover {
color: $color-white;
}
}
&__bottom {
border-top: 1px solid $color-gray-700;
padding-top: $spacing-lg;
text-align: center;
color: $color-gray-500;
font-size: $font-size-sm;
}
}
11.5 Créer les fichiers de composants¶
components/_buttons.scss¶
// ============================================
// COMPOSANT BOUTONS
// ============================================
@use '../abstracts/variables' as *;
@use '../abstracts/colors' as *;
@use '../abstracts/mixins' as *;
.btn {
display: inline-flex;
align-items: center;
justify-content: center;
gap: $spacing-sm;
padding: $spacing-sm $spacing-lg;
border: 2px solid transparent;
border-radius: $border-radius;
font-size: 1rem;
font-weight: $font-weight-medium;
line-height: 1.5;
cursor: pointer;
user-select: none;
text-decoration: none;
transition: background-color $transition-speed $transition-easing,
color $transition-speed $transition-easing,
border-color $transition-speed $transition-easing,
box-shadow $transition-speed $transition-easing;
&:disabled {
opacity: 0.5;
cursor: not-allowed;
pointer-events: none;
}
&:focus-visible {
outline: 2px solid $color-primary;
outline-offset: 2px;
}
// --- Variantes ---
&--primary {
background-color: $color-primary;
color: $color-white;
&:hover {
background-color: $color-primary-dark;
}
}
&--secondary {
background-color: $color-secondary;
color: $color-white;
&:hover {
background-color: $color-secondary-dark;
}
}
&--danger {
background-color: $color-danger;
color: $color-white;
&:hover {
background-color: darken($color-danger, 10%);
}
}
&--outline {
background-color: transparent;
border-color: $color-primary;
color: $color-primary;
&:hover {
background-color: $color-primary;
color: $color-white;
}
}
&--ghost {
background-color: transparent;
color: $color-primary;
&:hover {
background-color: rgba($color-primary, 0.1);
}
}
// --- Tailles ---
&--sm {
padding: $spacing-xs $spacing-md;
font-size: $font-size-sm;
}
&--lg {
padding: $spacing-md $spacing-xl;
font-size: 1.125rem;
}
&--block {
width: 100%;
}
&--pill {
border-radius: $border-radius-pill;
}
}
components/_cards.scss¶
// ============================================
// COMPOSANT CARTES
// ============================================
@use '../abstracts/variables' as *;
@use '../abstracts/colors' as *;
@use '../abstracts/mixins' as *;
.card {
background-color: $color-white;
border-radius: $border-radius-lg;
box-shadow: $shadow-sm;
overflow: hidden;
transition: box-shadow $transition-speed $transition-easing;
&:hover {
box-shadow: $shadow-lg;
}
&__image {
width: 100%;
height: 200px;
object-fit: cover;
}
&__body {
padding: $spacing-lg;
}
&__title {
font-size: 1.25rem;
font-weight: $font-weight-semibold;
color: $color-text-primary;
margin-bottom: $spacing-sm;
}
&__subtitle {
font-size: $font-size-sm;
color: $color-text-secondary;
margin-bottom: $spacing-md;
}
&__text {
color: $color-text-secondary;
margin-bottom: $spacing-md;
@include truncate(3);
}
&__footer {
padding: $spacing-md $spacing-lg;
border-top: 1px solid $color-gray-200;
@include flex-between;
}
&--outlined {
box-shadow: none;
border: 1px solid $color-gray-300;
&:hover {
border-color: $color-primary;
box-shadow: none;
}
}
&--flat {
box-shadow: none;
background-color: $color-gray-100;
&:hover {
background-color: $color-gray-200;
}
}
&--horizontal {
@include respond-to('md') {
display: flex;
.card__image {
width: 300px;
height: auto;
flex-shrink: 0;
}
}
}
&--interactive {
cursor: pointer;
&:hover {
transform: translateY(-2px);
box-shadow: $shadow-lg;
}
&:active {
transform: translateY(0);
}
}
}
components/_alerts.scss¶
// ============================================
// COMPOSANT ALERTES
// ============================================
@use '../abstracts/variables' as *;
@use '../abstracts/colors' as *;
@use '../abstracts/mixins' as *;
.alert {
display: flex;
align-items: flex-start;
gap: $spacing-md;
padding: $spacing-md $spacing-lg;
border-radius: $border-radius;
border: 1px solid transparent;
&__icon {
flex-shrink: 0;
font-size: 1.25rem;
line-height: 1.5;
}
&__content {
flex: 1;
}
&__title {
font-weight: $font-weight-semibold;
margin-bottom: $spacing-xs;
}
&__text {
line-height: 1.5;
}
&__close {
flex-shrink: 0;
background: none;
border: none;
cursor: pointer;
opacity: 0.7;
font-size: 1.25rem;
&:hover {
opacity: 1;
}
}
&--success {
background-color: rgba($color-success, 0.1);
border-color: $color-success;
color: darken($color-success, 20%);
}
&--warning {
background-color: rgba($color-warning, 0.1);
border-color: $color-warning;
color: darken($color-warning, 30%);
}
&--danger {
background-color: rgba($color-danger, 0.1);
border-color: $color-danger;
color: darken($color-danger, 20%);
}
&--info {
background-color: rgba($color-info, 0.1);
border-color: $color-info;
color: darken($color-info, 20%);
}
}
11.6 Créer les fichiers de pages¶
pages/_home.scss¶
// ============================================
// PAGE D'ACCUEIL
// ============================================
@use '../abstracts/variables' as *;
@use '../abstracts/colors' as *;
@use '../abstracts/mixins' as *;
@use '../abstracts/functions' as *;
.hero {
@include flex-column-center;
min-height: 80vh;
padding: $spacing-xxl $spacing-md;
background: linear-gradient(135deg, $color-primary, $color-primary-dark);
color: $color-white;
text-align: center;
&__title {
font-size: to-rem(48px);
font-weight: $font-weight-bold;
margin-bottom: $spacing-md;
animation: slideUp 0.5s ease-out;
@include respond-to('md') {
font-size: to-rem(64px);
}
}
&__subtitle {
font-size: to-rem(20px);
max-width: 600px;
margin-bottom: $spacing-xl;
opacity: 0.9;
animation: slideUp 0.5s ease-out 0.1s both;
}
&__actions {
display: flex;
gap: $spacing-md;
animation: fadeIn 0.5s ease-in 0.3s both;
}
}
.features {
padding: $spacing-xxl $spacing-md;
&__title {
text-align: center;
margin-bottom: $spacing-xl;
}
&__grid {
@include auto-grid(300px, $spacing-lg);
max-width: $container-max-width;
margin: 0 auto;
}
}
.feature-card {
padding: $spacing-xl;
text-align: center;
border-radius: $border-radius-lg;
background-color: $color-white;
box-shadow: $shadow-sm;
transition: transform $transition-speed $transition-easing,
box-shadow $transition-speed $transition-easing;
&:hover {
transform: translateY(-4px);
box-shadow: $shadow-lg;
}
&__icon {
width: 64px;
height: 64px;
margin: 0 auto $spacing-md;
background-color: rgba($color-primary, 0.1);
border-radius: $border-radius-circle;
@include flex-center;
font-size: 1.5rem;
color: $color-primary;
}
&__title {
font-size: 1.25rem;
font-weight: $font-weight-semibold;
margin-bottom: $spacing-sm;
}
&__text {
color: $color-text-secondary;
}
}
11.7 Créer les fichiers de thèmes¶
themes/_light.scss¶
// ============================================
// THEME CLAIR
// ============================================
$theme-bg-primary: #ffffff;
$theme-bg-secondary: #f8f9fa;
$theme-bg-tertiary: #e9ecef;
$theme-text-primary: #212529;
$theme-text-secondary: #6c757d;
$theme-text-muted: #adb5bd;
$theme-border-color: #dee2e6;
$theme-shadow-color: rgba(0, 0, 0, 0.1);
themes/_dark.scss¶
// ============================================
// THEME SOMBRE
// ============================================
$theme-bg-primary: #1a1a2e;
$theme-bg-secondary: #16213e;
$theme-bg-tertiary: #0f3460;
$theme-text-primary: #e0e0e0;
$theme-text-secondary: #a0a0a0;
$theme-text-muted: #666666;
$theme-border-color: #333355;
$theme-shadow-color: rgba(0, 0, 0, 0.3);
11.8 Créer les fichiers utilitaires¶
utilities/_spacing.scss¶
// ============================================
// UTILITAIRES D'ESPACEMENT
// ============================================
@use '../abstracts/variables' as *;
$sizes: (
"0": 0,
"xs": $spacing-xs,
"sm": $spacing-sm,
"md": $spacing-md,
"lg": $spacing-lg,
"xl": $spacing-xl,
"xxl": $spacing-xxl
);
@each $name, $value in $sizes {
.m-#{$name} { margin: $value; }
.mt-#{$name} { margin-top: $value; }
.mr-#{$name} { margin-right: $value; }
.mb-#{$name} { margin-bottom: $value; }
.ml-#{$name} { margin-left: $value; }
.mx-#{$name} { margin-left: $value; margin-right: $value; }
.my-#{$name} { margin-top: $value; margin-bottom: $value; }
.p-#{$name} { padding: $value; }
.pt-#{$name} { padding-top: $value; }
.pr-#{$name} { padding-right: $value; }
.pb-#{$name} { padding-bottom: $value; }
.pl-#{$name} { padding-left: $value; }
.px-#{$name} { padding-left: $value; padding-right: $value; }
.py-#{$name} { padding-top: $value; padding-bottom: $value; }
}
.mx-auto { margin-left: auto; margin-right: auto; }
.ml-auto { margin-left: auto; }
.mr-auto { margin-right: auto; }
utilities/_text.scss¶
// ============================================
// UTILITAIRES DE TEXTE
// ============================================
@use '../abstracts/variables' as *;
@use '../abstracts/colors' as *;
.text-left { text-align: left; }
.text-center { text-align: center; }
.text-right { text-align: right; }
.text-xs { font-size: $font-size-sm * 0.85; }
.text-sm { font-size: $font-size-sm; }
.text-base { font-size: $font-size-base; }
.text-lg { font-size: $font-size-lg; }
.text-xl { font-size: $font-size-xl; }
.text-2xl { font-size: 1.5rem; }
.text-3xl { font-size: 2rem; }
.font-light { font-weight: $font-weight-light; }
.font-normal { font-weight: $font-weight-normal; }
.font-medium { font-weight: $font-weight-medium; }
.font-semibold { font-weight: $font-weight-semibold; }
.font-bold { font-weight: $font-weight-bold; }
.text-primary { color: $color-primary; }
.text-secondary { color: $color-text-secondary; }
.text-muted { color: $color-text-muted; }
.text-success { color: $color-success; }
.text-warning { color: $color-warning; }
.text-danger { color: $color-danger; }
.text-info { color: $color-info; }
.text-white { color: $color-white; }
.text-uppercase { text-transform: uppercase; }
.text-lowercase { text-transform: lowercase; }
.text-capitalize { text-transform: capitalize; }
.text-italic { font-style: italic; }
.text-truncate {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.text-clamp-2 {
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
overflow: hidden;
}
.text-clamp-3 {
display: -webkit-box;
-webkit-line-clamp: 3;
-webkit-box-orient: vertical;
overflow: hidden;
}
utilities/_visibility.scss¶
// ============================================
// UTILITAIRES DE VISIBILITE
// ============================================
@use '../abstracts/mixins' as *;
.d-none { display: none; }
.d-block { display: block; }
.d-flex { display: flex; }
.d-grid { display: grid; }
.d-inline { display: inline; }
.d-inline-block { display: inline-block; }
.d-inline-flex { display: inline-flex; }
@include respond-to('md') {
.d-md-none { display: none; }
.d-md-block { display: block; }
.d-md-flex { display: flex; }
}
@include respond-to('lg') {
.d-lg-none { display: none; }
.d-lg-block { display: block; }
.d-lg-flex { display: flex; }
}
.sr-only {
@include visually-hidden;
}
.overflow-hidden { overflow: hidden; }
.overflow-auto { overflow: auto; }
.position-static { position: static; }
.position-relative { position: relative; }
.position-absolute { position: absolute; }
.position-fixed { position: fixed; }
.position-sticky { position: sticky; }
.opacity-0 { opacity: 0; }
.opacity-50 { opacity: 0.5; }
.opacity-100 { opacity: 1; }
.cursor-pointer { cursor: pointer; }
.cursor-not-allowed { cursor: not-allowed; }
11.9 Creer le fichier main.scss (point d'entree)¶
// ============================================
// MAIN.SCSS - POINT D'ENTREE DU PROJET
// ============================================
// Ce fichier est le SEUL a compiler
// Il importe tous les autres fichiers dans l'ordre correct
// ============================================
// 1. ABSTRACTS (pas de CSS genere)
@use 'abstracts/colors';
@use 'abstracts/variables';
@use 'abstracts/mixins' as *;
@use 'abstracts/functions' as *;
// 2. BASE (styles fondamentaux)
@use 'base/reset';
@use 'base/typography';
@use 'base/animations';
// 3. VENDORS (bibliotheques tierces)
@use 'vendors/bootstrap-overrides';
// 4. LAYOUT (structure de la page)
@use 'layout/grid';
@use 'layout/header';
@use 'layout/footer';
@use 'layout/sidebar';
// 5. COMPOSANTS (elements UI)
@use 'components/buttons';
@use 'components/cards';
@use 'components/forms';
@use 'components/alerts';
// 6. PAGES (styles specifiques)
@use 'pages/home';
@use 'pages/about';
@use 'pages/contact';
// 7. THEMES (themes visuels)
@use 'themes/light';
@use 'themes/dark';
// 8. UTILITAIRES (classes helper)
@use 'utilities/spacing';
@use 'utilities/text';
@use 'utilities/visibility';
11.10 Compiler uniquement main.scss¶
Commande de compilation¶
# Compiler le fichier principal
sass src/scss/main.scss dist/css/main.css
# Avec observation des changements
sass --watch src/scss/main.scss:dist/css/main.css
# Version minifiee
sass --style=compressed src/scss/main.scss dist/css/main.min.css
Pourquoi ne compiler QUE main.scss ?¶
NE JAMAIS faire :
sass src/scss/components/_buttons.scss buttons.css
sass src/scss/layout/_header.scss header.css
sass src/scss/base/_reset.scss reset.css
--> Genere des fichiers CSS multiples et non gerables
TOUJOURS faire :
sass src/scss/main.scss dist/css/main.css
--> Un seul fichier CSS contenant tout
--> Les fichiers partiels ne sont JAMAIS compiles seuls
Configuration avec des outils de build¶
Avec Vite¶
// vite.config.js
import { defineConfig } from 'vite';
export default defineConfig({
css: {
preprocessorOptions: {
scss: {
// Vite detecte automatiquement main.scss
// ou vous pouvez specifier :
// api: 'modern-compiler'
}
}
}
});
Avec Webpack¶
// webpack.config.js
module.exports = {
entry: {
main: './src/scss/main.scss'
},
module: {
rules: [{
test: /\.scss$/,
use: [
MiniCssExtractPlugin.loader,
'css-loader',
'sass-loader'
]
}]
}
};
Avec package.json scripts¶
{
"scripts": {
"build:css": "sass src/scss/main.scss dist/css/main.css --style=compressed",
"watch:css": "sass --watch src/scss/main.scss:dist/css/main.css"
}
}
11.11 Pourquoi cette structure fonctionne¶
1. Separation des responsabilites¶
Chaque dossier a UN role precis. Quand vous devez modifier les boutons, vous savez exactement ou aller : components/_buttons.scss.
2. Ordre de compilation logique¶
L'ordre des imports dans main.scss suit une logique :
- Abstracts : Les outils (pas de CSS)
- Base : Les fondations (reset, typographie)
- Vendors : Les overrides (apres base pour overrider)
- Layout : La structure (grid, header, footer)
- Components : Les elements UI (boutons, cartes)
- Pages : Les styles specifiques
- Themes : Les themes visuels
- Utilities : Les classes helper (dernier pour priorite)
3. Pas de CSS genere dans les abstracts¶
Les fichiers dans abstracts/ ne generent jamais de CSS. Ils contiennent uniquement des variables, mixins et fonctions. C'est pourquoi on les importe avec @use ... as * pour les utiliser directement.
4. Un seul point d'entree¶
main.scss est le fichier unique a compiler. Cela signifie :
- Un seul fichier CSS en sortie
- Pas de CSS duplique
- Compilation optimisee
- Gestion simple des dependances
5. Scalabilite¶
Pour ajouter un nouveau composant :
- Creer
components/_dropdown.scss - Ajouter
@use 'components/dropdown';dansmain.scss - C'est tout !
6. Collaboration facile¶
Developpeur A -> travaille sur components/_forms.scss
Developpeur B -> travaille on layout/_sidebar.scss
Developpeur C -> travaille on pages/_blog.scss
--> Aucun conflit possible !
11.12 Conventions de nommage¶
Fichiers¶
Variables¶
$color-primary: #3498db; --> prefixe du type
$spacing-md: 16px; --> nom descriptif
$border-radius-lg: 10px; --> taille optionnelle
$z-modal: 1050; --> prefixe z- pour z-index
$font-size-base: 16px; --> prefixe du type
Mixins¶
@mixin flex-center { } --> verbe ou description
@mixin respond-to($bp) { } --> nom descriptif
@mixin truncate($lines) { } --> action
@mixin button-variant($bg) { } --> composant + action
Classes CSS (BEM)¶
.block { } --> BEM block
.block__element { } --> BEM element
.block--modifier { } --> BEM modifier
// Exemples :
.card { }
.card__title { }
.card__text { }
.card--outlined { }
.card--interactive { }
.btn { }
.btn--primary { }
.btn--sm { }
.btn--lg { }
11.13 Exercices¶
Exercice 1 : Structure complete¶
Creez la structure de dossiers et tous les fichiers.listes dans ce chapitre. Verifiez que chaque dossier contient au moins 2 fichiers partiels et un _index.scss.
Exercice 2 : Ajouter un composant¶
Ajoutez un composant components/_modal.scss avec :
- Un mixin
modal-backdrop()pour l'overlay - Un mixin
modal-content()pour la boite de dialogue - Les variantes :
--small,--large,--fullscreen - Les animations : fade-in, scale-in
- Le fichier dans
_index.scssdu dossier components
Exercice 3 : Ajouter un theme¶
Creez un theme _high-contrast.scss pour l'accessibilite :
- Couleurs a tres fort contraste (noir sur blanc)
- Borders epaisses et visibles
- Focus outlines tres visibles
- Police plus grande par defaut
Exercice 4 : Creer un dossier print/¶
Creez un dossier print/ avec :
_print.scsspour les styles d'impression- Des media queries
@media print - La cache de elements non necessaires (nav, footer, sidebar)
- L'ajout dans
main.scss
Exercice 5 : Optimiser la compilation¶
- Configurez Sass pour generer une version minifiee
- Configurez les sourcemaps
- Ajoutez un script npm pour builder et watcher
- Testez que seul
main.scssest compile
Exercice 6 : Audit de structure¶
En vous basant sur la structure de ce chapitre, analysez un de vos projets existants et repondez :
- Combien de fichiers CSS avez-vous ?
- Combien de variables sont definies globalement ?
- Pouvons-nous identifier ou se trouve chaque style ?
- Quels fichiers pourraient etre des partiels ?
Resume¶
| Concept | Description |
|---|---|
abstracts/ |
Variables, mixins, fonctions, tokens (pas de CSS) |
base/ |
Reset, typographie, animations (fondations) |
layout/ |
Grid, header, footer, sidebar (structure) |
components/ |
Boutons, cartes, formulaires (elements UI) |
pages/ |
Styles specifiques a chaque page |
themes/ |
Themes clair, sombre, contraste |
vendors/ |
Overrides de bibliotheques externes |
utilities/ |
Classes helper (spacing, text, visibility) |
main.scss |
Point d'entree unique, seul fichier a compiler |
_index.scss |
Barrel file qui forward les modules d'un dossier |
Regles d'or¶
1. Toujours compiler main.scss, jamais les partiels
2. Abstracts en premier, utilities en dernier
3. Chaque dossier a un _index.scss (barrel file)
4. Utiliser BEM pour les noms de classes
5. Prefixer les variables ($color-, $spacing-, $font-)
6. Un role par dossier, un dossier par role
7. Ne jamais generer de CSS dans abstracts/