Quelles conventions CSS utiliser pour des structures HTML réutilisable ?

Je viens de (re)terminer la lecture du Guide CSS Fr et j'ai eu envie d'apporter quelques modifications et ajouts à ces très bons conseils. Cet article va donc en quelque sorte constituer mes conventions en matière de création et maintenance HTML et CSS. Elles sont donc identiques à ce qui est écrit sur Guide CSS Fr à cet article prêt !

Anatomie d'une règle CSS
Affreux sélecteur... http://www.puce-et-media.com/

N'ayant rien à ajouter aux parties autres que « 4. Convention de nommage », je passe directement à mes propres conventions de nommage en vous sensibilisant à l'anatomie d'une page HTML.

Je finirai néanmoins par pointer du doigt une erreur, sinon la seule, en ce qui concerne la totale inutilité du préfixe .js- destiné à séparer le visuel du fonctionnel.

Note : Tous les exemples d'inclusion de fragment HTML sont tirés du framework Node.js NodeAtlas mais c'est juste à titre illustratif, c'est la même chose avec vos frameworks préférés.

Composition d'une page

Chaque page HTML est composée par :

  • un gabarit de page ou disposition de page (nommé « template » ou « layout » en anglais),
  • des composants (nommé « components » en anglais) et
  • des patrons de conception (nommé « patterns » en anglais).

Avant de nous attacher au gabarit qui représente la structure qui va accueillir nos composants sur une page, intéressons nous au plus important : les composants eux-mêmes.

Les composants

Structure

Les composants sont des fragments de HTML qui découpent une page de contenu HTML de manière logique et consistante. Bien qu'ils puissent être spécialisés et quasiment dédiés à un seul endroit —comme l'en-tête (souvent nommé « header ») de site—, ils sont prévus pour être :

  • déplaçable dans une page,
  • réutilisable sur d'autres pages.

À cet effet, il est généralement bon d'écrire un composant dans un fichier dédié. Par exemple dans le dossier views de NodeAtlas, on pourrait créer un fichier components/hearder.htm et l'utiliser avec <?- include('components/header.htm') ?> dans un gabarit de page HTML.

Un composant est obligatoirement composé d'au moins deux <div> imbriquées (ou toutes autres balises de type block) pour pouvoir se suffire à lui-même et respecter n'importe quel design sans être disposé dans un gabarit complexe (voir plus loin). D'ailleurs il ne doit disposer que d'une seule balise racine.

La première <div> représente la place totale qu'occupera le composant, design visuel inclus (background généralement) et peut varier sémantiquement avec les éléments HTML5 : header, footer, section, article, aside ou nav. Nous reviendrons plus bas sur cet aspect.

La seconde <div> représente la limite du contenu en lui-même (1200px maximum habituellement). Elle contiendra toujours le prefixe de classe cmpt- (pour « component » en anglais) qui nous permettrait par exemple de définir la taille maximale d'affichage du contenu des composants, pour tous les composants, quand ils sont libres (utilisés en dehors d'une grille, ce que nous verrons plus loin).

Voici par exemple un composant HTML dans views/components/cmpt-<name-of-component>.htm

<div class="cmpt-<name-of-component>">
    <div class="cmpt-<name-of-component>--ui">
        <!-- Sous-parties du composant ici. -->
    </div>
</div>

La <div> de classe cmpt-<name-of-component>--ui fixe la taille maximale de chaque composant grâce à par exemple...

...cette directive CSS dans assets/stylesheets/common.css pour tous les composants

[class^="cmpt-"][class$="--ui"] {
    margin-left: auto;
    margin-right: auto;
    max-width: 1200px;
}

ou ponctuellement dans assets/stylesheets/cmpt-<name-of-component>.css avec

.cmpt-<name-of-component>--ui {
    max-width: 1024px;
}

Mais aucune taille width ou height ne doit être forcée de manière à ce que le contenu du composant soit toujours fluide, quelque soit sa place dans une grille.

Pourquoi deux « div » imbriquées et une « --ui » partout ?

Je vais vous laisser voir cela à travers les divers exemples d'affichages réalisables sans gabarit complexe (pas de grille) et à partir du même code HTML.

Nommage

Dans le Guide CSS Fr, il est expliqué que si le nom du composant est : my-component, alors celui d'un sous composant serait my-component__text et une version alternative de se composant serait permise avec la classe my-component--alternative à la place. Pour ma part j'ai opté pour quelque chose de différent pour des questions de lisibilité du __ ce qui nous permet de ne pas se soucier de séparer les classes par deux espaces.

  1. Le nom du composant est unique. Par exemple .cmpt-presentation-items.

     <div class="cmpt-presentation-items">
         <div class="cmpt-presentation-items--ui">
             <!-- Sous-parties -->
         </div>
     </div>
    
  2. Une sous partie de composant est nommée par exemple .cmpt-presentation-items--title, .cmpt-presentation-items--content ou encore .cmpt-presentation-items--item--title.

     <div class="cmpt-presentation-items">
         <div class="cmpt-presentation-items--ui">
             <div class="cmpt-presentation-items--title">
                 <!-- Titre -->
             </div>
    
             <ul class="cmpt-presentation-items--items">
                 <li class="cmpt-presentation-items--item">
                     <div class="cmpt-presentation-items--subtitle"> <!-- ou `cmpt-presentation-items--item--title` -->
                         <!-- Sous-titre -->
                     </div>
    
                     <div class="cmpt-presentation-items--content">
                         <!-- Contenu -->
                     </div>
                 </li>
             </ul>
         </div>
     </div>
    

    et est adressé en CSS directement par son sélecteur CSS

     .cmpt-presentation-item {
         background-color: #f00;
     }
    
     .cmpt-presentation-item--title {
         font-size: 1.4rem;
     }
    
     .cmpt-presentation-item--content {
         padding: 20px;
     }
    
  3. La version alternative d'un composant n'est pas préfixée par son nom. Au lieu de cela, le comportement alternatif est nommé et est préfixé par as-. C'est grâce au cumul des classes et non à son changement que l'on appliquera la différence. Par exemple as-carousel.

     <div class="cmpt-presentation-items as-carousel">
         <div class="cmpt-presentation-item--ui">
             <!-- ... -->
         </div>
     </div>
    

    qui est adressé en CSS grâce à la cascade

     /* ... Partie précédente ... */
    
     .cmpt-presentation-item.as-carousel { /* Cas rare de double sélection permise. */
         background-color: #00f;
     }
    
     /*.cmpt-presentation-item*/.as-carousel .cmpt-presentation-item--title {
         font-size: 1.2rem;
     }
    
     /*.cmpt-presentation-item*/.as-carousel .cmpt-presentation-item--content {
          padding: 10px;
     }
    

    ou encore en Less

     .cmpt-presentation-item {
         background-color: #f00;
    
         &.as-carousel {
             background-color: #00f;
         }
    
         &--title {
             font-size: 1.4rem;
         }
    
         &--content {
             padding: 20px;
         }
     }
    
     /*.cmpt-presentation-item*/.as-carousel {
         .cmpt-presentation-item {
             &--title {
                 font-size: 1.2rem;
             }
    
             &--content {
                 padding: 10px;
             }
         }
     }
    

    ou encore mieux, en Stylus

     .cmpt-presentation-item {
         background-color: #f00;
    
         &.as-carousel {
             background-color: #00f;
         }
    
         &--title {
             font-size: 1.4rem;
         }
    
         &--content {
             padding: 20px;
         }
    
         &.as-carousel ^[0] {
             &--title {
                 font-size: 1.2rem;
             }
    
             &--content {
                 padding: 10px;
             }
         }
     }
    

Le gabarit de page

Le gabarit est la carcasse qui va accueillir les composants. On appel souvent cette carcasse la grille (en anglais « Grid »). Dans son état le plus simple, c'est une page HTML sans grille, sans classe de gabarit : une page uniquement composée de composants les uns à la suite des autres.

Le contenu de la page HTML ne doit jamais être immédiatement sous le <body> mais dans une <div> de classe lyt (pour « layout » en anglais) par exemple. Cela permet d'ajouter les scripts HTML à l'extérieur et de pouvoir profiter de :nth-child(x) à l'intérieur sans que le nombre ou la place des items soient altérés par l'ajout d'un <script> (En ce qui concerne les <link>, ça se trouve dans le <head>).

Gabarit simple (sans classe)

Voici à quoi ressemble une page HTML sans grille qui accueil nos composants.

<body>
    <!-- Le minimum de `<script>` nécessaire au démarrage pré-HTML -->
    <div class="lyt">
        <!-- Boucle de composants ici. -->
    </div>
    <!-- Tous les `<script>` soit 99% du Javascript appelé ici. -->
</body>

Gabarit nommé

Il est intéressant d'ajouter un nom à chaque gabarit de manière à pouvoir changer, pour un gabarit précis, le comportement de n'importe quel composant avec le préfixe tmpl- (pour « template » en anglais).

<body>
    <div class="lyt tmpl-home"> <!-- Nom du gabarit -->
        <!-- Boucle de composants ici. -->
    <div>
</body>

En mettant ce nom au sommet de toute balise HTML, cela permet de manipuler les variations CSS de tous les pattrons de conception et composants en fonction du gabarit. En incluant donc la partie en-tête et pied-de-page de votre site au niveau de votre conteneur lyt global. Je peux donc changer l'arrière plan du site, spécifiquement pour le gabarit de classe tmpl-home.

Avec ce code CSS par exemple dans assets/stylesheets/common.css

.lyt {
    background-image: none;
}

/* ... */

/*.lyt*/.tmpl-home {
    background-image: url('../media/images/ads.png');
}

Gabarit complexe

Il est possible de créer des gabarits plus complexes en permettant de les habiller avec un système de grille pour avoir une page plus ordonnée.

Un exemple de composants répartis dans des airs lyt-area et lyt-part destinées à être habillées par une grille.

<div class="lyt tmpl-home">
    <section class="lyt-area for-main">
        <div class="lyt-part for-overview">
            <!-- Boucle de composants ici. -->
        </div>
        <aside class="lyt-part for-ads">
            <!-- Boucle de composants ici. -->
        </aside>
    </section>
    <section class="lyt-area for-presentation">
        <!-- Boucle de composants ici. -->
    </section>
    <aside class="lyt-area for-also">
        <!-- Boucle de composants ici. -->
    </aside>
</div>

avec un habillage de grille (Less avec Bootstrap par exemple)

@import (reference) 'bootstrap/bootstrap';

.tmpl-home {
    .lyt-area {
        .container; /* Ajouter une marge interne gauche et droite... */
    }

    /*.lyt-area*/.for-presentation {
        padding: 0; /* ...et l'enlever spécifiquement pour la présentation. */
    }

    .lyt-part {
        .make-sm-column(6); /* créer des colonnes de 6 sur 12 */
    }
}

Rien ne vous empêche non plus de changer la grille (l'application CSS sur le gabarit) en fonction des besoins, ce que vous ne pourriez pas faire si vous appliquer des patrons de conceptions comme col-xs-6 directement dans la grille (nous verrons cela plus bas).

Par exemple en ajoutant with-ads à votre gabarit, vous pourriez faire varier votre feuille CSS de manière à accueillir de la publicité de part et d'autre de la page.

<div class="lyt tmpl-home with-ads">
    <!-- Grille de gabarit -->
</div>

Les patrons de conception

Ce sont des modèles prêts à l'emploi destinés à être apposés sur une balise, ou, une balise contenant un ensemble de balise. Lors de sa définition, le patron nécessite « obligatoirement » un commentaire CSS sur son utilisation dans un code HTML.

Contrairement aux gabarit et composants, les patrons ne sont pas ciblés depuis la feuille CSS vers une classe « sémantique » mais mis à la main sur une balise HTML. En ce sens, ils sont parfait pour permettre à des rédacteurs de contenu de faire de la mise en page ou faire votre grille CSS dans le gabarit de manière HTML-Driven plutôt que CSS-Driven.

Dans nos exemples précédents, lyt-area et lyt-part pourrait être des patrons avec chacun un rôle « raccourci » spécifique (ici de mettre le code dans un containeur pour lyt-part. Les patrons peuvent donc servir dans les composants, mais également dans les gabarit en fonction du besoin.

Voici des exemples de patrons :

/**
    <div class="text-center">
        Ce texte est centré.
    </div>
*/
.text-center {
    text-align: center;
}

/**
    <div class="img-responsive">
        <img src="fit-container-in-all-size.png">
    </div>
* ou
    <img class="img-responsive" src="fit-container-in-all-size.png">
*/
.img-responsive {
    max-width: 100%;
    height: auto;

    img {
        max-width: 100%;
        height: auto;
    }
}

/**
    <button class="btn btn-primary">
        Click Me
    </button>
*/
.btn
    display: inline-block;
    padding: 4px 8px;
}
.btn.btn-lg {
    background-color: blue;
}

/**
    <div class="container">
        <!-- Des types blocks ou inlines -->
    </div>
*/
.container {
    max-width: 1200px;
    margin-left: auto;
    margin-right: auto;
}

Les patrons étant applicables dans tous les composants, un comportement spécifique à un patron peut-être spécifié dans le fichier du composant de cette manière :

Exemple de patron CSS surchargé spécifiquement pour un composant

/**
    <div class="color-alternative">
        I am white !
    </div>
*/
.color-alternative {
    color: white;
}

/* ... */

/**
    <div class="div.cmpt-footer">
        <div class="color-alternative">
            I am white !
        </div>
    </div>
*/
.cmpt-footer {
    background-color: #fff;
}
.cmpt-footer .color-alternative {
    color: black;
}

Mon conseil : un patron doit toujours être placé dans une zone dédiée au contenu et non au composant en lui-même. Il est parfait dans du HTML qui pourrait avoir vocation à finir dans une base de donnée de contenu. Aussi n'utilisez jamais un .text-right sur un .component--subcomponent car rien ne garanti que dans un autre contexte (gabarit), le texte ne serait pas à gauche. Et en ce qui concerne leur utilisation dans les gabarits : tout dépend si vous préférez intervertir les gabarits HTML (permet d'utiliser des grilles Bootstrap par exemple) quand la carcasse varie ou ajouter une classe .with-this-variation comme vu précédemment.

Classes alternatives

class="cmpt-<name-of-component>" sert à identifier toutes les portions de HTML relatives à se composant dans une page. En ajoutant des classes, on peut varier l'habillage ou identifier précisément une instance.

  • as-* : permet de faire varier le style / comportement d'un composant / gabarit. Exemple : class="cmpt-<name-of-component> as-popup" pour permettre au composant de s'afficher comme une popup.

  • with-* : un équivalent de as-*. Vous pouvez par exemple réserver as-* pour les composants et with-* pour les gabarits.

  • is-* : permet de décrire l'état d'un composant / gababrit afin d'activer / désactiver des visuels et / ou des fonctionnalités ou une capacité à permettre une fonctionnalité. Exemple : class="cmpt-<name-of-component> is-opened" pour décrire que la popup est ouverte ou class="cmpt-<name-of-component> is-closable" pour dire que la popup peut être manuellement fermée.

  • for-* : permet d'identifier précisément un composant / gabarit parmi plusieurs autres du même type. Exemple : class="tyl-area for-ads" pour identifier le contenu des composants qui seront placés dans cet endroit du gabarit comme étant de la publicité par exemple.

CSS et JS : une seule classe pour les gouverner toutes

Nous y voilà, au point qui à lui seul m'a donné l'envie d'écrire cet article.

Le Guide CSS FR nous dit « N'utilisez jamais une classe de style CSS pour vos ancres JavaScript. Associer un comportement JavaScript à une classe de style signifie que nous ne pourrons jamais avoir l'un sans l'autre. » et nous donne comme exemple : « is-sortable js-is-sortable »

Cela n'est pas un conseil à suivre. Je m'explique.

  1. Dans le cas des patrons ; une unique classe doit se suffire à elle-même. C'est précisément parce qu'elle a vocation à être apposée à la main qu'il ne doit pas être possible de gérer le visuel et le JavaScript indépendamment.

    Soit tous les .btn-popup ouvre une popup, soit aucun. Il n'y a pas de raison que les .btn-popup n'ouvre pas les popups et que les .btn-popup.js-btn-popup les ouvre. Le .btn-popup se suffit à lui-même. Si toutefois on souhaite un bouton identique au .btn-popup, sans que celui-ci n'ouvre de popup, c'est dans la CSS et le HTML que ça se passe avec par exemple les règles CSS .btn-popup, .btn-foo { /* même design */ } que l'on applique sur l'élément HTML .btn-foo. Ainsi si l'utilisateur veut un bouton qui n'ouvre pas de popup mais qui est visuellement identique, il utilisera .btn-foo par exemple.

    Une autre manière de faire (que je préfère réserver exclusivement aux composants et gabarits) serait alors les règles CSS .btn-foo { /* design */ } .is-popup-openable { cursor: pointer; } appliqué sur un élément HTML .btn-foo.is-popup-openable. Comprenez bien ici que le cursor: pointer et l'action de rendre cliquable sont encore une fois liés à une unique classe qui est .is-popup-openable et qu'il n'y a pas de sens à utiliser .is-popup-openable.js-is-popup-openable. Soit la classe y est et l'élément est cliquable avec une main, soit elle n'y ai pas et l'élément n'est pas cliquable sans main.

  2. Dans le cas des composants et des gabarits, c'est pareil. Il est cependant envisageable que dans un cas on souhaite afficher un composant de manière standard, et dans un autre cas on souhaite lui faire exécuter un script pour, par exemple, gérer un défilement automatique d'élément ; une visionneuse.

    Cela n'a pas de sens que .cmpt-presentation-items ne soit pas un visionneur mais que .cmpt-presentation-items.js-cmpt-presentation-items en soit un. Non, cela a plus de sens que .cmpt-presentation-items n'en soit pas nécessairement un mais que .cmpt-presentation-items.as-slider en soit un.

    Pas besoin de préciser .js- car c'est au moment de sélectionner un composant par la classe le désignant (et sa variation ou non) dans nos fichiers JavaScript qu'on décide qu'un code JavaScript doit s'appliquer dessus : et absolument pas parce que nous l'avons décidé en ajoutant la même classe avec .js- devant.

    Exemple : Il est possible que j'ajoute du JavaScript pour faire fonctionner .cmpt-presentation-items pour par exemple faire du Lazy Loading de contenu ou que je n'en utilise pas sur .cmpt-presentation-items.as-slider car mes transitions automatiques sont gérées en CSS3 avec transition et animation.

En conclusion : style et comportement doivent être liés à la même classe. Si une autre classe doit être ajoutée, c'est pour décrire un autre comportement visuel et / ou fonctionnel.

Des composants réellement déplaçable partout

La problématique lorsque l'on déplace des fragments de HTML, c'est de ne plus respecter l'arborescence des <h1-h6>. Car si un composant possède un <h1>, alors il peut difficilement être mis une fois dans le haut de la page et une fois en bas sans faire hurler au scandale les experts SEO. Réglons ce problème.

Rappel sur les block sémantiques

Sachez qu'en HTML5, il peut y avoir plus d'un <h1> par page mais pas à n'importes quelles conditions.

Sous la balise <body> : l'intégralité des <h1-h6> dispersés entre les balises forment un index hiérarchique. L'élément <h1> doit être unique. Mais les balises <section>, <article>, <aside> et <nav> remettent les compteurs à zéro et sous chacune de ces balises il est de nouveau possible de disperser des <h1-h6> avec un seul <h1>, etc.

Chaque nouvel espace de <section>, <article>, <aside> et <nav> peut lui-même contenir un <header> et un <footer>. L'importance des <h1-h6> est donc à présent bi-directionnel voir tri-directionnel : un <h2> est d'autant plus important qu'il se trouve directement dans <body> à l'intérieur d'un <header> au contraire d'un <h1> sous une pile de plusieurs <section>. Il est même probable que un body > article > header > h1 dans une page avec une unique balise <article> est plus de poids que le body > header > h1 du titre de page (mais je spécule, ce n'ai pas moi qui fait les règles des algorithmes).

En tout cas le voilà ! Notre moyen de rendre déplaçable les composants comme des sortes de modules pouvant chacun contenir : un <header>, un <footer> et une hiérarchie <h1-h6>.

L'application simple : faire gérer le cloisonnement par le gabarit

Le moyen le plus simple est de gérer le cloisonnement avec le gabarit. Imaginons ce gabarit :

<div class="lyt tmpl-home">
    <header class="lyt-area for-header">
        <!-- Boucle de composants ici. -->
    </header>
    <section class="lyt-area for-main">
        <div class="lyt-part for-overview">
            <!-- Boucle de composants ici. -->
        </div>
        <aside class="lyt-part for-ads">
            <!-- Boucle de composants ici. -->
        </aside>
    </section>
    <section class="lyt-area for-presentation">
        <!-- Boucle de composants ici. -->
    </section>
    <aside class="lyt-area for-also">
        <!-- Boucle de composants ici. -->
    </aside>
    <footer class="lyt-area for-footer">
        <!-- Boucle de composants ici. -->
    </footer>
</div>

ainsi que ce Component :

<div class="cmpt-component">
    <div class="cmpt-component--ui">
        <header class="cmpt-component--header">
            <h1>Title<h1>
        </header>
        <div  class="cmpt-component--content">
            <h2>Subtitle</h2>
            <p>Text</p>
            <p>Text</p>
        </div>
        <footer class="cmpt-component--footer">
            <h3>Lien<h3>
        </footer>
    </div>
</div>

La contrainte avec ce composant est donc qu'il ne pourra jamais être placé dans .for-header ni dans .for-footer sous peine d'injecter un double <header> et <footer> dans le <body>. Cela peut être évité en estimant que les composant d'en-tête et de pied-de-page globaux sont spécifiques à ces zones et ne seront pas déplaçables (comme c'est pratiquement toujours le cas).

On voit cependant que hors-mi ces zones, notre composant peut atterrir un peu partout ou il le souhaite sans que cela ne pose de soucis.

L'application complexe : faire gérer le cloisonnement au moment de l'injection du composant, hors gabarit et hors composant

Penchons nous de nouveau sur NodeAtlas qui va nous permettre de créer des injections de composant dynamique ! Vous pouvez faire des systèmes similaires de vos côtés avec vos outils ou frameworks préférés. Cela n'est qu'un brouillon car on pourrait même envisager l’injection de composants dans des composants, etc.

Avec...

  1. La configuration webconfig.json :

    {
     "controller": "common.js",
     "routes": {
         "/" : {
             "view": "home.htm",
             "variation": "home.json"
         }
     }
    }
    
  2. Le gabarit views/home.htm :

    <!-- ... -->
    <div class="lyt tmpl-home">
     <div class="lyt-area for-header">
         <?- includeComponent('placeholder-header') ?>
     </div>
     <div class="lyt-area for-main">
         <div class="lyt-part for-overview">
             <?- includeComponent('placeholder-overview') ?>
         </div>
         <div class="lyt-part for-ads">
             <?- includeComponent('placeholder-ads') ?>
         </div>
     </div>
     <div class="lyt-area for-presentation">
         <?- includeComponent('placeholder-presentation') ?>
     </div>
     <div class="lyt-area for-also">
         <?- includeComponent('placeholder-also') ?>
     </div>
     <div class="lyt-area for-footer">
         <?- includeComponent('placeholder-footer') ?>
     </div>
    </div>
    <!-- ... -->
    
  3. Le Fichier de variation variations/home.json :

    {
     "components": {
         "placeholder-header": [{
             "path": "views/component.htm",
             "variation": {
                 "mainTag": "div"
             }
         }, {
             "path": "views/component.htm",
             "variation": {
                 "mainTag": "header"
             }
         }],
    
         "placeholder-overview": [{
             "path": "views/three-boxes.htm",
             "variation": {
                 "mainTag": "section"
             }
         }],
    
         "placeholder-ads": [{
             "path": "views/component.htm",
             "variation": {
                 "mainTag": "aside"
             }
         }],
    
         "placeholder-presentation": [{
             "path": "views/three-boxes.htm",
             "variation": {
                 "mainTag": "article"
             }
         }],
    
         "placeholder-also": [{
             "path": "views/component.htm",
             "variation": {
                 "mainTag": "aside"
             }
         }],
    
         "placeholder-footer": [{
             "path": "views/component.htm",
             "variation": {
                 "mainTag": "footer"
             }
         }, {
             "path": "views/component.htm",
             "variation": {
                 "mainTag": "div"
             }
         }, {
             "path": "views/component.htm",
             "variation": {
                 "mainTag": "div"
             }
         }]
     }
    }
    
  4. Le composant views/components/component.htm :

    <?- sc('<section class="cmpt-component">', component) ?>
     <div class="cmpt-component--ui">
         <?- sc('<header class="cmpt-component--header">', component) ?>
             <?- sc('<h1>', component) ?>Title<?- sc('</h1>', component) ?>
         <?- sc('</header>', component) ?>
         <div class="cmpt-component--content">
             <?- sc('<h2>', component) ?>Title<?- sc('</h2>', component) ?>
             <p>Text</p>
             <p>Text</p>
         </div>
         <?- sc('<footer class="cmpt-component--footer">', component) ?>
             <?- sc('<h3>', component) ?>Lien<?- sc('</h3>', component) ?>
         <?- sc('</footer>', component) ?>
     </div>
    <?- sc('</section>', component) ?>
    

...on pourrait parvenir à nos fins. Pendant la réalisation de nos composants on utilise toujours une <section>. C'est au moment d'injecter le composant que l'on décide grâce à mainTag quelle balise globale va remplacer <section>. On s'assurerait également de transformer tous les <header> en <div class="header-like"> (etc.) dans le cas où le mainTag serait div, header ou footer.

Voici pour les petits curieux le code NodeAtlas de controllers/common.js qu'il faut pour faire fonctionner tout ça.

De la sémantique automatique

Bon, vous conviendrez que notre composant est assez laid, et ça rend la chose faussement complexe. Je vous propose de plutôt maintenir un fichier views/components/component.htm comme celui-ci :

<section$ class="cmpt-component">
    <div class="cmpt-component--ui">
        <header$ class="cmpt-component--header">
            <h1$>Title</h1$>
        </header$>
        <div  class="cmpt-component--content">
            <h2$>Title</h2$>
            <p>Text</p>
            <p>Text</p>
        </div>
        <footer$ class="cmpt-component--footer">
            <h3$>Lien</h3$>
        </footer$>
    </div>
</section$>

grâce au projet ComponentAtlas qui est un module additionnel de NodeAtlas. Il permet l'inclusion de composant en cascade, ne touchera à aucunes balises sans $ et permettra de transformer ceci :

<section$ class="cmpt-component">
    <header$ id="test">header</header$>
    <footer$ class="test">footer</footer$>
    <h1$ class='test'>h1</h1$>
    <h2$ class='test' id="test">h2</h2$>
    <h3$ id="test" class="test">h3</h3$>
    <h4$ id="test" class='test'>h4</h4$>
    <h5$ id='test' class='test'>h5</h5$>
    <h6$>h6</h6$>
</section$>

en ceci :

<header class="cmpt-component">
    <div class="header-like" id="test">header</div>
    <div class="footer-like test">footer</div>
    <div class='h1-like test'>h1</div>
    <div class="h2-like test' id="test">h2</div>
    <div class="h3-like test">h3</div>
    <div class='h4-like test'>h4</div>
    <div class='h5-like test'>h5</div>
    <div class="h6-like">h6</div>
</header>

si le maintTag était header ou de le transformer en ceci :

<section class="cmpt-component">
    <header id="test">header</header>
    <footer class="test">footer</footer>
    <h1 class='test'>h1</h1$>
    <h2 class='test' id="test">h2</h2>
    <h3 id="test" class="test">h3</h3>
    <h4 id="test" class='test'>h4</h4>
    <h5 id='test' class='test'>h5</h5>
    <h6>h6</h6>
</section>

sans mainTag de précisé.

Aller plus loin

Si vous désirez aller encore plus loin dans les conventions HTML et CSS pour bien séparer les zones dédiées aux composants et les zones dédiées au contenu, vous pouvez lire mes conventions HTML, CSS et JS complètes.