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.
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 HTML | Clé dans slots |
|---|---|
data-site-name | slots.siteName |
data-logo-src | slots.logoSrc |
data-image-alt | slots.imageAlt |
data-copyright | slots.copyright |
La syntaxe data-slot-* est également supportée comme alias.
API JavaScript
| Fonction | Description |
|---|---|
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
Header
Fichier : components/header.js
Le header est configuré entièrement par attributs et slots :
| Slot | Méthode | Description |
|---|---|---|
siteName | Attribut | Nom du site (texte logo si pas d'image). Défaut : Logo |
logoSrc | Attribut | URL de l'image logo (vide = texte siteName) |
logoLink | Attribut | Lien du logo. Défaut : / |
nav | Template | Liens de navigation (HTML) |
cta | Template | Bouton d'action (optionnel, intégré dans header__actions) |
search | Template | Active 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-contentest 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 automatiquementid="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
navsi 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.
Footer
Fichier : components/footer.js
Le footer est configuré par attributs et slots :
| Slot | Méthode | Description |
|---|---|---|
copyright | Attribut | Texte copyright. L'année est mise à jour automatiquement. Défaut : © YYYY |
content | Template | Contenu du footer (liens, nav, etc.) |
Card
Fichier : components/card.js
| Slot | Type | Description |
|---|---|---|
image | URL | Image de la card (optionnel) |
imageAlt | Texte | Alt de l'image |
title | Texte | Titre de la card |
text | Texte | Contenu texte |
footer | HTML | Pied 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).sectionfournit le padding vertical (padding-block: var(--space-20)).containerfournit le padding horizontal + la largeur maximale (--container-max)
Variantes de section :
| Classe | Padding vertical |
|---|---|
.section | var(--space-20) (5rem / 80px) |
.section--sm | var(--space-12) (3rem / 48px) |
.section--lg | var(--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.jsavant les scripts de composants
Problèmes courants
- Le composant ne s'affiche pas : vérifiez que
components.jsest chargé avant le script du composant, et que l'attributdata-componentest correct. - Un slot est vide : vérifiez l'orthographe du
data-slot. Les noms sont convertis en camelCase :data-logo-srcdevientslots.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.