CSS

Bonnes pratiques en CSS : BEM et OOCSS

Article par (Développeur JavaScript)
Créé le , mis à jour le (76010 lectures)
Tags : css, applications, OOCSS, objets, BEM, sullivan, composants

Des années durant, j'ai intégré des sites Web et développé des applications JavaScript sans ressentir le besoin d'une méthodologie pour nommer les classes CSS. Puis, les projets grossissant, le code CSS est devenu douloureux…

L'épineux sujet du nommage en CSS est loin d'être fermé. Depuis le début de la décennie, plusieurs auteurs majeurs ont partagé leurs recherches. Ils ont apporté un regard nouveau et sont allés à contrecourant, en rupture avec ce qui faisait jusqu'alors consensus. Je raconte dans cet article mon propre cheminement dans leurs travaux en espérant qu'il sera utile à l'intégrateur Web autant qu'au développeur JavaScript. J'ai cherché en effet une approche adaptée à la fois aux pages et aux applications Web.

Sommaire

  1. OOCSS
  2. BEM
  3. Pertinence de BEM
  4. Une syntaxe BEM… jolie !
  5. Ingérences transversales et… OOCSS
  6. Cas d'utilisation, partie 1 : HTML
  7. Cas d'utilisation, partie 2 : CSS avec SASS
  8. Conclusion

OOCSS

Je me suis d'abord tourné vers OOCSS (Object Oriented CSS), un ensemble de bonnes pratiques initiées par Nicole Sullivan (Stubbornella).

Le concept de OOCSS est de repérer des « objets CSS », c'est-à-dire des « patterns visuels » qui se répètent, et de définir ainsi des classes réutilisables. La méthode consiste à prendre le design comme point de départ : on repère des répétitions visuelles puis on les nomme. La sémantique du document n'est donc plus une base de travail, et des classes CSS nommées selon l'apparence sont autorisées à partir du moment où elles sont génériques. En cela, OOCSS est en décalage avec les bonnes pratiques des années 2000. L'ancien nommage par la sémantique préconisait des classes comme .last-articles-box ou .comment-title, alors qu'en OOCSS des classes .links-box ou .tiny-title seront préférées. Cela implique que le pilotage de l'apparence se fait depuis le code HTML. Ainsi, lorsqu'une feuille de styles écrite à la manière OOCSS est bien faite, il devient possible d'ajouter des morceaux entiers dans le design sans toucher à la feuille CSS, juste en réutilisant des objets CSS déjà existants.

OOCSS met en avant deux principes :

  1. Le principe de séparation de la structure et de l'apparence ;
  2. Le principe de séparation du conteneur et du contenu.

Le premier principe nous fera préférer, dans les sélecteurs CSS, les classes plutôt que des identifiants ou des noms d'éléments HTML. Il incite également à factoriser les propriétés visuelles répétées. Pour l'exemple, partons du code HTML suivant :

<button class="small-btn"></button>
<button class="large-btn"></button>

Mieux vaut factoriser dans une classe btn les règles CSS communes aux deux boutons, ce qui donne :

<button class="btn small-btn"></button>
<button class="btn large-btn"></button>

Le second principe consiste à éviter des cascades CSS comme .links-box .title, car l'apparence du contenu .title serait alors couplée au conteneur .links-box. Une classe .box-title sera plus réutilisable.

Je recommande au lecteur l'exposé de Nicole Sullivan : Our best practices are killing us, publié en 2011. Cet exposé est formidable. Il a été, pour de nombreux intégrateurs, le point de départ d'une aventure OOCSS. Le lecteur désirant aller plus loin lira ensuite cette introduction à OOCSS (en français) ainsi que le wiki du framework OOCSS.

Mais OOCSS correspondait imparfaitement à mon besoin. Les designers avec lesquels je travaille conçoivent trop de variations entre chaque bloc. OOCSS ne donne pas des règles de construction nettes et fermes, et le temps passé à factoriser l'apparence peut dépasser le gain de la réutilisation. L'approche OOCSS aide certainement à afficher des milliers d'objets dans une plateforme tentaculaire comme Facebook, car le designer lui-même raisonne alors par objets. Elle reste globalement inappropriée pour intégrer un joli design monolithique fait sur mesure pour un site plus modeste. En outre, dans le cadre de la conception d'une application JavaScript, le découpage de l'interface ne devrait pas être fait par l'apparence, les ressemblances entre composants différents étant toujours superficielles et amenées à diverger.

Quoi qu'il en soit, les nouvelles bonnes pratiques de OOCSS font prendre conscience que l'ancien nommage par la seule sémantique est obsolète dans la mesure où il nous fait produire du code CSS non-réutilisable. Mettons OOCSS de côté pour le moment, nous y reviendrons plus tard.

Design des Halles de Paris, 1863, par Victor Baltard [Source: Wikipedia]
La méthode consiste à prendre le design comme point de départ… [Design des Halles de Paris, 1863, par Victor Baltard, Wikipédia]

BEM

La méthodologie BEM est une solution élaborée par Yandex et publiée en 2010. BEM a deux faces : il s'agit d'abord d'une méthode déclarative de l'interface utilisateur servant à décrire un « arbre BEM », les outils open source de Yandex travaillent ensuite sur cet arbre. BEM apporte également une convention de nommage des classes CSS qui a gagné une certaine popularité. C'est de cette méthodologie du nommage, véritablement puissante, que nous allons parler ici.

BEM est l'acronyme de Block, Element, Modifier, et toute la méthodologie du nommage à la manière BEM tient dans ces trois mots. La force du concept ? Ce qui compose un page ou une application Web peut toujours être rangé dans une arborescence de blocs, d'éléments et de modificateurs.

Un bloc est une entité indépendante, une « brique » de l'application ou de la page Web. Un bloc forme son propre contexte autonome. Ci-dessous des exemples de blocs dans une illustration tirée du site officiel :

Logo, Onglets, Formulaires, etc.
Des blocs BEM [source : bem.info]

Un élément est une partie d'un bloc. Le contexte d'un élément est celui du bloc. Ci-dessous deux exemples empruntés toujours au site officiel :

Champ Input, Button
Des éléments BEM [source : bem.info]

En tant que « brique de construction », un bloc est réutilisable dans d'autres blocs ou dans des éléments. Il ne connait toutefois que son propre contexte et non celui du parent. Un bloc n'est donc pas livré avec les règles CSS de son propre positionnement au sein du conteneur parent. Nous éclaircirons ultérieurement, sur un cas d'utilisation, ce point important.

Un modificateur est une propriété qui sert à créer des variantes, pour faire des modifications minimes comme changer des couleurs. Il existe des modificateurs de blocs et des modificateurs d'éléments.

La méthodologie BEM établit ensuite trois règles essentielles :

  1. Les blocs et les éléments doivent chacun avoir un nom unique, lequel sera utilisé comme classe CSS ;
  2. Les sélecteurs CSS ne doivent pas utiliser les noms des éléments HTML (pas de .menu td) ;
  3. Les cascades dans les sélecteurs CSS devraient être évitées.

À propos de la première règle, précisons que les identifiants HTML (les attributs id) ne doivent pas être utilisés en CSS, chaque bloc pouvant par principe être instancié plusieurs fois. Les identifiants HTML ne servent que d'ancres. La deuxième règle est nécessaire dans la mesure où les blocs peuvent être imbriqués. Un sélecteur .menu td briserait la séparation des contextes en interagissant avec les balises <td> des sous-blocs, cela doit être évité.

Ces règles impliquent de préfixer les noms des éléments par leur contexte. Venons-en à la convention de nommage des classes CSS. Le site officiel prend soin de préciser que seuls comptent les concepts, la syntaxe restant libre. L'équipe de BEM utilise pour sa part une syntaxe à base de underscores :

  • block-name
  • block-name_modifier_name
  • block-name__element-name
  • block-name__element-name_modifier_name

Pouah ! Hideux ! La raison d'une telle laideur est un manque de caractères utilisables dans les identifiants en CSS.

Le code CSS, en méthodologie BEM, est presque plat. Voici un exemple de code CSS pour un bloc search-box doté d'un modificateur light, contenant un élément btn avec un modificateur max_visible :

.search-box {
  height: 300px;
  width: 300px;
}
.search-box_light {
  background-color: #DEF;
  color: #777;
}
.search-box__btn {
  padding: 4px;
}
.search-box__btn_max_visible {
  font-weight: bold;
}

Le code HTML :

<div class="search-box search-box_light">
  <!-- (input field here) -->
  <button class="search-box__btn search-box__btn_max_visible">Search</button>
</div>

Une cascade est utilisée lorsqu'un modificateur de bloc a un effet sur un élément :

.search-box_light .search-box__btn {
  background-color: #9AB;
}

Signalons entre parenthèses que cette cascade est à éviter sur les blocs pouvant s'imbriquer récursivement, car le modificateur du bloc parent affecterait alors les blocs enfants. Fort heureusement le cas est rare.

Nous n'en avons pas terminé avec BEM. Dans la suite de l'article cependant nous nous écarterons de la syntaxe et même de la terminologie originale. Je suggère au lecteur intéressé par l'orthodoxie : la présentation officielle de la méthodologie BEM dont sont tirées les deux illustrations, et un article sur l'utilisation de BEM dans de petits projets (incluant la partie déclarative de l'arbre BEM).

Pertinence de BEM

BEM, c'est un peu le chic type au visage ingrat. Il a des qualités mais qu'est-ce qu'il est… LAID ! Je ne sais pas pour vous ? En ce qui me concerne, travailler sur un code qui me dégoute, ça, jamais !

Tout de même, juger sur l'apparence n'est pas bien. Donnons-lui une chance et regardons au moins ses avantages.

La propreté

En BEM, aucun risque d'aboutir à ce code-là :

.my-aside-title {
  font-size: 1.5em; /* Je style mon titre, OK c'est propre */
}
.my-aside h2.my-aside-title {
  position: inherit; /* Ah merde, annulation d'une règle sur h2 prévue pour autre chose */
}

Performance [source: all-free-photos.com]La performance

La performance concerne plus particulièrement les applications Web. Les navigateurs rangent les classes CSS dans une table de hachage globale au document, mais il serait trop couteux de créer des sous-tables pour les descendants au niveau de chaque élément HTML. Aussi, en CSS, seul le premier niveau de sélection est performant. Les cascades CSS, lorsqu'elles sont nombreuses, engendrent des problèmes de fluidité surtout sur les pages animées des applications Web.

BEM, en limitant drastiquement l'usage des cascades CSS, incite à élaborer des feuilles de styles performantes.

La scalability et une architecture par composants

Scalability [source: Wikipedia]Un bloc peut être placé n'importe où dans la page, ou encore apparaitre (être instancié) plusieurs fois. Cela est possible parce que ses règles CSS sont radicalement séparées de celles des autres blocs. Il est alors possible de construire des applications géantes tout en travaillant toujours à une échelle réduite : le contexte d'un bloc.

Un parallèle est à faire avec les « composants Web », lesquels seront les briques des futures applications JavaScript. Chaque composant Web embarquera ses propres règles CSS et son propre code JavaScript. La norme prévoit un shadow DOM, c'est-à-dire une sandbox qui encapsule une portion de DOM dans le but d'empêcher les sélecteurs CSS et les identifiants HTML d'interagir par erreur avec le reste du DOM. Aujourd'hui, tout le challenge des frameworks JavaScript est de s'adapter à cette manière de travailler.

Les blocs BEM correspondent bien à la philosophie du développement par composants. BEM donne pour les technologies d'aujourd'hui un format de nommage utilisable et une manière de travailler compatible avec celle de demain.

Une syntaxe BEM… jolie !

D'un côté BEM en vaut la peine, de l'autre il n'est pas séduisant. Nous risquons le mariage de raison… Les lignes suivantes relatent une démarche qui m'a pris plusieurs mois.

HTML 5 est venu avec une bonne et une mauvaise nouvelle. La bonne nouvelle, c'est qu'il est désormais possible d'utiliser n'importe quel caractère dans les identifieurs des attributs class et id. Et la mauvaise nouvelle, c'est que… pas en CSS. Un auteur Belge a dressé la liste des caractères qui ont un sens spécial en CSS et qu'il faut donc échapper : !, ", #, $, %, &, ', (, ), *, +, ,, -, ., /, :, ;, <, =, >, ?, @, [, \, ], ^, `, {, |, } et ~. Échapper n'est pas une option, on ne va pas remplacer une laideur par une autre.

Il reste alors deux caractères de séparation : le trait d'union (-) qu'une règle spéciale permet d'utiliser dans un identifieur sauf en première position, et le underscore (_).

Aïe.

Bloqué.

Prenons le problème autrement. La norme en CSS dit ceci :

All CSS syntax is case-insensitive within the ASCII range […], except for parts that are not under the control of CSS. For example, the case-sensitivity of values of the HTML attributes "id" and "class" […] (Source : W3C).

La casse est donc utilisable. À titre personnel je rechignais un peu. J'ai toujours nommé mes classes CSS en minuscules avec des traits d'union, tous les gens bien font comme cela. Mais ici il faut sacrifier quelque chose. Accepter un séparateur illisible et moche ? Plutôt mourir ! S'assoir sur la norme ? Bon j'avoue avoir envisagé un temps une utilisation irrégulière du tilde (~)… Mais quoi ! La casse est valide, on l'utilise ailleurs en programmation, zut alors ! À cœur vaillant rien d'impossible, tant pis pour la tradition.

Une syntaxe BEM basée sur la casse est décrite par Nicolas Gallagher dans son mémorable article : About HTML semantics and front-end architecture, 2012. L'auteur en fait usage dans son framework SUIT CSS. La voici :

  • ComponentName
  • ComponentName--modifierName
  • ComponentName-descendantName
  • ComponentName.is-stateOfComponent

On l'aura compris, les composants sont les blocs et les descendants sont les éléments. La terminologie de BEM est ambigüe car « bloc » en CSS est aussi un type de flux d'affichage (l'opposé de « en ligne ») et « élément » désigne une balise HTML et son contenu. Aussi, adoptons définitivement les termes « composant » et « descendant ». Un composant CSS évoque en effet avec justesse l'idée d'encapsulation du composant Web, et un descendant en est effectivement un dans l'arbre DOM. Un composant Web pouvant embarquer plusieurs composants CSS, je préfère accoler ainsi « CSS » afin de les distinguer.

La syntaxe de l'état est intéressante : un simple point de séparation. C'est la syntaxe du sélecteur pour deux classes affectées à un même élément HTML. Ce sélecteur est devenu utilisable depuis que Windows XP et donc Internet Explorer 6 ont été abandonnés. Ci-dessous un exemple de bloc MenuBtn marqué avec l'état current en syntaxe SUIT CSS :

<button class="MenuBtn is-current">Open</button>

En revanche, la syntaxe des modificateurs oblige encore, à l'instar de celle originale de BEM, à de lourdes répétitions. Car la classe CSS d'un modificateur est déclarée en plus de celle du composant CSS. Voici comment s'écrira un bouton doté des modificateurs big et darkBlue :

<button class="MenuBtn MenuBtn--big MenuBtn--darkBlue">Open</button>

Pourquoi le principe de la double classe n'a-t-il pas été retenu pour les modificateurs ? L'auteur m'a répondu : « It helps keep specificity low » (« Ça aide à garder basse la spécificité »). Et c'est une réalité. La spécificité CSS est une mesure de la priorité d'un sélecteur CSS. Par exemple, le code suivant affichera en bleu les éléments ayant les deux classes c1 et c2, car la spécificité de .c1.c2 est plus élevée que celle de .c1 :

.c1.c2 {
  color: blue;
}
.c1 {
  color: red;
}

Toutefois, si c1 était le nom du composant CSS et c2 celui d'un modificateur, alors une spécificité plus haute pour le modificateur aurait du sens. Le but d'un modificateur n'est-il pas précisément de surcharger les règles d'affichage de base du composant ?

En outre, sur le plan de la performance, sélectionner simultanément deux classes CSS revient à faire l'intersection des résultats de deux sélections simples. La complexité, au sens algorithmique du terme, reste dans le même ordre de grandeur. Par conséquent la sélection simultanée de deux classes CSS est performante.

Voici alors la convention de nommage que je propose à mon tour, dérivée de celle de SUIT CSS, dérivée de BEM :

  • ComponentName
  • ComponentName.modifierName
  • ComponentName-descendantName
  • ComponentName-descendantName.modifierName
  • ComponentName.isStateOfComponent

J'ai gardé l'idée d'une convention pour un état booléen. Mais, visuellement, un état n'est rien d'autre qu'un modificateur spécial et je l'ai donc intégré dans la syntaxe des modificateurs. Voici un exemple de code HTML contenant de surcroit un descendant keyword :

<button class="MenuBtn big darkBlue isCurrent">
  Open the <b class="MenuBtn-keyword">archives</b>
</button>

Il arrive qu'une classe ne s'applique que sur certains types d'écrans. Un simple suffixe en majuscules fera ressortir cette caractéristique. Par exemple le modificateur stickyMT s'appliquera sur les écrans des mobiles et des tablettes seulement. Je compose pour ma part les suffixes avec D (desktop), T (tablet) et M (mobile). En cas d'écrans multiples, ordonner alphabétiquement.

Ingérences transversales et… OOCSS

La méthodologie BEM apporte une solide séparation entre les contextes CSS des différents composants CSS. Pourtant, tous ces contextes reposent en définitive sur du sable mouvant. Les composants supposent en effet toujours un certain contexte CSS global, comme l'existence d'un reset CSS pour annuler les marges ou encore le fait qu'une balise <strong> affiche par défaut son contenu en gras.

Et il est à mon avis justifié de jouer volontairement sur la corde transversale en marquant des éléments du DOM avec des classes ne respectant pas l'arborescence des composants CSS. Ainsi, un site réutilisant des patterns visuels globaux à la façon OOCSS fera dépendre de classes CSS transversales des propriétés décoratives. Dans une application JavaScript, un service du support multilingue peut travailler de manière transversale sur des libellés disposant de traductions.

Pour ces marqueurs transversaux, je propose d'utiliser un préfixe commençant par une lettre minuscule et se terminant par le premier trait d'union : myPrefix-…. Par exemple un objet CSS aux propriétés décoratives : ob-prettyBox ; un libellé dont le contenu est modifiable par un service multilingue transversal : lang-localizableLabel. Pas de convention pour ce qui suit le préfixe : lang-LocalizableLabel ou encore lang-localizable-label au bon vouloir du concepteur.

Nota. — Les ingérences transversales sortent de l'orthodoxie BEM.

À propos d'objets CSS

Parmi les objets CSS valides en OOCSS, distinguons ceux purement décoratifs de ceux qui organisent l'interface utilisateur.

Les objets CSS décoratifs qui se limitent aux propriétés sans effet sur le flux d'affichage, comme les couleurs, les ombres, les coins arrondis, sont à consommer sans modération. Cependant, la mise en commun de propriétés décoratives peut aller plus loin. S'il apparait que la moitié des composants CSS partagent un padding de dix pixels et une bordure de deux pixels, pourquoi ne pas définir une classe transversale ob-commonBox ? Grâce aux objets CSS, nous ne sommes plus limités à un seul contexte CSS global, nous pouvons créer de multiples contextes CSS globaux. Ils seront ensuite affectés aux composants ou descendants, et joueront le rôle d'habillage par défaut.

Quant aux objets CSS qui structurent l'interface utilisateur, à l'instar du media object, leur usage coïncide avec celui des composants CSS. Une syntaxe BEM devrait alors être préférée. Ensuite, lorsqu'il s'agit de délimiter nos composants CSS à partir d'un design, l'approche OOCSS consistant à repérer des répétitions de patterns visuels est excellente.

Cas d'utilisation, partie 1 : HTML

Un peu de pratique pour fixer les idées. Nous allons élaborer un modèle de page innovant pour un blog. Admirez la beauté :

Template de blog

Quatre grands composants CSS se distinguent aisément : SiteHeader pour l'en-tête, MainContent l'article principal en blanc, Sidebar la barre latérale et SiteFooter le pied de page.

Nous avons également besoin d'un composant PageWrapper pour centrer la page et lui fixer une largeur, et BodyLayout le conteneur de l'article principal et de la barre latérale. Intéressons-nous au code HTML de ce dernier :

  <div class="BodyLayout">
    <main class="BodyLayout-mainContent">
      <article class="MainContent"><!-- Content here… --></article>
    </main>
    <div class="BodyLayout-sidebar Sidebar"><!-- Widgets here… --></div>
  </div>

En tant que composants CSS, MainContent et Sidebar ne doivent pas contenir leur propre positionnement. Aussi seront-ils positionnés par des conteneurs mainContent et sidebar (notez bien les premières lettres minuscules qui distinguent les descendants des composants). Et dans le cas de la barre latérale, le descendant sidebar et le composant Sidebar sont associés au même élément HTML. Cela est autorisé par BEM : on dit alors du nœud du DOM qu'il est un mix des deux.

Les mix sont pratiques dans le cas d'un modèle de page Web car ils économisent des balises HTML. Ils impliquent toutefois une plus grande rigueur dans les CSS, comme nous le verrons dans la section suivante.

Voici le code HTML complet :

<!DOCTYPE html>
<html>
<head>
  <link rel="stylesheet" href="hello-bem.css">
</head>
<body>
<div class="PageWrapper">
  <header class="SiteHeader">
    <a class="SiteHeader-titles" href="/">
      <p class="SiteHeader-h1">Hello, World!</p>
      <p class="SiteHeader-h2">BEM is sooo handy</p>
    </a>
  </header>
  <div class="BodyLayout">
    <main class="BodyLayout-mainContent">
      <article class="MainContent ob-formattedText"><p>Main content here</p></article>
    </main>
    <div class="BodyLayout-sidebar Sidebar">
      <ul>
        <li class="Sidebar-li"><aside class="SmallBox sticky">Widget 1</aside></li>
        <li class="Sidebar-li"><aside class="SmallBox">Widget 2</aside></li>
      </ul>
    </div>
  </div>
  <footer class="SiteFooter">Something about copies here</footer>
</div>

Du texte formaté pourrait apparaitre dans plusieurs composants CSS différents, il fait donc l'objet d'une classe transversale ob-formattedText. Notons également la présence du modificateur sticky sur la première des deux instances du composant SmallBox.

Discussion HTML

Deux sujets méritent une attention particulière. Premièrement, constatons que les arbres BEM et DOM ne coïncident pas en tout point. Nous avons des descendants frères : SiteHeader-titles, SiteHeader-h1 et SiteHeader-h2, tous trois sont des enfants du composant SiteHeader alors que dans l'arbre DOM les deux derniers sont des enfants du premier. Autre exemple : le descendant BodyLayout-sidebar est le parent du composant Sidebar dans l'arbre BEM alors que dans le DOM il s'agit du même élément.

Ensuite, certains morceaux de HTML peuvent être au choix des descendants ou des composants. Pour déterminer quel est le meilleur choix, la question n'est pas : « Cette chose-là a-t-elle du sens indépendamment du reste ? » L'en-tête d'un article, contenant le titre et la date de publication, perdrait son sens s'il était séparé du corps de l'article. Il mérite pourtant souvent d'être un composant car la question à se poser est plutôt : « A-t-on besoin de créer un contexte (d'apparence) à ce niveau ? » Et en effet, la zone de titre d'un article, surtout si elle est complexe, mérite d'être un composant. Cela permettra, par exemple, de la déplacer sous le corps de l'article le jour où ce dernier deviendra une vidéo.

En cas d'indécision, la règle que je suggère est de faire au plus simple. Dans le code HTML au-dessus, le descendant SiteHeader-titles pourrait être un composant SiteTitles. Dans la mesure où le composant englobant SiteHeader est presque vide, j'ai préféré utiliser son contexte. Mais le jour où nous lui ajouterons d'autres enfants, il deviendra plus pratique de créer un contexte composant à part pour les titres du site.

Cas d'utilisation, partie 2 : CSS avec SASS

Dans cette partie, je vais utiliser le préprocesseur SASS avec sa variante syntaxique SCSS. SASS autorise les imbrications et depuis la version 3.3 sortie cette année elles fonctionnent même sur des noms composés.

En SASS, le & représente le sélecteur du bloc de déclarations parent. Un exemple de code SCSS :

.Sidebar {
  background-color: #998;
  min-height: 160px;
  padding: 20px 0;
  &-li {
    margin-bottom: 10px;
    padding: 0 20px;
  }
}

… Après compilation par SASS, le code CSS généré est le suivant :

.Sidebar {
  background-color: #998;
  min-height: 160px;
  padding: 20px 0
}
.Sidebar-li {
  margin-bottom: 10px;
  padding: 0 20px
}

Remarquez la réutilisation du sélecteur parent .Sidebar pour composer celui de l'enfant .Sidebar-li. Simple et nette.

Reprenons maintenant notre mix du descendant BodyLayout-sidebar et du composant Sidebar. Surtout ne vous laissez pas embrouiller. Cette explication est réellement facile. Il n'y a à chaque fois qu'un simple pattern de deux niveaux de hiérarchie, lequel se répète de manière imbriquée.

Dans l'arbre BEM, BodyLayout-sidebar est le conteneur de Sidebar. La règle CSS du composant Sidebar régit l'intérieur de la barre latérale, elle a été donnée plus haut. Le positionnement échoit au conteneur dont voici la règle :

.BodyLayout-sidebar {
  float: right;
  width: 25%;
}

La largeur du conteneur est définie en pourcentage : 25%. Celle du composant Sidebar est indéfinie, ce dernier prendra donc automatiquement la largeur allouée par le conteneur. Rappelons-nous que, dans le cas qui nous préoccupe, le composant et son conteneur sont en réalité le même élément HTML. Or, en CSS, un padding à gauche ou à droite, ou encore une bordure, s'ajoute à la largeur prise par l'élément HTML. Voilà pourquoi un mix demande de la rigueur : ici le composant ne doit pas utiliser ces propriétés CSS sous peine d'agrandir son propre conteneur.

Si l'on ne peut contrôler les règles CSS qui s'appliquent sur un composant, comme cela est le cas, notamment, dans une application JavaScript modulaire, alors mieux vaut éviter les mix en dissociant dans le DOM les descendants et les composants.

Le contenu complet du fichier hello-bem.scss :

@charset "UTF-8";
// Reset CSS (partial)
html, body, div, p, a, ul, li, footer, header, main {
  border: 0;
  font: inherit;
  margin: 0;
  padding: 0;
  vertical-align: baseline;
}
body {
  line-height: 1;
}
ul {
  list-style: none;
}
// CSS Objects
.ob-formattedText {
  p {
    margin-bottom: .5em;
  }
}
// CSS Components
body {
  background-color: #EEB;
}
.PageWrapper {
  background-color: #CCC;
  margin: 0 auto;
  width: 750px;
}
.SiteHeader {
  height: 120px;
  position: relative;
  &-titles {
    display: inline-block;
    left: 80px;
    position: absolute;
    top: 20px;
  }
  &-h1 {
    font-size: 3em;
    font-weight: bold;
  }
  &-h2 {
    font-size: 1.5em;
    font-style: italic;
  }
}
.BodyLayout {
  &-mainContent {
    float: left;
    width: 73%;
  }
  &-sidebar {
    float: right;
    width: 25%;
  }
  &::after {
    clear: both;
    content: "";
    display: block;
  }
}
.MainContent {
  background-color: #FFF;
  min-height: 160px;
  padding: 20px 40px;
}
.Sidebar {
  background-color: #998;
  min-height: 160px;
  padding: 20px 0;
  &-li {
    margin-bottom: 10px;
    padding: 0 20px;
  }
}
.SmallBox {
  background-color: #665;
  color: #fff;
  line-height: 50px;
  text-align: center;
  &.sticky {
    font-weight: bold;
  }
}
.SiteFooter {
  line-height: 2em;
  text-align: center;
}

Discussion CSS

Intéressons-nous tout d'abord à l'objet CSS ob-formattedText. Il est fait pour habiller des textes provenant d'un éditeur WYSIWYG. En situation réelle, il accueillera l'ensemble des règles d'affichage des éléments de formatage : du paragraphe aux listes à puces en passant par les sous-titres. Puisque les noms des balises HTML apparaissent directement, la séparation des contextes pour les éventuels sous-blocs est brisée. Cela n'est pas conforme à BEM mais il faut bien faire avec la réalité. Seuls des sous-blocs « résistants aux formatages » comme des médias devront être autorisés à l'intérieur de cet objet CSS.

Un mot encore à propos du positionnement. Le principe général est que les composants CSS et leurs modificateurs ne contiennent pas leur propre positionnement dans leur conteneur. Cela implique, la plupart du temps, de ne définir aucune marge sur l'élément HTML du composant (attention à la fusion des marges) et de ne toucher à aucune propriété qui influencerait le positionnement du composant dans son conteneur.

Ce principe n'est pas absolu. Le vrai principe sous-jacent est la réutilisation. Un positionnement est utilisé à bon escient dès qu'il fait partie intégrante du composant et ne gène donc aucunement sa réutilisation. Par exemple, le composant PageWrapper dans le code donné ci-dessus contient son propre positionnement centré. Ou encore, un composant InlineFig encapsulant une image avec sa légende, à l'intérieur d'un texte formaté, sera aligné en flottant à gauche ou à droite au moyen d'un modificateur :

.InlineFig {
  &.alignLeft {
    float: left;
    margin: 10px 10px 10px 0;
  }
  &.alignRight {
    float: right;
    margin: 10px 0 10px 10px;
  }
}

Conclusion

Tant que sera respecté le principe d'une séparation rigoureuse entre les contextes des composants CSS, BEM fournira une robuste armature à nos pages Web et à nos applications JavaScript. Mais le Web autorise également des manipulations transversales puissantes. Les objets CSS décoratifs et les traitements en JavaScript travaillant en dehors de toute hiérarchie tirent parti des capacités si particulières des navigateurs, ne nous en privons pas.

Un parallèle parlera aux amateurs de langages à objets. Un composant CSS est un contexte, de la même manière qu'en programmation un objet en est un. À l'intérieur du composant, toutes sortes de descendants s'appuient sans façon les uns sur les autres, en programmation les membres privés d'un objet font de même. Vu depuis l'extérieur, le composant CSS apparait comme une « boite noire » réutilisable dont le mode d'implémentation n'importe plus. De telles considérations ont donné leur nom aux « objets CSS » de OOCSS.

Dans le cadre d'une application JavaScript, une bonne ergonomie fera toujours correspondre la ressemblance visuelle à une similitude du comportement programmé en JavaScript. Aussi, les règles CSS doivent être couplées au code JavaScript qui anime la portion du DOM concernée. Terminons alors sur les composants Web, ces briques de construction dans le domaine applicatif et non plus seulement visuel. Ils reposent sur quatre fonctionnalités utilisables séparément :

Le support par les navigateurs progresse vite. Les quatre sont implémentées par Chrome et Opera, Firefox est en train de suivre, l'équipe d'Internet Explorer y réfléchit. Safari, en revanche, reste en dehors de la course.

Avec l'avènement du shadow DOM, certains nœuds du DOM deviennent des points de montage pour des portions de DOM encapsulées. Le DOM acquiert, en quelque sorte, du volume. La taille d'une portion de DOM ainsi encapsulée n'a pas de limite, un composant Web peut embarquer toute une application ! À l'intérieur des shadow DOM, de même bien entendu que dans le DOM principal du document, une syntaxe BEM reste appropriée. Du point de vue CSS, le shadow DOM est un espace de nommage pour les composants CSS embarqués.

En nous offrant de raisonner « par composants » à tous les niveaux de l'habillage en CSS, la méthodologie BEM et ses syntaxes préfixées s'intègrent naturellement dans un environnement fait de composants Web. Elles sont à la fois le présent et le futur des bonnes pratiques en CSS.

26/08/2014 : Mise à jour sur l'état de l'implémentation des Web components par les navigateurs.
Publication originale sur Developpez.com.

Commentaires

Nico3333fr a dit le

Article très intéressant, merci beaucoup :)

Pour ma part, je constate aussi qu'une approche complète OOCSS sur mes "petits" projets est un peu too much (surtout avec beaucoup de variations comme tu l'as écrit), ceci dit je me suis accommodé du côté rouleau compresseur d'OOCSS en gardant surtout les 2 principes de base qui eux sont très bons (séparer le positionnement d'un module de son look propre me solutionne énormément de problèmes pour le responsive, j'avais écrit là-dessus sur OpenWeb : http://openweb.eu.org/articles/penser-en-deho... ).

BEM a tendance à être un poil trop verbeux par moment, même si je reconnais bien aimer l'information que ça apporte.

J'aime bien l'idée de la double classe pour les modifieurs pour BEM, je me la suis interdite longtemps à cause d'IE6, je l'utilise aussi parfois pour les règles d'état (de SMACSS) pour éviter des effets de bord hasardeux.

Comme d'hab', dans toutes ces approches (SMACSS, OOCSS, BEM, etc.), y a à boire et à manger, autant prendre ce qui est bon à prendre pour le projet et laisser de côté le « pas nécessaire » dans ce cas.

jojaba a dit le

Je ne connaissais pas OOCSS et y voit plusieurs avantages effectivement :
* vitesse de traitement des css par les navigateurs
* maintenance plus aisée
* css réutilisables
Cela bouleverse un peu les principes que j'avais adoptés mais cela reste simle à appliquer alors va pour l'adoption de cette nouvelle philosophie ! Même le fait de ne plus utiliser d'id ne me choque plus (sauf pour identifier des sections ou pour contrôler un élément par JavaScript) tant le concept est intéressant.
J'attends de voir d'autres commentaires...

_laurent a dit le

Bonjour,

Bel article en plein dans mes questions du moment ! Merci Tarh !

J'identifie principalement ma méthodo à OOCSS (j’aime factoriser !). J'ai regardé du coté de BEM mais je n'ai pas été convaincu (même à la lecture de cet article). Pas seulement par son aspect (tes propositions le redent tout à fait digérable !) mais surtout car j'ai l'impression de "perdre" un peu la force du css (pour caricaturer à l’extrême : de faire un pas vers le css en ligne direct dans le HTML quoi).
C'est peut être un ressentit infondé basé sur ma mal-compréhension du concept et des règles mais je trouve BEM un peut trop "cloisonné", non ?

Bon par contre comme tout n'est pas soit blanc soit noir je pense quand même que j'utilise un peu de BEM pour des compos spécifiques des appli.

En fait, en écrivant ces ligne, je pense que j'utilise OOCSS pour tout les compos globaux (principal layout, boutons, liens, inputs etc) et switcher sur du BEM (mais light hein, pas aussi poussé que ce que tu décris) pour des compo spécifique à une vue. Je ne sais pas si c'est "bien" (par rapport à quoi je vous le demande) mais j'en suis plutôt satisfait.

Olivier C a dit le

Perso j'utilise OOCSS depuis un petit moment, après avoir lu "CSS maintenables" de Kaelig. Quand on a plusieurs milliers de lignes de code css, il vaut mieux.

erwan21a a dit le

Bonjour.
Voila un bien bel article. Merci à son auteur.

La séparation entre conteneurs et contenants et la création de "modules" génériques sont de très bonnes pratiques vantées par ces méthodologies.
Leur mise en place aboutie inévitablement au besoin de classes modificatrices afin que ces éléments génériques s'ajustent au contexte où on les a placé.

Ce genre de classes, précisant un état, un format ou un situation (type d'appareil par exemple), sur un de ces éléments ne me choque donc pas : class="MenuBtn isCurrent" ou class="MenuBtn isBig"
En effet, ces indications sont suffisamment neutres pour laisser les feuilles de style remplir leur rôle : définir le style.

Je m'interroge par contre sur le bien fondé de telles classes : class="MenuBtn darkBlue" ou class="InlineFig alignLeft"
Si un jour le site souhaite modifier sa charte graphique, des modifications sur l'ensemble des pages HTML sera alors nécessaire.
Pire, si sur desktop on souhaite un alignement à gauche et sur mobile un centrage des textes, une modification via JavaScript semble nécessaire. Donc sans JavaScript, plus de site viable.

Tarh a dit le

Bonjour,

Je remercie tout le monde pour les retours positifs, ça fait plaisir. :)

@erwan21a : Je commence par le cas de "InlineFig alignLeft" dans le cadre d'un code HTML généré par un éditeur WYSIWYG, par exemple dans un article publié par WordPress. L'information "alignée à gauche" a du sens pour l'utilisateur final, il a décidé que cette image dans son texte serait alignée à gauche. Stocker une information de l'utilisateur sous son vrai nom est objectivement une bonne manière de faire. Si le modificateur "alignLeft" ne s'applique que sur les écrans d'ordinateurs et les tablettes, mais non pas sur les smartphones, alors on peut le préciser en optant pour une classe "alignLeftDT" (Desktop, Tablet) qui ne sera stylée que dans les media queries pour ces types d'écrans.

Une petite remarque en marge de ces considérations : ignorer une information utilisateur n'est pas toujours possible. Pour centrer une image prévue pour être alignée à gauche, celle-ci ne doit pas être située à l'intérieur d'un paragraphe sous peine de couper ce dernier en deux. Il arrive aussi que l'utilisateur désigne dans son texte un média par sa position : "voir graphique ci-contre à gauche".

Concernant les modificateurs liés à l'apparence comme "darkBlue", votre argumentation est celle des bonnes pratiques des années 2000. Elle est fausse en tout cas dans mon expérience : à chaque fois qu'un client demande une refonte du design, je dois de toute manière refaire aussi le code serveur (PHP) et le HTML en plus du CSS. Cela n'existe pas, en pratique, un client qui demande une refonte n'impactant que le code CSS.

Dans un travail d'intégration, le code HTML est la petite partie facile du travail. Le code en langage serveur qui génère ce HTML, et le code CSS, demandent tous deux bien plus de travail. Si le code en langage serveur est bien factorisé, alors la modification d'une classe dans un template HTML est une chose plus simple et surtout plus sûre que des modifications dans le code CSS. Changer le code CSS implique de re-tester sur tous les écrans. Réutiliser une classe déjà bien testée ne demande aucun travail additionnel. Les complètes remises à plat font de toute manière repartir de zéro. Mais avec une approche en partie visuelle, les modifications marginales demandées par le client demandent finalement moins de travail.

Si des "darkBlue" vous paraissent exagérés, que diriez-vous de factoriser dans une classe "darkBgLightFg" des propriétés visuelles pour les blocs de couleurs inversées ? Ensuite vous vous apercevrez que votre design en contient trois variantes : une en bleu, une en vert, une en rose, et alors comment nommer les classes correspondantes d'une manière facile à mémoriser ? Vous pourriez imaginer les numéroter mais en vérité le nom d'un modificateur visuel est lié au travail de votre designer. Celui-ci a travaillé sur les trois couleurs bleu, vert et rose et pas sur d'autres. S'il devait changer le rose en violet, qui sait s'il n'imaginerait pas deux ou bien quatre variantes en les affectant différemment aux blocs existants ?

Nico3333fr a dit le

@Tarh
> Cela n'existe pas, en pratique, un client qui demande une refonte n'impactant que le code CSS.

Je vais faire mon chieur : ça arrive… mais très très rarement ! (du coup je suis moins chieur là :) )

En tout et pour tout, je crois avoir eu le cas 3/4 fois en 10 ans de métier.

La séparation parfaite structure/présentation qui ne permettrait de modifier que la CSS est un quasi-mythe en milieu professionnel (et oui, c'est qqu'un qui a 27 CSS alternatives sur son site perso qui le dit). Ceci dit, il y a moyen lors de rapides refontes de gagner du temps avec des CSS bien conçues et de peu toucher à la structure (j'ai dit «peu» et pas «pas»).

Et les classes atomiques sont en général pas si handicapantes : si tu veux simuler affichage deux colonnes pour du contenu, en général, c'est rare que ça doive totalement changer dans la refonte. Évidemment, faut pas utiliser ça n'importe comment :)

À choisir entre une CSS qui ne grossit pas/peu/moins et un super-pouvoir de ne toucher qu'à la CSS lors de chaque refonte (qui n'arrive jamais), je choisis direct le premier : les perfs et la maintenabilité sont 100 fois plus importantes.

tmos a dit le

Merci beaucoup pour cet article, il m'a permis de revoir mon approche des CSS, en particulier sur des gros projets, qui deviennent vite un gouffre si on ne s'organise pas un peu…

Mais une question subsiste : qu'en est-il de l'organisation du code ? Fichiers séparés ? Délimitation de chaque bloc par ordre de création ? Quelle méthode privilégieriez-vous ?

Au vu des efforts déployés pour arriver à une méthodologie aussi efficace que stricte, il serait dommage de ne pas penser au rangement des lignes, non ?

Tarh a dit le

Personnellement, dans le cadre d'une intégration, j'utilise des fichiers SASS/SCSS séparés, un par type d'écran, fusionnés en un seul fichier CSS au moyen d'importations. Voici à quoi ressemble mon fichier principal (les extensions ".scss" sont omises) :

// … Ici un Reset CSS …
@import "desktop";
@import "tablet";
@import "mobile";

Si certaines parties du site demandent un code volumineux, comme des sliders un peu complexes, on peut les mettre dans des fichiers SCSS dédiés. Mais au final, un seul fichier CSS est suffisant comme l'indique Google : http://google-styleguide.googlecode.com/svn/t... (section "Separation of Concerns").

Dans le cadre d'une application Web ça va dépendre du framework. Je travaille sur le framework Woc.ts ( https://github.com/tarh/woc.ts/releases/ ) dont le but est de développer "par composants" sans polyfills, juste avec une bonne organisation du code. Les fichiers CSS sont alors séparés, un par composant, ils sont ensuite fusionnés lors du déploiement.

PS. Il ne faut pas s'imaginer que je snobe les messages auxquels je ne réponds pas, je les lis tous avec intérêt. :)

erwan21a a dit le

@Tarh :
Mon expérience contredit la tienne. Mais te réponse convenablement nécessiterait d'écrire un texte aussi complet que celui que tu nous a proposé. Et vu que ton article est bien réalisé (même si je ne suis pas d'accord), ça met la pression. ahah

Je vais donc réfléchir à tout ça quelques temps, maturer mes arguments et les confronter profondément aux tiens. Et peux être que je proposerais un article à Alsacréation d'ici peu. ;)