Une arborescence peut être créée en utilisant uniquement du HTML et du CSS, sans avoir besoin de JavaScript. Voici un simple tuto pour créer une arborescence en HTML et CSS et comprenant des éléments pour ouvrir et fermer les niveaux.

La structure de base

Une arborescence est finalement un ensemble de liste imbriquées :

<ul>
  <li>
    Informatique
    <ul>
      <li>
        Périphériques
        <ul>
          <li>Clavier</li>
          <li>Souris</li>
          <li>Ecran</li>
        </ul>
      </li>
      <li>
        Stockage
        <ul>
          <li>Disque dur</li>
          <li>SSD</li>
          <li>Clé USB</li>
        </ul>
      </li>
      <li>
        Composants
        <ul>
          <li>Processeur</li>
          <li>Carte mère</li>
          <li>Mémoire</li>
          <li>Carte graphique</li>
        </ul>
      </li>
    </ul>
  </li>
</ul>

Voici le rendu :

details et summary

Le HTML permet d'avoir des blocs qui s'ouvrent / ferment grâce aux balises <details> et <summary>. Utilisons-les dans notre code :

<ul class="tree">
  <li>
    <details open>
      <summary>Informatique</summary>
      <ul>
        <li>
          <details>
            <summary>Périphériques</summary>
            <ul>
              <li>Clavier</li>
              <li>Souris</li>
              <li>Ecran</li>
            </ul>
          </details>
        </li>
        <li>
          <details open>
            <summary>Stockage</summary>
            <ul>
              <li>Disque dur</li>
              <li>SSD</li>
              <li>Clé USB</li>
            </ul>
          </details>
        </li>
        <li>
          <details>
            <summary>Composants</summary>
            <ul>
              <li>Processeur</li>
              <li>Carte mère</li>
              <li>Mémoire</li>
              <li>Carte graphique</li>
            </ul>
          </details>
        </li>
      </ul>
    </details>
  </li>
</ul>

Chaque niveau est encapsulé dans un <details>. L'attribut open permet de dire au navigateur que le bloc est ouvert et donc que son contenu est visible. L'élément <summary> permet de donner un libellé au bloc.

Vous pouvez ainsi cliquer sur "Informatique" pour replier toute l'arborescence ou sur "Stockage" par exemple :

Mise en forme des niveaux

Vous le constatez, les balises <details> et <summary> ajoutent un élément visuel pour savoir si le bloc est ouvert ou fermé. Combiné avec une liste (<ul>), le rendu est assez... moche. Corrigeons cela en gérant les marges et espacements :

Ajout de guides latéraux

L'arborescence créée est déjà sympa visuellement mais ajoutons des guides à gauche :

.tree ul li {
  border-left: 2px solid #ddd;
}
.tree ul li:last-child {
  border-color: transparent;
}

Ajout des lignes horizontales

Poursuivons en ajoutant les lignes horizontales, qui vous permettre de joindre la ligne verticale et l'élément de l'arborescence :

.tree ul li::before {
  content: "";
  display: block;
  position: absolute;
  top: calc(var(--spacing) / -2);
  left: -2px;
  width: calc(var(--spacing) + 2px);
  height: calc(var(--spacing) + 1px);
  border: solid #ddd;
  border-width: 0 0 2px 2px;
}

summary

Nous allons modifier totalement la flèche qui permet de savoir si le bloc est ouvert ou fermé. Pour cela, il faut jouer avec la balise <summary> et son marker :

.tree summary {
  display: block;
  cursor: pointer;
}
.tree summary::marker,
.tree summary::-webkit-details-marker {
  display: none;
}
.tree summary:focus {
  outline: none;
}
.tree summary:focus-visible {
  outline: 1px dotted #000;
}

Gestion du marqueur

Modifions le maker pour avoir quelque chose de plus sympa :

.tree li::after,
.tree summary::before {
  content: "";
  display: block;
  position: absolute;
  top: calc(var(--spacing) / 2 - var(--radius) + 2px);
  left: calc(var(--spacing) - var(--radius) - 2px);
  width: calc(2 * var(--radius));
  height: calc(2 * var(--radius));
  border-radius: 50%;
  background: var(--marker-bg-default);
}

Enfin, les marqueurs vont contenir un "+" si le niveau est refermé et un "-" s'il est ouvert :

.tree summary::before {
  content: "+";
  z-index: 1;
  background: var(--marker-bg-haschild);
  color: #fff;
  line-height: calc(2 * var(--radius));
  text-align: center;
}
.tree details[open] > summary::before {
  content: "−";
}

Code final

Voici le CSS final :

.tree {
  --spacing: 1.5rem;
  --radius: 10px;
  --marker-bg-default: #ddd;
  --marker-bg-haschild: #6b9abb;
  line-height: 2rem;
}
.tree li {
  display: block;
  position: relative;
  padding-left: calc(2 * var(--spacing) - var(--radius) - 2px);
}
.tree ul {
  margin-left: calc(var(--radius) - var(--spacing));
  padding-left: 0;
}

.tree ul li {
  border-left: 2px solid #ddd;
}
.tree ul li:last-child {
  border-color: transparent;
}

.tree ul li::before {
  content: "";
  display: block;
  position: absolute;
  top: calc(var(--spacing) / -2);
  left: -2px;
  width: calc(var(--spacing) + 2px);
  height: calc(var(--spacing) + 1px);
  border: solid #ddd;
  border-width: 0 0 2px 2px;
}

.tree summary {
  display: block;
  cursor: pointer;
}
.tree summary::marker,
.tree summary::-webkit-details-marker {
  display: none;
}
.tree summary:focus {
  outline: none;
}
.tree summary:focus-visible {
  outline: 1px dotted #000;
}

.tree li::after,
.tree summary::before {
  content: "";
  display: block;
  position: absolute;
  top: calc(var(--spacing) / 2 - var(--radius) + 2px);
  left: calc(var(--spacing) - var(--radius) - 2px);
  width: calc(2 * var(--radius));
  height: calc(2 * var(--radius));
  border-radius: 50%;
  background: var(--marker-bg-default);
}

.tree summary::before {
  content: "+";
  z-index: 1;
  background: var(--marker-bg-haschild);
  color: #fff;
  line-height: calc(2 * var(--radius));
  text-align: center;
}
.tree details[open] > summary::before {
  content: "−";
}