Composants / Slots

Système de composants réutilisables en pur JS. Chaque composant est une fonction qui reçoit des slots (contenus injectables) et retourne du HTML.

Note : Le dossier components/ est propre à chaque projet (pas un symlink framework). Les composants ne sont pas écrasés lors des mises à jour du framework. Le dossier est créé vide à l'initialisation via setup.sh. Des exemples de composants (Header, Footer, Card) sont disponibles dans .framework/snippets/components/ et via les snippets VSCode (bly-comp-header, bly-comp-footer, bly-comp-card).

Principe

Un composant est défini dans un fichier JS via registerComponent(). Il est ensuite utilisé dans le HTML via l'attribut data-component. Les contenus sont injectés via des slots.

Un composant est un bloc HTML réutilisable (comme un modèle de lettre). Les slots sont les zones où vous insérez votre propre contenu (comme les champs à remplir dans le modèle).

// 1. Définir le composant (components/mon-composant.js)
registerComponent('monComposant', function (slots) {
  return `
    <div class="mon-composant">
      <h3>${slots.title || 'Titre par défaut'}</h3>
      <p>${slots.text || ''}</p>
    </div>
  `;
});

// 2. Utiliser dans le HTML
// <div data-component="monComposant">
//   <template data-slot="title">Mon titre</template>
//   <template data-slot="text">Du contenu <strong>HTML</strong></template>
// </div>

Injection de slots

Deux méthodes pour passer du contenu à un composant :

Méthode 1 : <template data-slot>

Pour du contenu riche (HTML, liens, boutons, images). Le contenu HTML du <template> est injecté tel quel.

<div data-component="card">
  <template data-slot="title">Mon titre</template>
  <template data-slot="text">Du <strong>HTML</strong> riche</template>
  <template data-slot="footer">
    <a href="#" class="btn btn--primary">Action</a>
  </template>
</div>

Méthode 2 : attributs data-*

Pour des valeurs simples (texte, URLs). Les tirets sont convertis en camelCase. Tous les attributs data-* (sauf data-component) sont disponibles dans slots.

<!-- data-site-name → slots.siteName -->
<!-- data-logo-src  → slots.logoSrc  -->
<div data-component="header"
     data-site-name="MonSite"
     data-logo-src="logo.png"
     data-logo-link="/index.html">
</div>
Attribut HTMLClé dans slots
data-site-nameslots.siteName
data-logo-srcslots.logoSrc
data-image-altslots.imageAlt
data-copyrightslots.copyright

La syntaxe data-slot-* est également supportée comme alias.

API JavaScript

FonctionDescription
registerComponent(name, fn)Enregistre un composant. fn(slots) reçoit un objet et retourne du HTML.
renderComponents(root)Rend les [data-component] dans root (défaut : document.body). Appelé auto au DOMContentLoaded. Ajoute aussi id="main-content" au premier <main> sans ID.
escapeSlotHtml(str)Échappe une chaîne pour un usage sûr dans le HTML d'un composant (prévention XSS).

Vous pouvez appeler renderComponents(el) manuellement après avoir injecté du HTML dynamique contenant des data-component.

Composants inclus

Fichier : components/header.js

Le header est configuré entièrement par attributs et slots :

SlotMéthodeDescription
siteNameAttributNom du site (texte logo si pas d'image). Défaut : Logo
logoSrcAttributURL de l'image logo (vide = texte siteName)
logoLinkAttributLien du logo. Défaut : /
navTemplateLiens de navigation (HTML)
ctaTemplateBouton d'action (optionnel, intégré dans header__actions)
searchTemplateActive le bouton de recherche (déclarer vide)

Layout du header

La navigation est alignée à gauche après le logo (margin-right: auto). Les actions (toggle thème, recherche) et le CTA sont alignés à droite. Le CTA est intégré dans le groupe header__actions (pas un élément séparé).

<!-- Header minimal -->
<div data-component="header"
     data-site-name="MonSite"
     data-logo-link="/index.html">
  <template data-slot="nav">
    <a href="/index.html">Accueil</a>
    <a href="/contact.html">Contact</a>
  </template>
</div>

<!-- Header complet (logo image + CTA + recherche) -->
<div data-component="header"
     data-site-name="MonSite"
     data-logo-src="/assets/logo.svg"
     data-logo-link="/index.html">
  <template data-slot="nav">
    <a href="/index.html">Accueil</a>
    <a href="/docs/index.html">Documentation</a>
  </template>
  <template data-slot="cta">
    <button class="btn btn--primary">Contact</button>
  </template>
  <template data-slot="search"></template>
</div>

Accessibilité

Le header intègre plusieurs fonctionnalités d'accessibilité conformes aux WCAG :

  • Skip to content (WCAG 2.4.1) : un lien .skip-to-content est généré automatiquement. Il est invisible par défaut et apparaît au focus clavier, permettant de sauter directement au contenu principal (#main-content).
  • Auto-détection du <main> : le framework ajoute automatiquement id="main-content" au premier élément <main> de la page s'il n'a pas déjà un ID. Vous n'avez plus besoin de l'ajouter manuellement.
  • Bouton recherche : possède un aria-label="Rechercher" pour rester accessible sur mobile, où le label texte et le raccourci clavier sont masqués visuellement (display: none).
  • Toggle thème : possède un aria-label="Basculer le thème".

Responsive mobile (≤ 991px)

Le header inclut automatiquement un burger menu visible en dessous de 991px. Au clic, la navigation s'ouvre en off-canvas (panneau latéral droit) avec overlay.

  • Le burger se transforme en × à l'ouverture (animation CSS)
  • Fermeture par : bouton burger, overlay, touche Escape, clic sur un lien
  • Le scroll de la page est verrouillé quand le menu est ouvert
  • Le bouton CTA est masqué sur mobile (intégrez-le dans le slot nav si besoin)
  • Le label et le raccourci du bouton recherche sont masqués (icône seule, accessible via aria-label)

Aucune configuration nécessaire — le responsive est intégré au composant.

Fichier : components/footer.js

Le footer est configuré par attributs et slots :

SlotMéthodeDescription
copyrightAttributTexte copyright. L'année est mise à jour automatiquement. Défaut : &copy; YYYY
contentTemplateContenu du footer (liens, nav, etc.)

Card

Fichier : components/card.js

SlotTypeDescription
imageURLImage de la card (optionnel)
imageAltTexteAlt de l'image
titleTexteTitre de la card
textTexteContenu texte
footerHTMLPied de card (boutons, liens, etc.)
<div data-component="card">
  <!-- Le slot image est optionnel -->
  <template data-slot="image">photo.jpg</template>
  <template data-slot="title">Titre</template>
  <template data-slot="text">Description de la card.</template>
  <template data-slot="footer">
    <a href="#" class="btn btn--primary">En savoir plus</a>
  </template>
</div>

Créer un composant custom

Créez un fichier dans components/, enregistrez-le, puis incluez le script dans vos pages.

// components/testimonial.js
registerComponent('testimonial', function (slots) {
  return `
    <blockquote class="testimonial">
      <p class="testimonial__text">"${slots.quote || ''}"</p>
      <footer class="testimonial__author">
        ${slots.avatar ? `<img src="${slots.avatar}" alt="">` : ''}
        <cite>${slots.name || 'Anonyme'}</cite>
      </footer>
    </blockquote>
  `;
});
<!-- Dans le HTML -->
<script src="components/testimonial.js"></script>

<div data-component="testimonial">
  <template data-slot="quote">Un produit incroyable !</template>
  <template data-slot="name">Marie Dupont</template>
  <template data-slot="avatar">img/marie.jpg</template>
</div>

Structure de page

Le pattern standard pour structurer le contenu d’une page est :

<main>
  <section class="section">
    <div class="container">
      <!-- Contenu de la section -->
    </div>
  </section>
</main>
  • <main> est un simple wrapper sémantique — pas un conteneur (pas de padding ni max-width)
  • .section fournit le padding vertical (padding-block: var(--space-20))
  • .container fournit le padding horizontal + la largeur maximale (--container-max)

Variantes de section :

ClassePadding vertical
.sectionvar(--space-20) (5rem / 80px)
.section--smvar(--space-12) (3rem / 48px)
.section--lgvar(--space-24) (6rem / 96px)

Bonnes pratiques

  • Toujours prévoir des valeurs par défaut : slots.title || 'Défaut'
  • Rendre les slots optionnels : slots.footer ? '...' : ''
  • Nommer les composants en camelCase : registerComponent('heroSection', ...)
  • Convention BEM pour les classes CSS du composant
  • Un composant peut contenir d'autres data-component (rendu récursif automatique)
  • Charger core/js/components.js avant les scripts de composants

Problèmes courants

  • Le composant ne s'affiche pas : vérifiez que components.js est chargé avant le script du composant, et que l'attribut data-component est correct.
  • Un slot est vide : vérifiez l'orthographe du data-slot. Les noms sont convertis en camelCase : data-logo-src devient slots.logoSrc.
  • Les animations ne marchent pas dans un composant : les composants sont rendus au DOMContentLoaded. Les animations et éléments interactifs sont ré-initialisés automatiquement après le rendu.

Voir aussi