Conventions HTML et CSS orientés composants

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 de NodeAtlas mais 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 que nous nommerons ici Template,
  • des Composants que nous nommerons ici Components et
  • des Patrons de conception que nous nommerons ici Patterns.

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

Les Components (Composants)

Structure

Les Components 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 le 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 Component dans un fichier dédié. Par exemple dans le dossier components de NodeAtlas, on pourrait créer un hearder.htm et l'utiliser avec <%- include('header.htm') %> dans un Template de page HTML.

Un Component est obligatoirement composé de 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 Template Complexe (voir plus loin).

La première <div> représente la place totale qu'occupera le Component, design visuel inclus (background généralement) et peut varier sémantiquement avec : 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 la classe .ui (par exemple) qui nous permet de définir la taille maximale d'affichage du contenu des Components, pour tous les Components, quand ils sont libres (utilisés en dehors d'une grille, ce que nous verrons plus loin).

Voici par exemple un Component HTML dans components/name-of-component.htm

<div class="name-of-component">
  <div class="ui">
    <!-- Subparts of component here. -->
  </div>
</div>

La <div> de classe .ui fixe la taille maximale dans chaque composant grâce à par exemple...

...ces directives CSS dans assets/stylesheets/common.css

.ui {
  margin-left: auto;
  margin-right: auto;
  max-width: 1200px;
}

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

.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 Template 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 Component est : my-component, alors celui d'un sous Component serait my-component__text et une version alternative de se Component 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 Component est unique. Par exemple .presentation-items.

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

    <div class="presentation-items">
     <div class="ui">
       <div class="presentation-items--title">
         <!-- Title -->
       </div>
       <ul class="presentation-items--items">
         <li class="presentation-items--item">
           <div class="presentation-items--subtitle"> <!-- ou `presentation-items--item--title` -->
             <!-- Subtitle -->
           </div>
           <div class="presentation-items--content">
             <!-- Content -->
           </div>
         </li>
       </ul>
     </div>
    </div>
    

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

    .presentation-item {
      background-color: #f00;
    }
    .presentation-item--title {
      font-size: 1.4rem;
    }
    .presentation-item--content {
      padding: 20px;
    }
    
  3. La version alternative d'un Component 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="presentation-items as-carousel">
     <div class="ui">
       <!-- ... -->
     </div>
    </div>
    

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

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

    ou encore en Less

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

Le Template (Gabarit de page)

Le Template est la carcasse qui va accueillir les Components. On appel souvent cette carcasse la Grille ou Grid. Dans son état le plus simple c'est une page HTML sans grille, sans classe de Template : une page uniquement composé de Components 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 layout 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>).

Template Simple (sans classe)

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

<body>
  <!-- Minimum of `script` tags for boot -->
  <div class="layout">
    <!-- Loop of Components here. -->
  </div>
  <!-- All `script` tags for 99% of Javascript here. -->
</body>

Template Nommé

Il est intéressant d'ajouter un nom à chaque Template de manière à pouvoir changer, pour un Template précis, le comportement de n'importe quel Component.

<body class="home"> <!-- Name of Template -->
  <div class="layout">
    <!-- Loop of Components here. -->
  <div>
</body>

En mettant ce nom au sommet de toute balise HTML, cela permet de manipuler les variations CSS de tous les Patterns et Components en fonction du Template. En incluant donc la partie Header et Footer de votre site et même le conteneur .layout global. Je peux donc changer l'arrière plan du site, spécifiquement pour le Template de classe home.

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

.layout {
  background-image: none;
}

/* ... */

.home {

  /**
   * Extend `.layout` in common.less
   */
  .layout {
    background-image: url('../media/images/ads.png');
  }
}

Template Complexe

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

Un exemple de Components répartis dans des .area destinées à être habillées par une grille.

<body class="home">
  <div class="layout">
    <section class="area for-main">
      <div class="part for-overview">
        <!-- Loop of Components here. -->
      </div>
      <aside class="part for-ads">
        <!-- Loop of Components here. -->
      </aside>
    </section>
    <section class="area for-presentation">
      <!-- Loop of Components here. -->
    </section>
    <aside class="area for-also">
      <!-- Loop of Components here. -->
    </aside>
  </div>
</body>

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

@import (reference) 'bootstrap/bootstrap';

.home {
  .area {
     .container; /* Add padding left and right... */
  }
  .for-presentation {
    padding: 0; /* ...and remove it. */
  }

  .part {
    .make-sm-column(6);
  }
}

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

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

<body class="home with-ads">
  <div class="layout">
    <!-- Template Grid -->
  </div>
</body>

Les Patterns (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 Pattern nécessite « obligatoirement » un commentaire CSS sur son utilisation dans un code HTML.

Contrairement aux Templates et Components, les Patterns 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 Template de manière HTML-Driven plutôt que CSS-Driven.

Dans nos exemples précédents, .container, .area et .part pourrait être des Patterns avec chacun un rôle « raccourci » spécifique. Les Patterns peuvent donc servir dans les Components, mais également dans les Templates en fonction du besoin.

Voici des exemples de Patterns :

/** 
  <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>
 * or
  <img class="img-responsive" src="fit-container-in-all-size.png">
 */
.img-responsive {
  max-width: 100%;
  img {
    max-width: 100%;
  }
}

/** 
  <button class="btn-right-arrow">
    <span>Click Me</span>
  </button>
 */
.btn-right-arrow {
  position: relative;
  span {
    display: inline-block;
    padding: 4px 8px;
    position: relative;
    z-index: 2;
  }
  &:after {
    content: "";
    position: absolute;
    top: 50%;
    right: 0;
    .translateY();
    background-image: url('../media/images/arrow.png');
    width: 16px;
    height: 16px;
  }
}

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

Les Patterns étant applicables dans tous les Components, un comportement spécifique à un Component peut-être spécifié dans le fichier du Component de cette manière :

Exemple de Pattern CSS surchargé spécifiquement pour un Component

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

/* ... */

.background-white-component {
  background-color: #fff;

  /**
   * Extend `.color-alternative` in common.less
   */
  /* Pattern */.color-alternative {
    color: #888;
  }
}

Mon conseil : un Pattern 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 (Template), le texte ne serait pas à gauche. Et en ce qui concerne leur utilisation dans les Template : tout dépend si vous préférez intervertir les Templates HTML quand la carcasse varie ou ajouter une classe .with-this-variation comme vu précédemment.

Classes Alternatives

class="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="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 ou afin d'activer/désactiver des visuels et/ou fonctionnalités ou une capacité à permettre une fonctionnalité. Exemple : class="name-of-component is-opened" pour décrire que la popup est ouverte ou class="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="part-of-template for-ads" pour identifier le contenu du composant 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 est faux. Je m'explique.

  1. Dans le cas des Patterns ; 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 { /* same design */ } que l'on applique sur l'élément HTML .btn-foo.

    Une autre manière de faire 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 Components et des Templates, c'est pareil. Il est cependant envisageable que dans un cas on souhaite afficher un Component 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'item ; une visionneuse.

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

    Pas besoin de préciser .js- car c'est au moment de sélectionner un Component 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 .presentation-items pour par exemple faire du Lazy Loading de contenu ou que je n'en utilise pas sur .presentation-items.as-viewer 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 Components 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 Component 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 Components comme des sortes de module pouvant chacun contenir : un <header>, un <footer> et une hiérarchie <h1-h6>.

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

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

<body class="home">
  <div class="layout">
    <header class="area for-header">
      <!-- Loop of Components here. -->
    </header>
    <section class="area for-main">
      <div class="part for-overview">
        <!-- Loop of Components here. -->
      </div>
      <aside class="part for-ads">
        <!-- Loop of Components here. -->
      </aside>
    </section>
    <section class="area for-presentation">
      <!-- Loop of Components here. -->
    </section>
    <aside class="area for-also">
      <!-- Loop of Components here. -->
    </aside>
    <footer class="area for-footer">
      <!-- Loop of Components here. -->
    </footer>
  </div>
</body>

ainsi que ce Component :

<div class="component">
  <div class="ui">
    <header class="component--header">
      <h1>Title<h1>
    </header>
    <div  class="component--content">
      <h2>Subtitle</h2>
      <p>Text</p>
      <p>Text</p>
    </div>
    <footer class="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 Components de Header et de Footer globaux sont spécifiques à ses zones et ne seront pas déplaçable (comme c'est pratiquement toujours le cas).

On voit cependant que hors-mi ces zones, notre Component 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 Component, hors Template et hors Component

Penchons nous de nouveau sur NodeAtlas qui va nous permettre de créer des injections de Component 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 Component dans des Component, etc.

Avec...

  1. Le webconfig suivant webconfig.json :

    {
    "commonController": "common.js",
    "routes": {
     "/" : {
       "template": "home.htm",
       "variation": "home.json"
     }
    }
    }
    
  2. Le Template templates/home.htm :

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

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

    <%- sc('<section class="banner">', component) %>
    <div class="ui">
     <%- sc('<header class="component--header">', component) %>
       <%- sc('<h1>', component) %>Title<%- sc('</h1>', component) %>
     <%- sc('</header>', component) %>
     <div  class="component--content">
       <%- sc('<h2>', component) %>Title<%- sc('</h2>', component) %>
       <p>Text</p>
       <p>Text</p>
     </div>
     <%- sc('<footer class="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 Components on utilise toujours une <section>. C'est au moment d'injecter le Component que l'on décide grâce à mainTag quelle balise globale va remplacée <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 comtrollers/common.js qu'il faut pour faire fonctionner tout ça.

De la sémantique automatique

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

<section$ class="banner">
  <div class="ui">
    <header$ class="component--header">
      <h1$>Title</h1$>
    </header$>
    <div  class="component--content">
      <h2$>Title</h2$>
      <p>Text</p>
      <p>Text</p>
    </div>
    <footer$ class="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 Component en cascade, ne touchera à aucunes balises sans $ et permettra de transformer ceci :

<section$ class="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="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="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é.