Niveau Niveau confirmé

Bonnes pratiques en CSS : BEM et OOCSS

Articlecss

Publié par le , mis à jour le (214732 lectures)

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 a eu une histoire chahutée. Au début des années 2010, plusieurs auteurs majeurs ont apporté un regard nouveau et sont allés à contre-courant, en rupture avec ce qui faisait consensus durant la décennie précédente. 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 classes utilitaires
  6. Cas d'utilisation, partie 1 : HTML
  7. Cas d'utilisation, partie 2 : SCSS
  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é et intéressons-nous à BEM.

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 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 » d'une application JavaScript. Cetains composant embarquent leurs propres règles CSS, et il existe des technologies de développement qui aident en "scopant" le CSS dans son composant. Le format de nommage de BEM remplace ces outils avec élégance et simplicité.

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 syntaxe prévue pour un é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 bloc 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 bloc 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 bloc ?

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 :

  • BlockName
  • BlockName.modifierName
  • BlockName-elementName
  • BlockName-elementName.modifierName

Revoici l'exemple en HTML, contenant de surcroit un élément keyword :

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

Ingérences transversales et classes utilitaires

La méthodologie BEM apporte une solide séparation entre les contextes CSS des différents blocs CSS. Pourtant, tous ces contextes reposent en définitive sur du sable mouvant. Les blocs supposent en effet toujours un certain contexte CSS global, comme l'existence d'un reset CSS pour annuler les marges.

Et il est justifié de jouer volontairement sur la corde transversale en attribuant aux éléments du DOM des classes CSS ne respectant pas une arborescence de blocs. Je pense en particulier aux classes utilitaires d'un framework CSS. Les classes des frameworks CSS existants n'utilisent pas les majuscules. elles ne provoquent donc que peu d’ambiguïté avec notre syntaxe BEM. Il est aisé d'utiliser les deux sur le même projet.

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

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 blocs 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 bloc 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 blocs 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 éléments). Et dans le cas de la barre latérale, l'élément sidebar et le bloc Sidebar sont associés au même tag HTML. Cela est autorisé par BEM : on dit alors qu'il s'agit d'un mix.

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"><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>

Notons la présence du modificateur sticky sur la première des deux instances du bloc 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 éléments frères : SiteHeader-titles, SiteHeader-h1 et SiteHeader-h2, tous trois sont des enfants du bloc SiteHeader alors que dans l'arbre DOM les deux derniers sont des enfants du premier. Autre exemple : l'élément BodyLayout-sidebar est le parent du bloc 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 éléments ou des blocs. 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 bloc 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 bloc. 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, l'élément SiteHeader-titles pourrait être un bloc SiteTitles. Dans la mesure où le bloc 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 bloc qui sera un contexte à part pour les titres du site.

Cas d'utilisation, partie 2 : SCSS

Dans cette partie, je vais utiliser le préprocesseur SASS en variante SCSS. En SCSS, 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 SCSS, 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 de l'élément BodyLayout-sidebar et du bloc 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 bloc 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 bloc 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 bloc et son conteneur sont en réalité le même élément HTML. Un mix demande de la rigueur : l'élément est chargé du positionnement dans son bloc, le bloc ne doit donc recevoir aucune règle CSS qui aurait un effet sur ce positionnement.

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;
}

// Global
body {
  background-color: #EEB;
}

// BEM
.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

Un mot à propos du positionnement. Le principe général est que les blocs 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 bloc et de ne toucher à aucune propriété qui influencerait le positionnement du bloc 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 bloc et ne gène donc aucunement sa réutilisation. Par exemple, le bloc PageWrapper dans le code donné ci-dessus contient son propre positionnement centré.

Conclusion

La méthodologie de nommage de BEM fournit une robuste armature à nos pages Web. Grâce à ses syntaxes préfixées et à la séparation rigoureuse entre les contextes des blocs CSS, elle s'intègre naturellement dans un environnement fait de composants JavaScript. Cela n'empêche pas d'utiliser simultanément des frameworks CSS. Les manipulations en dehors de toute hiérarchie tirent parti des capacités si particulières des navigateurs, ne nous en privons pas.

Je propose de finir avec une réflexion sur le nommage par la sémantique versus par l'apparence. Le code CSS est utilisé pour « habiller » les blocs de contenu. Imaginons que les habits soient notre métier. Nous devons créer deux vêtements, le premier pour Albert, le second pour Nicolas. Pour des raisons pratiques, il nous faut les nommer. La méthode naïve consiste à appeler ces vêtements ainsi : « habit d'Albert » et « habit de Nicolas ». Il s'agit d'un nommage selon la sémantique du contenu. Telles étaient les bonnes pratiques en CSS dans les années 2000.

Le temps passe et puis Pierre arrive. Et nous devons l'habiller. Mais notre dénomination empêche la réutilisation. Il faudrait créer un nouvel « habit de Pierre » même si celui-ci serait très similaire à celui d'Albert. Nous changeons donc notre façon de travailler pour faciliter la réutilisation de notre travail. Nous éviterons désormais intentionnellement de nommer par le contenu (la personne habillée). Nous préférons nommer les vêtements eux-mêmes. On pourrait utiliser des noms abstraits : « habit 001 », « habit 002 », etc. Pourquoi pas ? Ce serait réutilisable. Mais une dénomination plus intuitive demandera moins d'effort de mémorisation et nous nommerons en fonction de ce que nous voyons : le « grand habit bleu », ou le « petit élargi », etc. Et ainsi Albert portera le « grand habit bleu » plutôt que « l'habit de Albert ». Et il ne s'agit pas ici de confondre présentation et contenu.

Avec la sémantique du nommage par le contenu, le code CSS était intrinsèquement lié et dépendant du code HTML. Alors qu'avec le nommage par l'apparence, le code CSS devient un code qui existe par lui-même, séparé du code HTML, et cela permet à des blocs distincts dans le code HTML de mieux réutiliser la même apparence.

Février 2023 : 9 ans ont passé et l'article original avait vieilli, il méritait une réécriture pour rester utile au lecteur d'aujourd'hui. C'est chose faite.

Commentaires

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.

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...

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.

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.

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.

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 ?

@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.

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 ?

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. :)

@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. ;)

Bonjour @paleo, je suis tombé sur ton article par hasard alors que je cherchais une approche CSS pour utiliser les attributs html comme modifier (je cherche toujours un article qui en parle)

Ce qui est fou en lisant ton article et qui me conforte dans le choix de mon approche existante, c'est que je suis arrivé à la même conclusion que toi avec une légère différence :

.BlockName-elementName.-modifierName

Je préfix la class du modifier par un tiret car ça me permet tout de suite de voir que c'est une class modifier dans mon html et que ce n'est pas une autre classe quelconque qui ne respecterait pas la convention nommage ou qui serait utilisée par le CMS ou des lib externes.

j'utilise cette approche depuis plusieurs années maintenant et elle est bien éprouvée et j'ai beaucoup de mal lorsque je repasse en TMA sur des projets qui utilisent du BEM ou du OOCSS pure.

Tu lui avais trouvé un nom à cette approche ? moi je pensais à BEUM (Block Element Utility Modifier) mais c'est pas très jolie