Cours HTML et CSS · Module 9: CSS moderne

CSS Nesting - L'imbrication native des styles

14 min de lecture
Dans cette leçon4

En CSS classique, tu commences chaque règle pour l'intérieur d'une carte par le même sélecteur : .card h2, .card p, .card .button. Tu le répètes comme un scribe qui recopie le même titre sur chaque rouleau, et quand tu changes le nom de la classe, tu dois corriger tous les endroits. Pendant des années, seul Sass résolvait ce problème. CSS Nesting est une fonctionnalité native de CSS qui permet d'imbriquer des sélecteurs à l'intérieur d'autres sélecteurs, à la manière des préprocesseurs comme Sass ou Less, sans aucune compilation. Cela améliore considérablement la lisibilité et l'organisation du code, et depuis 2023, cela fonctionne dans tous les principaux navigateurs.

Les bases du CSS Nesting

Imbrication simple

Commençons par une comparaison. Nous avons écrit les mêmes styles de carte d'abord de façon traditionnelle, puis avec l'imbrication :

1/* CSS traditionnel */
2.card {
3  padding: 1rem;
4  background: white;
5  border-radius: 8px;
6}
7
8.card h2 {
9  font-size: 1.5rem;
10  margin-bottom: 1rem;
11  color: #333;
12}
13
14.card p {
15  line-height: 1.6;
16  color: #666;
17}
18
19.card .button {
20  padding: 0.5rem 1rem;
21  background: #007bff;
22  color: white;
23  border: none;
24  border-radius: 4px;
25}
26
27/* CSS Nesting */
28.card {
29  padding: 1rem;
30  background: white;
31  border-radius: 8px;
32
33  h2 {
34    font-size: 1.5rem;
35    margin-bottom: 1rem;
36    color: #333;
37  }
38
39  p {
40    line-height: 1.6;
41    color: #666;
42  }
43
44  .button {
45    padding: 0.5rem 1rem;
46    background: #007bff;
47    color: white;
48    border: none;
49    border-radius: 4px;
50  }
51}

Les deux versions donnent un résultat identique. Le h2 imbriqué signifie exactement .card h2, c'est-à-dire le titre à l'intérieur de la carte, et il a la même spécificité. Seule l'écriture change : le nom .card n'apparaît qu'une fois, et tout ce qui concerne la carte se trouve dans un seul bloc.

Utiliser le symbole &

Le symbole & représente le sélecteur parent et permet de créer des sélecteurs plus complexes, comme des états, des variantes et des pseudo-éléments :

1.button {
2  padding: 0.75rem 1.5rem;
3  background: #007bff;
4  color: white;
5  border: none;
6  border-radius: 4px;
7  cursor: pointer;
8  transition: all 0.3s ease;
9
10  /* États hover et focus */
11  &:hover {
12    background: #0056b3;
13    transform: translateY(-1px);
14  }
15
16  &:focus {
17    outline: none;
18    box-shadow: 0 0 0 3px rgba(0, 123, 255, 0.25);
19  }
20
21  &:active {
22    transform: translateY(0);
23  }
24
25  /* Variantes de bouton */
26  &.secondary {
27    background: #6c757d;
28
29    &:hover {
30      background: #545b62;
31    }
32  }
33
34  &.danger {
35    background: #dc3545;
36
37    &:hover {
38      background: #c82333;
39    }
40  }
41
42  /* Modificateurs d'état */
43  &.loading {
44    position: relative;
45    color: transparent;
46
47    &::after {
48      content: '';
49      position: absolute;
50      top: 50%;
51      left: 50%;
52      width: 16px;
53      height: 16px;
54      border: 2px solid #ffffff;
55      border-top: 2px solid transparent;
56      border-radius: 50%;
57      animation: spin 1s linear infinite;
58      transform: translate(-50%, -50%);
59    }
60  }
61
62  &:disabled {
63    opacity: 0.6;
64    cursor: not-allowed;
65
66    &:hover {
67      background: #007bff;
68      transform: none;
69    }
70  }
71}
72
73@keyframes spin {
74  0% { transform: translate(-50%, -50%) rotate(0deg); }
75  100% { transform: translate(-50%, -50%) rotate(360deg); }
76}

&:hover sans espace désigne le même bouton à l'état survolé, et &.secondary un bouton qui possède en même temps la classe secondary. Attention à l'espace : & :hover désignerait n'importe quel descendant du bouton situé sous le curseur. Il y a aussi une différence importante avec Sass : en CSS natif, tu ne peux pas coller un nom de classe avec &__title, cela ne fonctionne que dans le préprocesseur.

Techniques avancées d'imbrication

Combinateurs et pseudo-sélecteurs

Tu peux tout imbriquer : des sélecteurs descendants, des pseudo-classes, des combinateurs, et même des media queries. Voici toute une navigation avec un menu déroulant dans un seul bloc :

1.navigation {
2  display: flex;
3  align-items: center;
4  padding: 1rem 2rem;
5  background: #ffffff;
6  box-shadow: 0 2px 4px rgba(0,0,0,0.1);
7
8  .logo {
9    font-size: 1.5rem;
10    font-weight: bold;
11    color: #007bff;
12    text-decoration: none;
13
14    &:hover {
15      color: #0056b3;
16    }
17  }
18
19  .nav-list {
20    display: flex;
21    list-style: none;
22    margin: 0 0 0 auto;
23    padding: 0;
24    gap: 2rem;
25
26    .nav-item {
27      position: relative;
28
29      .nav-link {
30        display: block;
31        padding: 0.5rem 0;
32        color: #333;
33        text-decoration: none;
34        font-weight: 500;
35        transition: color 0.3s ease;
36
37        &:hover {
38          color: #007bff;
39        }
40
41        &.active {
42          color: #007bff;
43
44          &::after {
45            content: '';
46            position: absolute;
47            bottom: -8px;
48            left: 0;
49            right: 0;
50            height: 2px;
51            background: #007bff;
52          }
53        }
54      }
55
56      /* Menu déroulant */
57      .dropdown {
58        position: absolute;
59        top: 100%;
60        left: 0;
61        min-width: 200px;
62        background: white;
63        border-radius: 8px;
64        box-shadow: 0 4px 12px rgba(0,0,0,0.15);
65        opacity: 0;
66        visibility: hidden;
67        transform: translateY(-10px);
68        transition: all 0.3s ease;
69        z-index: 1000;
70
71        .dropdown-item {
72          .dropdown-link {
73            display: block;
74            padding: 0.75rem 1rem;
75            color: #333;
76            text-decoration: none;
77
78            &:hover {
79              background: #f8f9fa;
80              color: #007bff;
81            }
82
83            &:first-child {
84              border-radius: 8px 8px 0 0;
85            }
86
87            &:last-child {
88              border-radius: 0 0 8px 8px;
89            }
90          }
91        }
92      }
93
94      /* Afficher le menu déroulant au survol */
95      &:hover .dropdown {
96        opacity: 1;
97        visibility: visible;
98        transform: translateY(0);
99      }
100    }
101  }
102
103  /* Navigation réactive */
104  .mobile-toggle {
105    display: none;
106    flex-direction: column;
107    gap: 4px;
108    background: none;
109    border: none;
110    cursor: pointer;
111
112    .bar {
113      width: 25px;
114      height: 3px;
115      background: #333;
116      transition: all 0.3s ease;
117    }
118
119    &.active {
120      .bar:nth-child(1) {
121        transform: rotate(45deg) translate(5px, 5px);
122      }
123
124      .bar:nth-child(2) {
125        opacity: 0;
126      }
127
128      .bar:nth-child(3) {
129        transform: rotate(-45deg) translate(7px, -6px);
130      }
131    }
132  }
133
134  @media (max-width: 768px) {
135    .nav-list {
136      position: fixed;
137      top: 70px;
138      left: 0;
139      right: 0;
140      flex-direction: column;
141      background: white;
142      padding: 1rem 2rem;
143      transform: translateY(-100%);
144      transition: transform 0.3s ease;
145      box-shadow: 0 4px 12px rgba(0,0,0,0.15);
146
147      &.active {
148        transform: translateY(0);
149      }
150
151      .nav-item {
152        .dropdown {
153          position: static;
154          opacity: 1;
155          visibility: visible;
156          transform: none;
157          box-shadow: none;
158          background: #f8f9fa;
159          margin-top: 0.5rem;
160        }
161      }
162    }
163
164    .mobile-toggle {
165      display: flex;
166    }
167  }
168}

La règle &:hover .dropdown affiche le menu quand le curseur se trouve au-dessus d'un élément de la liste. Fais toutefois attention à la profondeur : .dropdown-link se trouve six niveaux plus bas, donc le sélecteur obtenu a la spécificité de six classes et il est difficile à surcharger. C'est un exemple de ce que nous éviterons dans les bonnes pratiques.

Imbrication avec les Media Queries

Une media query imbriquée dans une règle concerne précisément cette règle. Les déclarations écrites directement à l'intérieur de @media s'appliquent au sélecteur parent :

1.hero-section {
2  display: flex;
3  align-items: center;
4  min-height: 100vh;
5  padding: 2rem;
6  background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
7  color: white;
8
9  .hero-content {
10    max-width: 1200px;
11    margin: 0 auto;
12    text-align: center;
13
14    .hero-title {
15      font-size: 3.5rem;
16      font-weight: 700;
17      margin-bottom: 1.5rem;
18      line-height: 1.2;
19
20      @media (max-width: 768px) {
21        font-size: 2.5rem;
22      }
23
24      @media (max-width: 480px) {
25        font-size: 2rem;
26      }
27    }
28
29    .hero-subtitle {
30      font-size: 1.5rem;
31      font-weight: 300;
32      margin-bottom: 2rem;
33      opacity: 0.9;
34
35      @media (max-width: 768px) {
36        font-size: 1.25rem;
37      }
38
39      @media (max-width: 480px) {
40        font-size: 1.1rem;
41      }
42    }
43
44    .hero-buttons {
45      display: flex;
46      gap: 1rem;
47      justify-content: center;
48      flex-wrap: wrap;
49
50      @media (max-width: 480px) {
51        flex-direction: column;
52        align-items: center;
53      }
54
55      .btn-primary {
56        padding: 1rem 2rem;
57        font-size: 1.1rem;
58        background: rgba(255, 255, 255, 0.2);
59        border: 2px solid white;
60        color: white;
61        border-radius: 50px;
62        text-decoration: none;
63        transition: all 0.3s ease;
64        backdrop-filter: blur(10px);
65
66        &:hover {
67          background: white;
68          color: #667eea;
69          transform: translateY(-2px);
70          box-shadow: 0 8px 25px rgba(0,0,0,0.2);
71        }
72
73        @media (max-width: 480px) {
74          width: 200px;
75        }
76      }
77
78      .btn-secondary {
79        padding: 1rem 2rem;
80        font-size: 1.1rem;
81        background: transparent;
82        border: 2px solid rgba(255, 255, 255, 0.5);
83        color: white;
84        border-radius: 50px;
85        text-decoration: none;
86        transition: all 0.3s ease;
87
88        &:hover {
89          border-color: white;
90          background: rgba(255, 255, 255, 0.1);
91        }
92
93        @media (max-width: 480px) {
94          width: 200px;
95        }
96      }
97    }
98  }
99
100  @media (max-width: 768px) {
101    padding: 1rem;
102    min-height: 80vh;
103  }
104}

Le titre et le sous-titre rétrécissent à 768px et à 480px, et la dernière media query modifie les espacements de la section elle-même. Chaque breakpoint se trouve à côté du style qu'il modifie, tu n'as donc pas besoin de faire défiler le fichier jusqu'à un bloc @media séparé à la fin.

Imbriquer @container

Tu imbriqueras de la même façon les Container Queries de la leçon précédente. Rappelons l'ordre : un conteneur avec container-type et un nom facultatif, la requête @container, les styles des enfants, puis un test à différentes largeurs :

1.card-wrapper {
2  container: card / inline-size;
3}
4
5.card-title {
6  font-size: 1.25rem;
7
8  @container card (min-width: 400px) {
9    font-size: 5cqw;
10  }
11}

Quand le conteneur card mesure au moins 400px, le titre reçoit 5cqw, soit 5% de la largeur du conteneur. Les autres unités fonctionnent de la même manière : cqh se base sur la hauteur, et cqi et cqb sur les dimensions inline et block.

Exemples pratiques d'imbrication

Formulaire complexe

Dans un gros composant, l'imbrication montre la structure comme un plan des chambres. Tout le formulaire de contact, avec ses champs, ses états et ses boutons, tient dans une seule règle :

1.contact-form {
2  max-width: 600px;
3  margin: 2rem auto;
4  padding: 2rem;
5  background: white;
6  border-radius: 12px;
7  box-shadow: 0 8px 25px rgba(0,0,0,0.1);
8
9  .form-header {
10    text-align: center;
11    margin-bottom: 2rem;
12
13    h2 {
14      font-size: 2rem;
15      color: #333;
16      margin-bottom: 0.5rem;
17    }
18
19    p {
20      color: #666;
21      font-size: 1.1rem;
22    }
23  }
24
25  .form-row {
26    display: flex;
27    gap: 1rem;
28    margin-bottom: 1.5rem;
29
30    @media (max-width: 600px) {
31      flex-direction: column;
32    }
33
34    .form-group {
35      flex: 1;
36
37      &.full-width {
38        flex-basis: 100%;
39      }
40    }
41  }
42
43  .form-group {
44    margin-bottom: 1.5rem;
45
46    label {
47      display: block;
48      margin-bottom: 0.5rem;
49      font-weight: 600;
50      color: #333;
51
52      .required {
53        color: #e74c3c;
54        margin-left: 0.25rem;
55      }
56    }
57
58    input, textarea, select {
59      width: 100%;
60      padding: 0.75rem;
61      border: 2px solid #e1e5e9;
62      border-radius: 8px;
63      font-size: 1rem;
64      transition: all 0.3s ease;
65      background: #fafbfc;
66
67      &:focus {
68        outline: none;
69        border-color: #007bff;
70        background: white;
71        box-shadow: 0 0 0 3px rgba(0, 123, 255, 0.1);
72      }
73
74      &::placeholder {
75        color: #adb5bd;
76      }
77
78      &.error {
79        border-color: #e74c3c;
80        background: #fff5f5;
81
82        &:focus {
83          box-shadow: 0 0 0 3px rgba(231, 76, 60, 0.1);
84        }
85      }
86
87      &.success {
88        border-color: #28a745;
89        background: #f8fff9;
90
91        &:focus {
92          box-shadow: 0 0 0 3px rgba(40, 167, 69, 0.1);
93        }
94      }
95    }
96
97    textarea {
98      min-height: 120px;
99      resize: vertical;
100    }
101
102    select {
103      cursor: pointer;
104      background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 20 20'%3e%3cpath stroke='%236b7280' stroke-linecap='round' stroke-linejoin='round' stroke-width='1.5' d='m6 8 4 4 4-4'/%3e%3c/svg%3e");
105      background-position: right 0.5rem center;
106      background-repeat: no-repeat;
107      background-size: 1.5em 1.5em;
108      padding-right: 2.5rem;
109    }
110
111    .error-message {
112      display: block;
113      margin-top: 0.5rem;
114      color: #e74c3c;
115      font-size: 0.875rem;
116    }
117
118    .help-text {
119      display: block;
120      margin-top: 0.5rem;
121      color: #6c757d;
122      font-size: 0.875rem;
123    }
124  }
125
126  .checkbox-group {
127    margin: 1.5rem 0;
128
129    .checkbox-item {
130      display: flex;
131      align-items: center;
132      gap: 0.75rem;
133      margin-bottom: 1rem;
134
135      input[type="checkbox"] {
136        width: auto;
137        margin: 0;
138
139        &:checked + label::before {
140          background-color: #007bff;
141          border-color: #007bff;
142        }
143      }
144
145      label {
146        margin: 0;
147        cursor: pointer;
148        user-select: none;
149
150        a {
151          color: #007bff;
152          text-decoration: none;
153
154          &:hover {
155            text-decoration: underline;
156          }
157        }
158      }
159    }
160  }
161
162  .form-actions {
163    display: flex;
164    gap: 1rem;
165    justify-content: flex-end;
166    margin-top: 2rem;
167
168    @media (max-width: 480px) {
169      flex-direction: column;
170    }
171
172    .btn {
173      padding: 0.75rem 2rem;
174      border: none;
175      border-radius: 8px;
176      font-size: 1rem;
177      font-weight: 600;
178      cursor: pointer;
179      transition: all 0.3s ease;
180      text-decoration: none;
181      display: inline-block;
182      text-align: center;
183
184      &.btn-primary {
185        background: #007bff;
186        color: white;
187
188        &:hover:not(:disabled) {
189          background: #0056b3;
190          transform: translateY(-1px);
191        }
192
193        &:disabled {
194          opacity: 0.6;
195          cursor: not-allowed;
196        }
197      }
198
199      &.btn-secondary {
200        background: #f8f9fa;
201        color: #6c757d;
202        border: 1px solid #dee2e6;
203
204        &:hover {
205          background: #e9ecef;
206          color: #495057;
207        }
208      }
209
210      @media (max-width: 480px) {
211        width: 100%;
212      }
213    }
214  }
215
216  .loading-overlay {
217    position: absolute;
218    top: 0;
219    left: 0;
220    right: 0;
221    bottom: 0;
222    background: rgba(255, 255, 255, 0.9);
223    display: flex;
224    align-items: center;
225    justify-content: center;
226    border-radius: 12px;
227
228    .spinner {
229      width: 40px;
230      height: 40px;
231      border: 4px solid #f3f3f3;
232      border-top: 4px solid #007bff;
233      border-radius: 50%;
234      animation: spin 1s linear infinite;
235    }
236  }
237}

La liste input, textarea, select reçoit des styles communs, et &:focus, &::placeholder et &.error décrivent leurs états. L'écriture &:checked + label::before montre qu'après &, tu peux utiliser n'importe quel combinateur, ici celui du voisin direct.

Composant carte avec variantes

Les variantes et les états d'un composant sont l'endroit naturel pour &. Remarque la règle dans laquelle & se trouve à la fin du sélecteur :

1.card {
2  background: white;
3  border-radius: 12px;
4  overflow: hidden;
5  transition: all 0.3s ease;
6  position: relative;
7
8  /* Variantes d'ombre */
9  &.shadow-sm {
10    box-shadow: 0 1px 3px rgba(0,0,0,0.1);
11  }
12
13  &.shadow-md {
14    box-shadow: 0 4px 6px rgba(0,0,0,0.1);
15  }
16
17  &.shadow-lg {
18    box-shadow: 0 10px 25px rgba(0,0,0,0.15);
19  }
20
21  /* Variantes interactives */
22  &.hoverable {
23    cursor: pointer;
24
25    &:hover {
26      transform: translateY(-4px);
27      box-shadow: 0 12px 30px rgba(0,0,0,0.2);
28    }
29  }
30
31  &.clickable {
32    cursor: pointer;
33    user-select: none;
34
35    &:active {
36      transform: scale(0.98);
37    }
38  }
39
40  /* Composants de la carte */
41  .card-header {
42    padding: 1.5rem;
43    border-bottom: 1px solid #f0f0f0;
44
45    &.with-image {
46      padding: 0;
47      position: relative;
48
49      .header-image {
50        width: 100%;
51        height: 200px;
52        object-fit: cover;
53      }
54
55      .header-content {
56        position: absolute;
57        bottom: 0;
58        left: 0;
59        right: 0;
60        padding: 1.5rem;
61        background: linear-gradient(transparent, rgba(0,0,0,0.7));
62        color: white;
63      }
64    }
65
66    .card-title {
67      font-size: 1.5rem;
68      font-weight: 600;
69      margin: 0 0 0.5rem 0;
70      color: #333;
71
72      .card-header.with-image & {
73        color: white;
74      }
75    }
76
77    .card-subtitle {
78      font-size: 1rem;
79      color: #666;
80      margin: 0;
81
82      .card-header.with-image & {
83        color: rgba(255,255,255,0.9);
84      }
85    }
86  }
87
88  .card-body {
89    padding: 1.5rem;
90
91    .card-text {
92      line-height: 1.6;
93      color: #555;
94      margin-bottom: 1rem;
95
96      &:last-child {
97        margin-bottom: 0;
98      }
99    }
100
101    .card-meta {
102      display: flex;
103      align-items: center;
104      gap: 1rem;
105      margin-bottom: 1rem;
106      font-size: 0.9rem;
107      color: #666;
108
109      .meta-item {
110        display: flex;
111        align-items: center;
112        gap: 0.25rem;
113
114        .icon {
115          width: 16px;
116          height: 16px;
117        }
118      }
119    }
120
121    .card-tags {
122      display: flex;
123      flex-wrap: wrap;
124      gap: 0.5rem;
125      margin-bottom: 1rem;
126
127      .tag {
128        padding: 0.25rem 0.75rem;
129        background: #f8f9fa;
130        color: #495057;
131        border-radius: 20px;
132        font-size: 0.8rem;
133        font-weight: 500;
134
135        &.primary {
136          background: #e3f2fd;
137          color: #1976d2;
138        }
139
140        &.success {
141          background: #e8f5e8;
142          color: #2e7d32;
143        }
144
145        &.warning {
146          background: #fff3e0;
147          color: #f57c00;
148        }
149
150        &.danger {
151          background: #ffebee;
152          color: #d32f2f;
153        }
154      }
155    }
156  }
157
158  .card-footer {
159    padding: 1rem 1.5rem;
160    background: #f8f9fa;
161    border-top: 1px solid #f0f0f0;
162
163    .card-actions {
164      display: flex;
165      justify-content: space-between;
166      align-items: center;
167      gap: 1rem;
168
169      @media (max-width: 480px) {
170        flex-direction: column;
171        align-items: stretch;
172      }
173
174      .action-group {
175        display: flex;
176        gap: 0.5rem;
177
178        @media (max-width: 480px) {
179          justify-content: center;
180        }
181      }
182
183      .btn {
184        padding: 0.5rem 1rem;
185        border: none;
186        border-radius: 6px;
187        font-size: 0.9rem;
188        cursor: pointer;
189        text-decoration: none;
190        transition: all 0.2s ease;
191
192        &.btn-primary {
193          background: #007bff;
194          color: white;
195
196          &:hover {
197            background: #0056b3;
198          }
199        }
200
201        &.btn-outline {
202          background: transparent;
203          color: #007bff;
204          border: 1px solid #007bff;
205
206          &:hover {
207            background: #007bff;
208            color: white;
209          }
210        }
211
212        &.btn-ghost {
213          background: transparent;
214          color: #6c757d;
215
216          &:hover {
217            background: #e9ecef;
218            color: #495057;
219          }
220        }
221      }
222    }
223  }
224
225  /* États spéciaux */
226  &.loading {
227    pointer-events: none;
228
229    &::after {
230      content: '';
231      position: absolute;
232      top: 0;
233      left: 0;
234      right: 0;
235      bottom: 0;
236      background: rgba(255,255,255,0.8);
237      display: flex;
238      align-items: center;
239      justify-content: center;
240    }
241  }
242
243  &.disabled {
244    opacity: 0.6;
245    pointer-events: none;
246  }
247}

L'écriture .card-header.with-image & signifie : ce titre, mais seulement lorsqu'il se trouve à l'intérieur d'un en-tête avec une image. Le symbole & n'a donc pas besoin d'être au début.

Bonnes pratiques du CSS Nesting

1. Limiter la profondeur d'imbrication

Chaque niveau d'imbrication ajoute une classe au sélecteur et augmente la spécificité. Compare une profondeur raisonnable et une profondeur excessive :

1/* Bien - 3 à 4 niveaux maximum */
2.header {
3  .navigation {
4    .nav-item {
5      .nav-link {
6        /* niveau maximum */
7      }
8    }
9  }
10}
11
12/* Mauvais - imbrication trop profonde */
13.page {
14  .content {
15    .section {
16      .container {
17        .row {
18          .column {
19            .card {
20              .header {
21                /* trop profond ! */
22              }
23            }
24          }
25        }
26      }
27    }
28  }
29}

Trois ou quatre niveaux, c'est la limite. Tu imposeras automatiquement la règle max-nesting-depth dans la leçon sur Stylelint.

2. Utiliser des noms de sélecteurs explicites

Des noms courts comme .title ou .img se confondent facilement avec les classes d'autres composants. Mieux vaut les préfixer par le nom du composant :

1/* Bien */
2.product-card {
3  .product-image {
4    /* styles */
5  }
6
7  .product-info {
8    .product-title { /* styles */ }
9    .product-price { /* styles */ }
10  }
11}
12
13/* Mauvais */
14.card {
15  .img {
16    /* styles */
17  }
18
19  .info {
20    .title { /* styles */ }
21    .price { /* styles */ }
22  }
23}

Les noms préfixés par product- indiquent immédiatement à quoi ils appartiennent, et n'entrent pas en conflit avec d'autres parties de la page.

3. Regrouper les styles liés

À l'intérieur d'un même bloc, garde un ordre : d'abord la mise en page, puis les parties du composant, et enfin les états et les animations, séparés par des commentaires :

1.modal {
2  /* Styles de mise en page de base */
3  position: fixed;
4  top: 0;
5  left: 0;
6  width: 100%;
7  height: 100%;
8  z-index: 1000;
9
10  /* Arrière-plan et superposition */
11  .modal-backdrop {
12    position: absolute;
13    top: 0;
14    left: 0;
15    width: 100%;
16    height: 100%;
17    background: rgba(0,0,0,0.5);
18    backdrop-filter: blur(4px);
19  }
20
21  /* Contenu de la modale */
22  .modal-content {
23    position: relative;
24    max-width: 500px;
25    margin: 5% auto;
26    background: white;
27    border-radius: 12px;
28    overflow: hidden;
29
30    /* Composants de la modale */
31    .modal-header {
32      /* styles */
33    }
34
35    .modal-body {
36      /* styles */
37    }
38
39    .modal-footer {
40      /* styles */
41    }
42  }
43
44  /* États et animations */
45  &.fade-in {
46    animation: modalFadeIn 0.3s ease;
47  }
48
49  &.fade-out {
50    animation: modalFadeOut 0.3s ease;
51  }
52}

CSS Nesting améliore considérablement l'organisation et la lisibilité du code CSS, permettant de créer des feuilles de style plus intuitives et plus faciles à maintenir. La clé est de faire preuve de modération et de suivre les bonnes pratiques. Mon conseil : dans les nouveaux projets sans préprocesseur, utilise sans hésiter l'imbrication native, mais garde en tête les différences avec Sass. Dans la prochaine leçon, tu découvriras les Cascade Layers, qui mettent de l'ordre dans des groupes entiers de styles.

Souviens-toi : l'imbrication ressemble à la descente dans les chambres d'une pyramide, car un ou deux étages plus bas, c'est pratique, mais celui qui descend trop profond perd son chemin.

Code de cette leçon : index.html
1<!DOCTYPE html>
2<html lang="en">
3<head>
4    <meta charset="UTF-8">
5    <title>Sélecteur :has()</title>
6    <link rel="stylesheet" href="style.css">
7</head>
8<body>
9    <h1>Sélecteur :has() - le parent réagit aux enfants</h1>
10    <article>
11        <h2>Pyramide de Khéops</h2>
12        <div class="img-placeholder">IMG</div>
13        <p>L'une des sept merveilles du monde.</p>
14    </article>
15    <article>
16        <h2>Sphinx</h2>
17        <p>Gardien des pyramides, sans illustration.</p>
18    </article>
19    <article>
20        <h2>Temple de Karnak</h2>
21        <div class="video-placeholder">VIDEO</div>
22        <p>Le plus grand complexe de temples.</p>
23    </article>
24</body>
25</html>

Tu as repéré une erreur dans cette leçon ?

Teste tes connaissances

Réponds aux questions de cette leçon. Choisis une réponse pour voir tout de suite si elle est correcte.

  1. 1. Qu'est-ce que le CSS Nesting natif ?

  2. 2. Quelle unité de container query fait référence à la largeur du conteneur ?

Tâches pratiques dans le jeu

  • Tri vertical

    Ordonne la syntaxe correcte du CSS Nesting :

  • Clique dans l'ordre

    Ordonne la syntaxe correcte d'un pseudo-sélecteur imbriqué en CSS Nesting :

  • Tri vertical

    Ordonne les étapes d'utilisation des Container Queries :

Articles utiles