BEM. Wie scheinbar alle Techniken in der Welt der Front-End-Entwicklung kann das Schreiben von CSS im BEM-Format polarisieren. Aber es ist – zumindest in meiner Twitter-Bubble – eine der beliebteren CSS-Methoden.
Persönlich halte ich BEM für gut und denke, Sie sollten es verwenden. Aber ich verstehe auch, warum Sie es vielleicht nicht tun.
Unabhängig von Ihrer Meinung zu BEM bietet es mehrere Vorteile, der größte ist, dass es hilft, Spezifitätskollisionen in der CSS-Kaskade zu vermeiden. Das liegt daran, dass, wenn es richtig angewendet wird, alle Selektoren im BEM-Format denselben Spezifitätswert (0,1,0) haben sollten. Ich habe im Laufe der Jahre die CSS für viele große Websites entworfen (denken Sie an Regierungen, Universitäten und Banken), und gerade bei diesen größeren Projekten habe ich festgestellt, dass BEM wirklich glänzt. Das Schreiben von CSS macht viel mehr Spaß, wenn man sich darauf verlassen kann, dass die Styles, die man schreibt oder bearbeitet, nicht andere Teile der Website beeinflussen.
Es gibt tatsächlich Ausnahmen, bei denen es als völlig akzeptabel gilt, die Spezifität zu erhöhen. Zum Beispiel: die Pseudo-Klassen :hover und :focus. Diese haben einen Spezifitätswert von 0,2,0. Eine weitere sind Pseudo-Elemente – wie ::before und ::after –, die einen Spezifitätswert von 0,1,1 haben. Für den Rest dieses Artikels gehen wir jedoch davon aus, dass wir keine weitere Spezifitäts-Kriege wollen. 🤓
Aber ich bin nicht wirklich hier, um Sie von BEM zu überzeugen. Stattdessen möchte ich darüber sprechen, wie wir es zusammen mit modernen CSS-Selektoren – denken Sie an :is(), :has(), :where() usw. – verwenden können, um noch mehr Kontrolle über die Kaskade zu erhalten.
Worum geht es bei modernen CSS-Selektoren?
Die CSS Selectors Level 4 Spezifikation bietet uns einige mächtige, neue (nun ja, relativ neue) Wege, Elemente auszuwählen. Einige meiner Favoriten sind :is(), :where() und :not(), die alle von modernen Browsern unterstützt werden und heutzutage in fast jedem Projekt sicher verwendet werden können.
:is() und :where() sind im Grunde dasselbe, außer wie sie die Spezifität beeinflussen. Insbesondere hat :where() immer einen Spezifitätswert von 0,0,0. Ja, sogar :where(button#widget.some-class) hat keine Spezifität. Die Spezifität von :is() ist hingegen das Element in seiner Argumentliste mit der höchsten Spezifität. Wir haben also bereits eine Kaskaden-steuerende Unterscheidung zwischen zwei modernen Selektoren, mit der wir arbeiten können.
Die unglaublich mächtige relationale Pseudo-Klasse :has() gewinnt ebenfalls schnell Browser-Unterstützung (und ist meiner bescheidenen Meinung nach die größte neue Funktion von CSS seit Grid). Zum Zeitpunkt des Schreibens ist die Browserunterstützung für :has() jedoch noch nicht gut genug für den Produktionseinsatz.
Fügen wir eine dieser Pseudo-Klassen in meine BEM und...
/* ❌ specificity score: 0,2,0 */
.something:not(.something--special) {
/* styles for all somethings, except for the special somethings */
}
Hoppla! Sehen Sie diesen Spezifitätswert? Denken Sie daran, dass wir mit BEM im Idealfall möchten, dass unsere Selektoren alle einen Spezifitätswert von 0,1,0 haben. Warum ist 0,2,0 schlecht? Betrachten Sie ein ähnliches Beispiel, erweitert
.something:not(a) {
color: red;
}
.something--special {
color: blue;
}
Auch wenn der zweite Selektor an letzter Stelle in der Quellreihenfolge steht, gewinnt die höhere Spezifität des ersten Selektors (0,1,1), und die Farbe von .something--special-Elementen wird auf red gesetzt. Vorausgesetzt, Ihr BEM ist richtig geschrieben und die ausgewählten Elemente haben sowohl die Basisklasse .something als auch die Modifier-Klasse .something--special im HTML angewendet.
Bei unvorsichtiger Verwendung können diese Pseudo-Klassen die Kaskade auf unerwartete Weise beeinflussen. Und es sind diese Inkonsistenzen, die später, insbesondere in größeren und komplexeren Codebasen, Kopfschmerzen bereiten können.
Mist. Und jetzt?
Erinnern Sie sich an das, was ich über :where() und die Tatsache gesagt habe, dass seine Spezifität null ist? Das können wir zu unserem Vorteil nutzen.
/* ✅ specificity score: 0,1,0 */
.something:where(:not(.something--special)) {
/* etc. */
}
Der erste Teil dieses Selektors (.something) erhält seinen üblichen Spezifitätswert von 0,1,0. Aber :where() – und alles darin – hat eine Spezifität von 0, was die Spezifität des Selektors nicht weiter erhöht.
:where() ermöglicht uns das Verschachteln
Leute, die sich nicht so sehr um Spezifität kümmern wie ich (und das sind wahrscheinlich viele Leute, fairerweise), hatten bisher beim Verschachteln ziemlich leichtes Spiel. Mit ein paar sorglosen Tastenhieben können wir CSS wie dieses erhalten (beachten Sie, dass ich Sass zur Kürze verwende)
.card { ... }
.card--featured {
/* etc. */
.card__title { ... }
.card__title { ... }
}
.card__title { ... }
.card__img { ... }
In diesem Beispiel haben wir eine .card-Komponente. Wenn es sich um eine "Featured"-Karte handelt (unter Verwendung der Klasse .card--featured), müssen der Titel und das Bild der Karte anders gestylt werden. Aber, wie wir jetzt wissen, führt der obige Code zu einem Spezifitätswert, der inkonsistent mit dem Rest unseres Systems ist.
Ein Verfechter der Spezifität hätte stattdessen dies getan
.card { ... }
.card--featured { ... }
.card__title { ... }
.card__title--featured { ... }
.card__img { ... }
.card__img--featured { ... }
Das ist doch nicht so schlimm, oder? Ehrlich gesagt, das ist schönes CSS.
Es gibt jedoch einen Nachteil im HTML. Erfahrene BEM-Autoren sind sich wahrscheinlich schmerzlich der umständlichen Template-Logik bewusst, die erforderlich ist, um Modifier-Klassen bedingt auf mehrere Elemente anzuwenden. In diesem Beispiel muss die HTML-Vorlage bedingt die Modifier-Klasse --featured auf drei Elemente (.card, .card__title und .card__img) anwenden, obwohl in einem realen Beispiel wahrscheinlich noch mehr. Das sind viele if-Anweisungen.
Der :where()-Selektor kann uns helfen, deutlich weniger Template-Logik – und weniger BEM-Klassen – zu schreiben, ohne das Spezifitätsniveau zu erhöhen.
.card { ... }
.card--featured { ... }
.card__title { ... }
:where(.card--featured) .card__title { ... }
.card__img { ... }
:where(.card--featured) .card__img { ... }
Hier ist dasselbe, aber in Sass (beachten Sie die nachgestellten Ampersands)
.card { ... }
.card--featured { ... }
.card__title {
/* etc. */
:where(.card--featured) & { ... }
}
.card__img {
/* etc. */
:where(.card--featured) & { ... }
}
Ob Sie sich für diesen Ansatz gegenüber dem Anwenden von Modifier-Klassen auf die verschiedenen Kindelemente entscheiden sollten, ist eine Frage der persönlichen Vorliebe. Aber zumindest gibt uns :where() jetzt die Wahl!
Was ist mit Nicht-BEM-HTML?
Wir leben nicht in einer perfekten Welt. Manchmal müssen Sie mit HTML umgehen, das außerhalb Ihrer Kontrolle liegt. Zum Beispiel ein Drittanbieter-Skript, das HTML injiziert, das Sie gestalten müssen. Dieser Markup ist oft nicht mit BEM-Klassennamen geschrieben. In einigen Fällen verwenden diese Stile überhaupt keine Klassen, sondern IDs!
Auch hier hat :where() uns geholfen. Diese Lösung ist etwas hacky, da wir auf eine Klasse eines Elements verweisen müssen, das weiter oben im DOM-Baum existiert und von dem wir wissen, dass es existiert.
/* ❌ specificity score: 1,0,0 */
#widget {
/* etc. */
}
/* ✅ specificity score: 0,1,0 */
.page-wrapper :where(#widget) {
/* etc. */
}
Das Referenzieren eines übergeordneten Elements fühlt sich jedoch etwas riskant und einschränkend an. Was ist, wenn sich diese übergeordnete Klasse ändert oder aus irgendeinem Grund nicht vorhanden ist? Eine bessere (aber vielleicht ebenso hacky) Lösung wäre die Verwendung von :is(). Denken Sie daran, die Spezifität von :is() ist gleich der spezifischsten Selektor in seiner Selektorliste.
Anstatt also auf eine Klasse zu verweisen, die wir kennen (oder hoffen!), dass sie existiert, wie im obigen Beispiel, könnten wir auf eine erfundene Klasse und das <body>-Tag verweisen.
/* ✅ specificity score: 0,1,0 */
:is(.dummy-class, body) :where(#widget) {
/* etc. */
}
Der allgegenwärtige body hilft uns, unser #widget-Element auszuwählen, und die Anwesenheit der Klasse .dummy-class innerhalb desselben :is() verleiht dem body-Selektor den gleichen Spezifitätswert wie eine Klasse (0,1,0)... und die Verwendung von :where() stellt sicher, dass der Selektor nicht spezifischer wird.
Das ist alles!
So können wir die modernen Spezifitätsverwaltungsfunktionen der Pseudo-Klassen :is() und :where() neben dem Spezifitätskollisionsschutz nutzen, den wir beim Schreiben von CSS im BEM-Format erhalten. Und in nicht allzu ferner Zukunft, sobald :has() die Firefox-Unterstützung erhält (es wird zum Zeitpunkt des Schreibens hinter einem Flag unterstützt), werden wir es wahrscheinlich mit :where() kombinieren wollen, um seine Spezifität aufzuheben.
Ob Sie sich nun voll und ganz auf die BEM-Namensgebung einlassen oder nicht, ich hoffe, wir können uns darauf einigen, dass Konsistenz bei der Selektorspezifität eine gute Sache ist!
BEM, das ist ein Klassiker. Ich dachte, die ganzen jungen Leute würden heute Utility-Frameworks wie Tailwind in React-Komponenten verwenden. Schön zu sehen, dass BEM wieder aufgegriffen wird.
Danke, Bill. BEM ist nie verschwunden! :D
Der Fokus hat sich vielleicht verschoben, wie es in unserer Branche eben so ist.
Tailwind und Co. sind gut für bestimmte Arten von Projekten. Aber für die großen CMS-Sites, mit denen ich oft arbeite, würde ich nichts anderes verwenden als BEM (mit sehr wenigen Utility-Klassen, nur so nebenbei).
Für das letzte Beispiel, ich weiß, dass Sie die Macht von
:isgezeigt haben, aber zur Sicherheit, es könnte gut sein, auch hervorzuheben, dass Sie[id]:where(#widget)oder sogar[id="widget"]tun können, die beide eine Spezifität von0,1,0haben.Oh,
[id="widget"]ist ein wirklich großartiger Punkt, das hatte ich total vergessen!Ich bin verwirrt. Warum sollte
.something:not(.something--special)auf ein Element mit der Klassesomething--specialangewendet werden? Ist das nicht buchstäblich der Sinn des:not()-Operators?Oh je, da habe ich mich vertan, deshalb! Wir werden das im Beitrag aktualisieren. Guter Fang, danke :)
Habe :has für einige wirklich wichtige Dinge verwendet.. liebe es, diese zu benutzen.
Wenn wir
:where()haben, um das Klettern der Spezifität zu verhindern, und wir haben Selektor-Verschachtelungen (entweder über SCSS oder die native, die kommt), brauchen wir dann noch BEM? Vielleicht können wir jetzt jeden CSS-Block als isoliert behandeln und einfachere Klassennamen verwenden.Tolle Fragen.
BEM bietet mehrere Vorteile, abgesehen davon, dass es nur bei der Spezifität hilft.
Zum Beispiel gefällt mir an BEM, dass es klar macht, welche Elemente Sie direkt stylen. Stellen Sie sich zum Beispiel den "Titel" einer "Karte"-Komponente vor. Sie könnten tun
Aber was passiert, wenn Sie entscheiden, dass sie eigentlich
h2oderh4sein sollten? Sie müssten sicherstellen, dass auch das HTML aktualisiert wird. Und es wäre nicht einfach zu wissen, welche CSS, wenn überhaupt, entfernt werden sollte, wenn Sie etwas wie dieses aus dem HTML entfernen.(Ich erkenne an, dass Sie BEM nicht unbedingt verwenden müssen, um daran zu denken, eine Klasse zu Ihrem h3-Element hinzuzufügen – aber der Punkt ist, mit BEM ist es eine Erwartung, dass Sie sie zu allem hinzufügen, was Sie stylen wollen).
Der zweite Teil Ihrer Frage betraf einfachere Klassennamen. Ich verstehe, dass niemand gerne ständig .wirklich__lannggg–Klassennamen tippt. Aber das bringt mich zu dem nächsten Punkt, den ich an BEM mag – Einzigartigkeit. Das Hinzufügen des vollständigen Kontexts zu jeder Klasse bedeutet, dass ich leichter nach ihren Verwendungen suchen und sie finden kann. Zum Beispiel kann ich nach
card__titlesuchen und wissen, dass ich keine "Titel" von anderen Komponenten erhalte. Deshalb vermeide ich diese Art von Sass-Verschachtelung.Gut, dass es die Spezifität nicht erhöht, aber schlecht, dass man keine zuverlässige Code-Suche durchführen kann.
Nichtsdestotrotz müssen Sie BEM nicht speziell verwenden, um diese Prinzipien zu erreichen. Letztendlich wird dies auf Ihre (und die bevorzugte Arbeitsweise Ihres Teams) hinauslaufen. Wenn Sie eine "Element"-entsprechende Klasse wünschen, die wie
.card-titleaussieht, dann legen Sie los. Der Hauptpunkt dieses Artikels war die Verwaltung der Spezifität. BEM ist (immer noch) weit verbreitet, daher habe ich mich darauf konzentriert :)Ich fand diesen Artikel ausgezeichnet. Ich weiß, dass es nicht Ihr Hauptziel für diesen Artikel war, aber ich fand die Beschreibung der Vorteile von BEM klarer als die meisten anderen Artikel, die ich zu diesem Thema gelesen habe.
Persönlich bevorzuge ich SUIT CSS-Namensgebung – wir verwenden die Namenskonventionen, nicht die anderen SUIT-Tools. Ich bevorzuge SUIT, weil die Namenswahl besser zu unserem anderen Code passt (Typescript und C#). Wir sind keine Puristen dabei, wir verwenden Kombinationen (Kind- oder Geschwister-) Selektoren, um die Namen im HTML leichter lesbar und wartbar zu machen, was sich auf die Spezifität auswirkt.
Mit
:whereund:isund Cascade Layers, die alle gute Unterstützung in modernen Browsern haben, haben wir jetzt die Werkzeuge für Namenskonventionen, um sauberes HTML und CSS-Design zu priorisieren, ohne uns so sehr um die Spezifität sorgen zu müssen. Ich denke, es könnte eine nächste Generation von BEM oder SUIT (oder jeder anderen Namenskonvention) geben, die die Klarheit verbessert, indem sie die Spezifitätsanforderung entfernt, die das frühere Design einschränkte.Wenn Sie phpstorm (oder andere Jetbrains IDEs, nehme ich an) verwenden, dann funktioniert das
&__title-Format. Es erlaubt Ihnen, mit Command/Ctrl auf den Klassennamen zu klicken, und das Programm kann Sie zum richtigen CSS verlinken.Ich bin mir bei anderen Programmen nicht sicher, aber es kann Plugins geben, die dabei helfen.
@Dan Christofi
Interessant. Es ist gut, dass phpstorm/jetbrains das tun. Ich nehme aber an, Sie können immer noch keine standortweite Suche nach "card__title" durchführen?
Ich habe gerade Ihre Anregung in meinem Editor (VS Code) ausprobiert und sie funktioniert nicht. Dennoch funktioniert sie nicht einmal ohne die Sass-Verschachtelung. Ich stelle mir vor, dass es ein Add-on/eine Einstellung gibt, die ich aktivieren muss.
Dennoch ist das Fehlen einer zuverlässigen Codebase-Textsuche für mich persönlich ein Dealbreaker.
Ich denke,
.special:not(a)hat einen Spezifitätswert von0,1,1, nicht0,2,0. Ich war tatsächlich verwirrt und habe die Dokumentation zu:notnachgeschlagen.Hoppla! Ich hatte diesen verfluchten Codeblock nach einem anderen Fehler aktualisiert, aber die Erklärung darunter nicht. Alles behoben!
Danke für diesen Beitrag, BEM + SASS funktioniert für mich auch noch gut!
Wenn Sie einen Root-Selektor für :where() benötigen, wie im letzten Beispiel
/* ✅ Spezifitätswert: 0,1,0 */:is(.dummy-class, body) :where(#widget) {
/* etc. */
}
Ich denke, Sie könnten einfach :root verwenden
:root :where(#widget) {/* etc. */
}
Guter Vorschlag, Jonathan.
Ich habe erst vor kurzem (letzte Woche) erfahren, dass der Selektor
:rooteine Spezifität von 0,1,0 hat. Das ist sicherlich eine elegantere Möglichkeit, dies zu tun als meine hacky Ideen :)Ich denke, diese Erwähnung von
:is(.dummy-class, body)ist eines der CSS-tricksigsten Dinge, die ich hier seit langem gelesen habe, ich liebe es, wie Sie es formuliert und vorgeschlagen haben, es für diesen Zweck zu verwenden.✔️ totaler Hack
Danke, Tamm! Ich war ziemlich stolz auf dieses Stück, bis jemand darauf hinwies, dass man stattdessen einfach tun könnte
…was meiner Meinung nach viel sauberer ist. Beachten Sie, dass
:rootdie gleiche Spezifität wie eine Klasse hat, 0,1,0Gut, die detaillierten Kenntnisse des BEM-Formats zu haben. Gut zu wissen, dass es noch im Rennen ist.
Ich bin so verwirrt über diese Spezifitätszahlen. Woher bekommt man diese geordneten Tupel? In https://web.dev/learn/css/specificity/ hat die Spezifität Zahlen wie 1, 10, 100 usw.
Also
hat 11 Punkte Spezifität.
Hallo Tarun!
Ich denke, der beste Weg, sich mit Spezifitätswerten vertraut zu machen, ist, mit einem Rechner zu spielen.
Ich empfehle diesen: https://polypane.app/css-specificity-calculator/#selector=div%3Anot(.my-class)
Beachten Sie, dass Ihr Beispiel hier vorab geladen sein sollte.
Es gibt andere CSS-Spezifitätsrechner, aber dieser ist der beste, den ich gefunden habe, da er alle Selektoren der Stufe 4 unterstützt, über die ich in diesem Artikel spreche (einige Rechner tun dies nicht).
Was das Komma-getrennte Format betrifft, können Sie es einfach als einfache Zahl lesen. Zum Beispiel,
p ahat einen Wert von
0,0,2. Das können Sie als "2" lesen.Während,
#thing p ahat einen Wert von
1,0,2. Das können Sie als "102" lesen.Je höher diese Zahl, desto höher die Spezifität.
Hallo Liam,
Ich habe mich irgendwie in diesen Nebensatz verirrt
Warum ist es akzeptabel, Pseudo-Klassen zu verwenden, während
.card--featured .card-titlees nicht ist?Außerdem bin ich derzeit an einem Projekt beteiligt, bei dem wir Pseudo-Klassen wie
:hoverin Betracht ziehen oder ob wir besser Modifier wie--hoververwenden sollten. Was ist Ihre Expertenmeinung?