Si tu bosses sur un design system, une app qui vit, ou juste un gros front un peu ancien, tu connais le film. Les classes globales mordent là où il ne faut pas. On rajoute des préfixes. On invente du BEM de plus en plus long. On augmente la spécificité pour « gagner » un fight… et on se réveille six mois plus tard avec un CSS qui fait peur à tout le monde.
Ce que WebKit vient de pousser avec les name-only @container queries (Safari 26.4), c’est une idée simple et franchement séduisante : utiliser uniquement le nom d’un container pour appliquer des styles, sans aucune condition de taille. Autrement dit, détourner @container en mécanisme de scope. Pas pour faire du responsive. Pour arrêter les « guerres de noms ».
Name-only @container : ce que ça change (et pourquoi c’est un vrai sujet de scope)
Une container query classique, c’est « applique ce bloc si mon container fait au moins 600px » (ou une condition similaire). Le problème, c’est que ça ne répond pas à un besoin hyper courant : isoler des styles de composant sans dépendre d’une convention de nommage agressive, et sans tooling (CSS Modules, Shadow DOM, CSS-in-JS, etc.).
Le mode name-only permet d’écrire une règle du type « applique ce bloc quand je suis dans un container nommé X », point. Pas de parenthèses. Pas de condition. Tu obtiens un filtre de cascade basé sur le contexte, et ça te donne une option de plus entre « tout est global » et « je compile tout en hashed classnames ».
Je le dis comme je le pense : sur un design system partagé entre plusieurs produits, ou sur une app avec des pages assemblées façon Lego, c’est potentiellement un gros levier. Mais seulement si tu le cadres. Sinon tu déplaces juste le chaos… dans les noms de containers.
Sponsorisé par Le Scribouillard
Besoin de contenu optimisé SEO ?
Utilisez la meilleure plateforme française de création de contenu assistée par IA ! Et générez des articles pour moins de 1€ !
Le principe concret : container-name comme “frontière” de styles
Pour utiliser ce pattern, tu as besoin d’un ancêtre qui définit un container-name. Ensuite, n’importe quel élément à l’intérieur peut bénéficier de styles encapsulés via @container avec ce nom.
La sensation côté code est assez différente d’un préfixage type .ds-Button .ds-Button__label. Tu peux garder des sélecteurs plus simples, plus lisibles, parce que c’est le container qui fait office de namespace.
/* Le wrapper du composant pose la frontière */
.ds-button {
container-name: ds-button;
}
/* Styles “scopés” : ne s’appliquent que dans un container nommé ds-button */
@container ds-button {
.label { font-weight: 600; }
.icon { width: 1em; height: 1em; }
.label + .icon { margin-left: .5rem; }
}
Le point important à comprendre : tu n’as pas « créé un Shadow DOM ». Tu as juste ajouté une condition de contexte à la cascade. Si tes sélecteurs à l’intérieur sont trop génériques (.title, .content, .row…), ce n’est pas grave tant que tu restes dans ce container. Là où ça devient dangereux, c’est si tu poses le même container-name ailleurs par accident, ou si tu l’utilises comme un attrape-tout global.
Pourquoi c’est différent de BEM, CSS Modules ou :where()
BEM règle un problème de collision en rendant les noms uniques. Ça marche, mais sur un vrai DS, ça produit vite des classes interminables, des variantes qui explosent (--compact, --dense, --inverted, etc.), et une pression sociale du style « on a le droit d’ajouter une classe ou pas ? ».
CSS Modules (ou du CSS-in-JS) règle le problème en changeant le modèle : tu compiles, tu scopes automatiquement, tu relies style et composant. C’est solide, mais tu payes avec du tooling, de l’intégration, des conventions d’import, parfois une DX moins directe (et sur certains environnements, ce n’est juste pas négociable).
Et les solutions type :where(.Scope) .title ou .Scope :is(.title) restent des solutions « par classe », donc tu retombes sur les mêmes débats de naming et de « qui est responsable de poser le bon wrapper ».
Le name-only @container est intéressant parce qu’il crée une frontière de style sans te forcer à préfixer tous tes sélecteurs. Tu gardes un CSS plus proche de l’intention (« dans Button, la label ressemble à ça ») au lieu d’un CSS qui raconte sa propre stratégie de survie.
Patterns qui marchent vraiment sur un design system “qui vit”
1) Composants imbriqués sans collision de sous-classes
Un cas classique : Card contient un Button, et les deux ont une .title ou une .label interne parce que, oui, ce sont des mots normaux. En global, c’est la bagarre assurée, donc on invente des conventions artificielles. Avec le name-only, tu peux avoir @container ds-card et @container ds-button avec des sélecteurs internes simples, et ils cohabitent sans se marcher dessus tant que chaque composant pose son container-name sur son wrapper.
Ce pattern est particulièrement agréable quand tu as des composants « layout » (Card, Panel, Sidebar, Modal) qui embarquent des composants « controls » (Button, Input, Tabs). Tu réduis drastiquement le risque qu’un refactor visuel d’un composant casse un autre par effet de bord.
2) Variantes de composant sans multiplier les classes publiques
Une autre guerre connue, c’est « combien de classes publiques on expose ? ». Si tu commences à exposer .Button + .Button--primary + .Button--danger + .Button--quiet, tu finis avec un API CSS à maintenir comme une API produit.
Avec un container name-only, tu peux poser une variante via une donnée (classe, attribut, data-variant) sur le wrapper, mais garder le style bien enfermé dans le container. L’idée n’est pas d’éliminer les variantes, c’est de limiter leur rayon d’explosion.
3) Slots et “children” qui devraient rester stylables
Les slots (au sens large) sont le coin où tout le monde triche. Tu veux permettre à un consumer d’injecter un bout de DOM (un icon, un badge, un texte riche), et tu veux quand même que le composant sache l’habiller un minimum. Si tu fais ça en global, tu te retrouves avec des sélecteurs très larges et un risque énorme de dégâts collatéraux.
Le name-only @container te permet d’écrire des règles « dans ce composant, si on trouve un truc qui ressemble à un badge, voilà comment on l’aligne » sans que ça devienne une règle globale « tous les badges de l’app sont comme ça ».
L’introduire sans tout refactor : stratégie progressive (et pas héroïque)
Le bon move, c’est d’en faire un progressive enhancement. Tu ne réécris pas ton DS. Tu choisis un ou deux composants à haut risque de collision (souvent Button, Card, Modal), tu leur ajoutes un wrapper stable qui pose container-name, et tu commences à déplacer les règles qui causent des conflits.
Concrètement, tu peux garder tes styles existants, puis ajouter par-dessus des règles name-only, protégées par @supports. Comme ça, les navigateurs qui ne supportent pas la feature gardent le comportement actuel. Les navigateurs modernes appliquent le scope plus propre.
/* Base : fonctionne partout */
.ds-button .label { font-weight: 600; }
/* Progressive enhancement : scope par container-name si supporté */
@supports (container-name: ds-button) {
.ds-button { container-name: ds-button; }
@container ds-button {
.label { font-weight: 600; }
}
}
Oui, tu vois le doublon dans cet exemple. Et c’est justement ça le trade-off réel : pendant une période, tu vas accepter de porter un peu de redondance pour migrer proprement. En échange, tu réduis le risque de casser la prod sur des navigateurs qui traînent, et tu peux migrer composant par composant.
Les pièges (ceux qui vont te coûter du temps si tu les sous-estimes)
Premier piège : la lisibilité. Un CSS préfixé te dit immédiatement « ce style appartient à Button ». Avec un name-only @container, tu dois avoir le réflexe de chercher le container correspondant. Ce n’est pas dramatique, mais ça change tes habitudes de debug. Si ton équipe n’a pas une culture CSS solide, ça peut devenir un nouveau type de confusion.
Deuxième piège : les collisions… mais au niveau des containers. Si tu nommes tes containers card, button, modal sans convention, tu vas finir par avoir le même type de guerre qu’avec les classes globales. Mon conseil terrain : impose une convention dès le début, même simple, du genre ds-* ou acme-*, et évite les noms trop génériques.
Troisième piège : la cascade reste la cascade. Le scope par container réduit les dégâts, mais n’annule pas les fights de spécificité à l’intérieur d’un composant. Si tu écris des règles trop agressives, tu peux toujours te tirer une balle dans le pied. Ce n’est pas magique, c’est juste une frontière.
Quatrième piège : la compat navigateur. WebKit annonce Safari 26.4. Chrome en parle côté beta (Chrome 148). Tant que ce n’est pas largement stable partout, tu ne peux pas en faire un socle exclusif. Par contre tu peux très bien t’en servir comme couche d’amélioration progressive, et ça, c’est rentable assez vite.
Cinquième piège : le faux sentiment de “scoping complet”. Ça ne remplace pas le Shadow DOM quand tu as besoin d’une isolation stricte, ni CSS Modules quand tu veux une garantie « build-time ». C’est un outil CSS natif pour mieux segmenter la cascade, pas une sandbox.
Mon avis : une bonne idée, mais à condition de la traiter comme une convention d’équipe
Si tu l’introduis en mode freestyle, tu vas juste ajouter une couche mentale de plus. Si tu l’introduis avec une convention de nommage claire, deux-trois règles de code review, et une stratégie de migration progressive, tu peux vraiment calmer un design system qui commence à sentir le stress.
Le truc que je ferais en premier sur un projet réel : choisir 2 composants très utilisés, poser container-name sur leur root, migrer uniquement les styles « à risque » (ceux qui ont déclenché des collisions), et valider sur iOS Safari en conditions réelles. Parce que oui, les surprises CSS, ça n’arrive pas dans un simulateur, ça arrive sur le téléphone du PM juste avant une démo.
Conclusion : un “namespace CSS” natif, ça se mérite
Les name-only container queries ne vont pas tuer BEM, ni remplacer CSS Modules. Mais elles ajoutent une option très pragmatique dans l’arsenal : scoper des styles par contexte, proprement, sans transformer ton HTML en roman et sans tooling.
Si tu as déjà un design system en prod, le bon angle n’est pas « on refait tout ». C’est « où est-ce que la cascade me coûte cher aujourd’hui ? ». Là, le name-only @container peut être une réponse étonnamment simple. Et si tu le fais bien, tu peux réduire les guerres de noms… sans en déclencher de nouvelles.