Les fonctions CSS qui suppriment du JavaScript. Chacune remplace un schema qui demandait, il y a deux ans, une bibliotheque ou plusieurs dizaines de lignes. Chaque fiche indique son etat de disponibilite, et la regle tenue partout est la meme : une fonction recente est employee en amelioration, jamais en fondation. Le repli n'est pas une degradation, c'est le comportement normal du site pour qui n'a pas la derniere version de son navigateur.
A. Elements flottants sans JavaScript
Popover natif, ancrage de position avec repli automatique, info-bulle complete sans une ligne de code.
Panneau natif
Fermeture au clic exterieur et sur Echap, sans une ligne de code. La pile d'empilement est geree par le navigateur.
Popover natif
L'attribut popover donne le comportement complet d'un panneau flottant : ouverture, fermeture au clic exterieur, fermeture sur Echap, gestion du focus et de la pile. Zero ligne de JavaScript.
Voir le codeMasquer le code
<div class="cm-demo">
<button class="btn btn--secondaire" popovertarget="pop-demo">Ouvrir le panneau <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="m6 9 6 6 6-6"/></svg></button>
<div id="pop-demo" popover class="cm-pop">
<strong>Panneau natif</strong>
<p>Fermeture au clic exterieur et sur Echap, sans une ligne de code. La pile d'empilement est geree par le navigateur.</p>
<button class="btn btn--plein btn--sm" popovertarget="pop-demo" popovertargetaction="hide">Fermer</button>
</div>
</div>
Le panneau se positionne par rapport a son declencheur, et bascule tout seul quand il manque de place. C'est ce qui remplace les bibliotheques de positionnement flottant et leurs calculs au defilement.
Voir le codeMasquer le code
<div class="cm-demo">
<button class="btn btn--secondaire cm-ancre" popovertarget="pop-ancre" style="anchor-name:--a1">En bas <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="m6 9 6 6 6-6"/></svg></button>
<div id="pop-ancre" popover class="cm-pop cm-pop--ancre" style="position-anchor:--a1">
<strong>Ancre automatique</strong>
<p>Positionne sous le bouton, bascule au-dessus s'il n'y a pas la place en bas.</p>
</div>
<span class="cm-note">position-area: block-end span-inline-end<br>position-try-fallbacks: flip-block, flip-inline</span>
</div>
.cm-pop--ancre {
margin: 6px 0 0;
/* Se place sous l'ancre, aligne a gauche. */
position-area: block-end span-inline-end;
/* Bascule si la place manque, plutot que de sortir de l'ecran. */
position-try-fallbacks: flip-block, flip-inline;
}
.cm-note {
font-family: var(--font-mono);
font-size: 11px;
line-height: 1.6;
color: var(--c-text-muted);
padding: var(--sp-2) var(--sp-3);
border-radius: var(--r-sm);
background: var(--c-surface-2);
}
/* Repli : sans ancrage, le panneau reste centre a l'ecran, ce qui est le
comportement natif d'un popover. Rien ne casse. */
@supports not (position-area: block-end) {
.cm-pop--ancre { margin: 0; }
}
anchor-name sur le declencheur, position-anchor sur le panneau
Le prix indique comprend la pose et la mise en service.
Genere un fichier Excel avec la mise en page de la societe.
Info-bulle sans JavaScript
Popover et ancrage combines : la bulle suit son declencheur, se ferme seule, et se replace quand elle depasse. Le tout en CSS, avec le comportement clavier inclus.
Voir le codeMasquer le code
<div class="cm-demo">
<button class="btn btn--icone btn--secondaire" popovertarget="bulle1" style="anchor-name:--b1" aria-label="Aide"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M12 9v4M12 17h.01M10.3 3.9 1.8 18a2 2 0 0 0 1.7 3h17a2 2 0 0 0 1.7-3L13.7 3.9a2 2 0 0 0-3.4 0Z"/></svg></button>
<div id="bulle1" popover class="cm-bulle" style="position-anchor:--b1">Le prix indique comprend la pose et la mise en service.</div>
<button class="btn btn--secondaire" popovertarget="bulle2" style="anchor-name:--b2">Exporter <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4M7 10l5 5 5-5M12 15V3"/></svg></button>
<div id="bulle2" popover class="cm-bulle" style="position-anchor:--b2">Genere un fichier Excel avec la mise en page de la societe.</div>
</div>
:has() permet enfin de styler un element selon ce qu'il contient. C'est ce qui supprime la plupart des classes ajoutees en JavaScript pour refleter un etat.
Voir le codeMasquer le code
<div class="demo-col demo-col--large">
<label class="cm-carte-choix"><input type="checkbox"><span class="coche__boite"></span><span><strong>Pose comprise</strong><small>Intervention sous 3 semaines</small></span><em>+ 1 200 EUR</em></label>
<label class="cm-carte-choix"><input type="checkbox" checked><span class="coche__boite"></span><span><strong>Extension de garantie</strong><small>5 ans pieces et main d'oeuvre</small></span><em>+ 340 EUR</em></label>
<div class="cm-total">Total des options : <strong>340,00 EUR</strong></div>
<span class="cm-note">.carte:has(:checked) { border-color: var(--c-primary) }<br>form:has(:invalid) button[type=submit] { opacity: .5 }</span>
</div>
:has(:checked), :has(> img), form:has(:invalid) — disponible partout depuis 2023
/* Les regles ne s'appliquent qu'a l'interieur de .carte,
et s'arretent avant toute .carte imbriquee */
@scope (.carte) to (.carte .carte) {
h3 { font-size: 18px; font-weight: 700; }
p { color: var(--c-text-soft); }
a { color: var(--c-primary); }
}
/* :scope designe la racine, et la proximite l'emporte sur
la specificite : plus besoin de surenchere de selecteurs */
@scope (.theme-sombre) {
:scope { background: #14181f; color: #e8eef6; }
a { color: #7fb3ff; }
}
Sans @scope, isoler un composant demandait soit un prefixe sur chaque classe,
soit une surenchere de specificite. La limite haute (to) est ce qui manquait le plus :
elle permet a un composant de ne pas styler ses propres copies imbriquees.
Portee de style
@scope limite des regles a un sous-arbre, et coupe la cascade a une frontiere. Il remplace les prefixes de classe defensifs et les guerres de specificite.
Voir le codeMasquer le code
<div class="mv-code">
<pre><code>/* Les regles ne s'appliquent qu'a l'interieur de .carte,
et s'arretent avant toute .carte imbriquee */
@scope (.carte) to (.carte .carte) {
h3 { font-size: 18px; font-weight: 700; }
p { color: var(--c-text-soft); }
a { color: var(--c-primary); }
}
/* :scope designe la racine, et la proximite l'emporte sur
la specificite : plus besoin de surenchere de selecteurs */
@scope (.theme-sombre) {
:scope { background: #14181f; color: #e8eef6; }
a { color: #7fb3ff; }
}</code></pre>
<p class="thm-note">Sans <code>@scope</code>, isoler un composant demandait soit un prefixe sur chaque classe,
soit une surenchere de specificite. La limite haute (<code>to</code>) est ce qui manquait le plus :
elle permet a un composant de ne pas styler ses propres copies imbriquees.</p>
</div>
/* aucun style specifique : le socle .btn suffit */
@scope (.racine) to (.limite) { ... } — disponible dans tous les moteurs depuis 2025
Dossier 2026-103
Neuilly-sur-Marne — PAC air/eau 11 kW
Signe
Dossier 2026-103
Neuilly-sur-Marne — PAC air/eau 11 kW
Signe
Dossier 2026-103
Neuilly-sur-Marne
Signe
Requetes de conteneur
Un composant qui s'adapte a la largeur de SON conteneur, pas a celle de la fenetre. C'est la seule facon d'avoir un composant vraiment reutilisable dans une colonne etroite comme en pleine page.
container-type: inline-size sur le parent, @container (min-width: X) pour l'enfant
C. Mise en page et texte
Champ qui grandit, ajustement optique, grille en lanes, couleurs derivees, logique conditionnelle, transitions entre vues.
field-sizing: content; max-height: 200px;
Champ qui grandit
field-sizing: content fait grandir un champ ou une zone de texte avec son contenu. Il remplace le calcul de hauteur en JavaScript qu'on trouvait dans toutes les zones de saisie.
Voir le codeMasquer le code
<div class="demo-col demo-col--large">
<div class="champ"><label class="champ__label">Zone qui s'adapte</label><textarea class="saisie cm-grandit" rows="1">Cette zone grandit avec son contenu, sans une ligne de JavaScript. Ajoutez du texte pour la voir s'etendre, jusqu'a la limite fixee par max-height.</textarea></div>
<div class="champ"><label class="champ__label">Champ qui s'ajuste a la valeur</label><input class="saisie cm-grandit cm-grandit--min" value="4 280,00"></div>
<span class="cm-note">field-sizing: content; max-height: 200px;</span>
</div>
field-sizing: content — Chrome et Edge depuis 2024, Safari 26, Firefox en cours
Sans ajustement
Titre de section
Avec text-box-trim
Titre de section
Ajustement optique du texte
text-box-trim retire l'espace au-dessus des majuscules et sous la ligne de base. C'est ce qui permet d'aligner vraiment un titre sur une icone ou un bord, sans marge negative bricolee.
Voir le codeMasquer le code
<div class="cm-trim">
<div><span class="cm-trim__etiq">Sans ajustement</span><div class="cm-trim__boite"><h4>Titre de section</h4></div></div>
<div><span class="cm-trim__etiq">Avec text-box-trim</span><div class="cm-trim__boite"><h4 class="cm-coupe">Titre de section</h4></div></div>
</div>
La mise en page en briques decalees, native. Elle remplace les bibliotheques de masonry et leurs recalculs a chaque redimensionnement. Encore recente : le repli est une grille reguliere, ce qui reste parfaitement correct.
color-mix() et les couleurs relatives permettent de deduire une palette entiere d'une seule teinte. Un client fournit sa couleur, les survols, fonds doux et bordures s'en deduisent.
color-mix(in srgb, X 12%, white) et oklch(from X calc(l + .28) c h)
/* Une seule regle, trois resultats selon un attribut */
.etiquette {
background: if(
style(--ton: succes): var(--c-success-soft);
style(--ton: alerte): var(--c-warn-soft);
else: var(--c-surface-3)
);
}
/* Ce qu'on ecrivait avant : trois regles et trois classes */
.etiquette { background: var(--c-surface-3); }
.etiquette--succes { background: var(--c-success-soft); }
.etiquette--alerte { background: var(--c-warn-soft); }
Encore experimental — Chrome 137 et suivants seulement, au moment ou ces lignes
sont ecrites. La forme a trois classes reste la bonne fondation ; if() se pose par-dessus quand
il permet d'eviter une combinatoire de classes qui deviendrait ingerable.
Logique conditionnelle
if() introduit une condition dans une valeur, sans classe ni media query. Encore tres recent : a n'employer que la ou le repli est acceptable.
Voir le codeMasquer le code
<div class="mv-code">
<pre><code>/* Une seule regle, trois resultats selon un attribut */
.etiquette {
background: if(
style(--ton: succes): var(--c-success-soft);
style(--ton: alerte): var(--c-warn-soft);
else: var(--c-surface-3)
);
}
/* Ce qu'on ecrivait avant : trois regles et trois classes */
.etiquette { background: var(--c-surface-3); }
.etiquette--succes { background: var(--c-success-soft); }
.etiquette--alerte { background: var(--c-warn-soft); }</code></pre>
<p class="thm-note"><strong>Encore experimental</strong> — Chrome 137 et suivants seulement, au moment ou ces lignes
sont ecrites. La forme a trois classes reste la bonne fondation ; <code>if()</code> se pose par-dessus quand
il permet d'eviter une combinatoire de classes qui deviendrait ingerable.</p>
</div>
/* aucun style specifique : le socle .btn suffit */
if(style(--x: valeur): A; else: B) — experimental, prevoir le repli
// Une seule ligne enveloppe le changement de contenu
document.startViewTransition(() => {
liste.innerHTML = nouvellesLignes;
});
/* Chaque element qui doit etre suivi porte un nom unique */
.ligne[data-id="103"] { view-transition-name: ligne-103; }
/* La duree et la courbe se reglent en CSS */
::view-transition-group(*) {
animation-duration: 280ms;
animation-timing-function: cubic-bezier(.2,0,0,1);
}
/* Entre deux PAGES, une seule declaration suffit */
@view-transition { navigation: auto; }
C'est ce qui remplace FLIP dans la plupart des cas : le navigateur mesure les positions,
applique la difference et anime, sans qu'on ait a le faire soi-meme. Les navigateurs qui ne le connaissent
pas executent simplement le changement sans animation, ce qui reste correct.
Transitions entre vues
Les View Transitions animent le passage d'un etat a l'autre dans la meme page : le filtre change, les lignes qui restent glissent a leur nouvelle place au lieu de clignoter.
Voir le codeMasquer le code
<div class="mv-code">
<pre><code>// Une seule ligne enveloppe le changement de contenu
document.startViewTransition(() => {
liste.innerHTML = nouvellesLignes;
});
/* Chaque element qui doit etre suivi porte un nom unique */
.ligne[data-id="103"] { view-transition-name: ligne-103; }
/* La duree et la courbe se reglent en CSS */
::view-transition-group(*) {
animation-duration: 280ms;
animation-timing-function: cubic-bezier(.2,0,0,1);
}
/* Entre deux PAGES, une seule declaration suffit */
@view-transition { navigation: auto; }</code></pre>
<p class="thm-note">C'est ce qui remplace FLIP dans la plupart des cas : le navigateur mesure les positions,
applique la difference et anime, sans qu'on ait a le faire soi-meme. Les navigateurs qui ne le connaissent
pas executent simplement le changement sans animation, ce qui reste correct.</p>
</div>
/* aucun style specifique : le socle .btn suffit */