Engineering

Komponentisierung der Kibana-UI, Teil 1: Skalierbares CSS

CSS kann skaliert werden – wir zeigen wie

Zehntausende Zeilen CSS. Selektoren bis in Level 8. Überschreiben, überschreiben, überschreiben, bis … (oh nein) …die !important-Regel auftaucht! Einigen bricht allein bei der Erwähnung dieser Biester der Schweiß aus, aber hier im Kibana-Team stellen wir uns diesen Gegnern mutig in den Weg (mit Unterstützung von viel Koffein und einsatzbereiten Web Inspectors).

Übertreibe ich? Vielleicht! Aber es ist nicht von der Hand zu weisen, dass das Schreiben von skalierbarem CSS ein echter K(r)ampf ist. Im Kibana-Team lösen wir dieses Problem mit einem formell festgelegten Konzept für das Schreiben von CSS.

  • Wir stellen uns die Benutzeroberfläche als ein System aus verschiedenen Komponenten vor.
  • Wir nutzen Klassen, um unser CSS zu vereinfachen.
  • Wir verwenden BEM als Namenskonvention für bessere Vorhersehbarkeit und Lesbarkeit unseres Markups und CSS.

Wir bauen unsere Benutzeroberfläche aus Komponenten

Komponenten sind die Bausteine einer Benutzeroberfläche. Sie sind die Buttons, die Formularfelder, die modalen Fenster und andere visuelle Elemente, die Entwickler zusammenfügen, wenn sie eine Funktion erstellen.

Dadurch, dass wir CSS in Form von Komponenten schreiben, erleichtern wir unseren Entwicklern das Leben enorm, denn sie können sich auf vorhandenes CSS und Markup stützen, anstatt es selbst schreiben zu müssen. CSS, das für einzelne Komponenten geschrieben wird, lässt sich außerdem leichter pflegen, denn eine Komponente begrenzt den Style-Umfang auf natürliche Weise und verhindert unerwünschte Nebenwirkungen, wenn man Änderungen daran vornimmt.

Hier ist ein Beispiel für solch eine Komponente, die wir Panel nennen.

Our Panel component

Für Komponenten gibt es eine visuelle und eine Code-Darstellung und beide lassen sich anhand desselben Namens identifizieren. Da kommen wir auch schon zu einem weiteren wichtigen Punkt: eine gemeinsame Sprache. Denn sobald ein Designer, ein Entwickler oder ein Produktmanager von einem „Panel“ spricht, wissen die anderen immer, worum es geht.

Schauen wir uns nun an, wie wir unser CSS einfach halten. Danach betrachten wir das CSS hinter unserer Panel-Komponente genauer.

Einfachheit ist die Grundlage für skalierbares CSS

Einfacher Code hat weniger bewegliche Teile und drückt die Absicht dahinter klar und deutlich aus. Die darin enthaltenen Logikeinheiten sind voneinander entkoppelt, enthalten explizite und eindeutige Interaktionen und haben klar definierte Grenzen.

Einfaches CSS hat die gleichen positiven Eigenschaften. Aber die Methoden, mit denen wir CSS vereinfachen, unterscheiden sich von den Methoden, die wir zum Schreiben von vereinfachtem Code in imperativen Sprachen wie JavaScript oder Python verwenden würden. Wir vom Kibana-Team haben herausgefunden, dass das leistungsstärkste Tool in unserem Werkzeugkasten für vereinfachtes CSS die CSS-Klasse ist.

Hier sind die Grundregeln, wie wir Klassen zur Vereinfachung unseres CSS nutzen:

Verwende Klassen, um Markup explizit der gerenderten UI zuzuweisen. Wenn wir die Klassen anhand der Komponenten benennen, erhalten die Klassen eine Bedeutung. Diese Klassenbezeichnungen sind wie Orientierungspunkte und Straßenschilder in unserem Markup. Sie helfen uns bei der Navigation im Markup, beschreiben die Beziehung zwischen Elementen und bilden ein gedankliches Bild der Benutzeroberfläche, ohne dass man ständig den Browser prüfen muss.

Beschränke Selektoren auf eine einzige Klasse. Wenn ein Selektor mehr als eine Klasse enthält, entsteht eine Kontextabhängigkeit. Das bedeutet, dass die Styles eine bestimmte Beziehung zwischen den Elementen brauchen, um zum Tragen zu kommen. Und es kann schwierig sein, diese Beziehungen beim Lesen des Markups zu erkennen. Durch die Beschränkung jedes Selektors auf eine einzige Klasse vereinfachen wir die Beziehungen zwischen Elementen, machen unser Markup leichter lesbar und reduzieren die Wahrscheinlichkeit, dass wir aus Versehen irgendetwas kaputt machen, wenn wir den Kontext ändern.

Verwende vererbte Eigenschaften nur in Blattknoten. Wenn CSS Eigenschaften wie „font-size“ für die Schriftgröße und „color“ für die Farbe bei einem Element anwendet, gelten diese aufgrund der Vererbungsregeln auch für die untergeordneten Elemente. Diese Nebeneffekte sind manchmal nicht geplant und ebenso wenig erwünscht. Unsere Lösung: Wir vermeiden Vererbung komplett, indem wir vererbte Eigenschaften nur für die Bereiche unserer UI verwenden, die keine untergeordneten Elemente enthalten (z. B. Formularbezeichnungen und Schaltflächen).

Schauen wir uns nun an, wie es aussieht, wenn wir diese Prinzipien beim Code für unsere Panel-Komponente anwenden. Hier ist das Markup zur Erstellung eines Panels:

<div class="panel">
  <div class="panelHeader">
    <div class="panelHeader__title">
      Panel title
    </div>
  </div>

  <div class="panelBody">
    <!-- Content goes here -->
  </div>
</div>

Du siehst, dass die Benutzeroberfläche eindeutig durch die Klassennamen im Markup definiert ist. Es ist offensichtlich, dass es sich hierbei um eine Panel-Komponente handelt, die eine Überschrift und einen Text enthält. Wir im Kibana-Team haben herausgefunden, dass wir durch die Verbesserung der Lesbarkeit unseres Markups unsere Benutzeroberflächen schneller und zuverlässiger erstellen können.

Hier ist das CSS, das die Darstellung des Markups definiert:

.panel {
  border-left: 2px solid #e4e4e4;
  border-right: 2px solid #e4e4e4;
  border-bottom: 2px solid #e4e4e4;
}

.panelHeader {
  display: flex;
  align-items: center;
  padding: 10px;
  height: 50px;
  background-color: #e4e4e4;
}

  /*
   * FYI, we indent child classes like this to emphasize its role
   * in the markup as a tightly-coupled child of the .panel class.
   */
  .panelHeader__title {
    font-size: 18px;
    line-height: 1.5;
  }

.panelBody {
  padding: 10px;
}

Achte vor allem darauf, wie wir jedem Selektor eine einzige Klasse zugewiesen haben. So können wir viel leichter erkennen, wie sich das CSS im Markup verhält und welche Resultate in der visuellen UI-Komponente zu erwarten sind.

Außerdem ist dir vielleicht aufgefallen, dass der einzige Selektor mit vererbten Eigenschaften die Klasse .panelHeader__title ist. Der Grund: Wir wissen, dass das Element, für das dieser Selektor verwendet wird, niemals ein untergeordnetes Element neben dem Text im Panel-Titel enthalten wird. Das ist ein Beispiel dafür, wie wir Probleme vermeiden, die durch die CSS-Vererbungsregel auftreten können.

Die BEM-Namenskonvention

Die BEM-Namenskonvention ist seit ihrer ersten Entwicklung von Yandex im Jahr 2007 sehr populär geworden. Es würde den Rahmen dieses Blog-Beitrags sprengen, wenn wir jetzt die Nuancen von BEM erklären. Solltest du Interesse an diesem Thema haben, empfehle ich dir diesen Artikel von Harry Roberts oder diesen Artikel von CSS-Tricks.

Wir haben uns für BEM als CSS-Namenskonvention entschieden, da wir eine Lösung finden mussten, um beim Lesen unseres Markups Zusammenhänge zwischen den unterschiedlichen Klassen zu erkennen, die eine Komponente enthalten. Gleichzeitig brauchten wir eine Methode, um die Rolle, die jede Klasse in unserem Markup spielt, zu unterscheiden. Einige Klassen haben eine strukturelle Aufgabe, manche modifizieren etwas und wiederum andere sollen dynamisch via JavaScript angewendet werden. BEM stellt uns die Muster zur Verfügung, anhand derer wir die verschiedenen Klassen unterscheiden können.

Um mehr darüber zu erfahren, wie BEM funktioniert, folge den oben erwähnten Links.

Nächstes Thema: Die Komponentisierung

Hierbei handelt es sich um Teil 1 einer mehrteiligen Serie. Im nächsten Beitrag verrate ich, was wir aus dem Komponentisierungsprozess gelernt haben (darunter auch, wie man Komponenten so designt, dass sie erneut eingesetzt werden können). Danke fürs Lesen dieses Beitrags!

Und falls du gerne Teil unseres Team werden möchtest: Wir suchen immer nach klugen Köpfen.