Dans ma vie de développeur, j’ai eu 2 moments d’épiphanies : le moment où j’ai compris comment fonctionnaient les pointeurs en programmation et le jour où j’ai compris qu’il fallait bien séparer mes composants CSS entre disposition et présentation.

En effet, je fais du CSS (et du SCSS) depuis plusieurs années, avec plus ou moins de succès mais à chaque fois, je me retrouvais à jouer avec des marges ici et là pour obtenir un résultat harmonieux entre les différents composants.

J’avais beau suivre la méthodologie BEM, rien n’y changeait jusqu’à ce que je lise un article (impossible de retrouver la source …) qui parlait justement de règles simples à respecter pour éviter ce désagrément.

Le soucis

Imaginons que l’on souhaite disposer des champs de formulaires verticalement avec un HTML qui ressemblerait à ceci :

<form>
  <div class="field">
    <label class="field__label" for="email">Email</label>
    <input class="field__control" type="email" id="email" />
  </div>

  <div class="field">
    <label class="field__label" for="password">Mot de passe</label>
    <input class="field__control" type="password" id="password" />
  </div>
</form>

Jusqu’ici, je faisais quelque chose dans ce genre :

.field {
  margin-bottom: 16px;
}

/** D'autres définitions pour .field__label et .field__control */

Ok, il n’y a pas non plus mort d’Homme me direz-vous. Sauf qu’en faisant ceci, on a appliqué des marges à un élément car on estime savoir dans quel contexte il sera utilisé. Donc si je fais un formulaire horizontal, je devrais supprimer ces marges pour ne pas avoir un résultat étrange par exemple.

En BEM, le but est justement de faire en sorte qu’un bloc puisse être utilisé à différents endroits d’un document sans se soucier des éléments extérieurs.

La solution

Au final, vous avez sans doute déjà trouvé la solution : il faut séparer les composants de disposition et ceux de présentation.

Jusque là, c’est plutôt simple, mais ce qui a fait 💡 chez moi, c’est de lire ceci : un composant définissant des propriétés d’affichage (couleurs, comportements, …) n’est pas autorisé à définir des marges extérieures.

Réciproquement, quand vous définissez un composant de disposition, vous ne devez que lui affecter des propriétés de disposition, rien d’autre.

Et ça, ça change tout ! Quand vous définissez un composant de présentation comme un bouton, un champ, un entête de page, vous ne devez pas appliquer de marges car elles définissent le comportement d’un élément par rapport à un autre. Vous pouvez cependant appliquer un padding car il définit des marges internes au composant.

Pour reprendre notre exemple, la solution est alors d’introduire un nouveau composant uniquement pour la disposition (ici .fields-group) :

<form class="fields-group">
  <div class="fields-group__item field">
    <label class="field__label" for="email">Email</label>
    <input class="field__control" type="email" id="email" />
  </div>

  <div class="fields-group__item field">
    <label class="field__label" for="password">Mot de passe</label>
    <input class="field__control" type="password" id="password" />
  </div>
</form>

Et ensuite on applique la marge aux enfants de ce composant :

.fields-group {
  display: flex;
  flex-direction: column;
}

.fields-group > .fields-group__item {
  margin-bottom: 16px;
}

Stack et Grid 💕

Une fois qu’on a commencé à réfléchir de la sorte tout devient plus simple. On se focalise sur nos composants de présentation, peu importe comment ils seront agencés les uns avec les autres et on se rend compte qu’au final, on peut disposer n’importe quels éléments avec des classes simples.

Pour ma part, avec globalement 2 classes CSS principales (et plusieurs modificateurs), j’arrive à achever facilement 95% de mes tâches de positionnement.

La .stack

Utilisées pour aligner horizontalement ou verticalement des éléments avec un retour automatique quand il n’y a plus suffisamment d’espace. Dans les grandes lignes, ça donne ceci :

.stack {
  display: flex;
  flex-direction: row;
  flex-wrap: wrap;
  /** Les marges servent à séparer correctement les éléments, même sur plusieurs lignes 👍 */
  margin-left: -16px;
  margin-top: -16px;
}

.stack > .stack__item {
  display: block;
  margin-left: 16px;
  margin-top: 16px;
}

/** Un modificateur pour les layouts verticaux */
.stack.stack--vertical {
  flex-direction: column;
}

/** Et pleins d'autres modificateurs pour l'espacement, l'alignement des enfants 
    comme .stack--middle .stack--bottom, ... */

Elle sera utilisée pour aligner des éléments au sein d’un bouton (icône, label, badge, ce genre de chose), ou alors des boutons entre eux pour distinguer des actions d’un formulaire par exemple. C’est aussi la classe que j’utilise pour aligner des champs d’un formulaire plutôt que de redéfinir une classe .fields-group comme dans l’exemple précédent.

La .grid

Utilisée pour placer des élements au sein de la page, responsive pour permettre un affichage optimal suivant la place disponible. En règle général, sur mobile il s’agit d’une colonne et à partir de la tablette, on appliquera les largeurs de colonnes qui vont bien. Ci-dessous, un exemple très simple pour illustrer mes propos :

.grid,
.grid__item {
  display: block;
}

@media all and (min-width: 769px) {
  .grid {
    display: flex;
    flex-direction: row;
    flex-wrap: nowrap;
  }

  .grid > .grid__item {
    flex: none;
  }

  .grid > .grid__item.grid__item--one {
    width: 8.3%;
  }

  .grid > .grid__item.grid__item--two {
    width: 16.6%;
  }

  /** Et ainsi de suite pour les 12 colonnes ... */
}

Une fois que vous avez vos classes pour disposer vos éléments, cela devient un jeu d’enfant de composer vos layouts vous même sans forcément faire appel à un Bootstrap, Bulma ou autre !

Comme quoi, des fois il suffit d’un petit déclic, d’une phrase bien tournée pour que tout s’éclaire !