Bloc HTML/CSS/JS plug-and-play : typographie système, chevron animé, transitions douces, ARIA complète, navigation clavier (↑ ↓ Home End), ouverture exclusive
<!-- Accordéon FAQ minimal — Plug & Play -->
<section class="qa" data-accordion="single" aria-label="FAQ">
<!-- ÉLÉMENTS À MODIFIER : duplique .qa-item pour ajouter des questions -->
<div class="qa-item">
<button class="qa-toggle" aria-expanded="false" aria-controls="qa1" id="q1">
<span class="qa-q">Puis-je changer les couleurs et la police ?</span>
<svg class="qa-ico" viewBox="0 0 24 24" aria-hidden="true">
<path d="M6 9l6 6 6-6" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
</button>
...<!-- Accordéon FAQ minimal — Plug & Play -->
<section class="qa" data-accordion="single" aria-label="FAQ">
<!-- ÉLÉMENTS À MODIFIER : duplique .qa-item pour ajouter des questions -->
<div class="qa-item">
<button class="qa-toggle" aria-expanded="false" aria-controls="qa1" id="q1">
<span class="qa-q">Puis-je changer les couleurs et la police ?</span>
<svg class="qa-ico" viewBox="0 0 24 24" aria-hidden="true">
<path d="M6 9l6 6 6-6" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
</button>
<div class="qa-panel" id="qa1" role="region" aria-labelledby="q1">
<p>Oui. Modifiez les variables CSS dans <code>.qa</code> (ex. <code>--accent</code>, <code>--text</code>) et la stack de police.</p>
</div>
</div>
<div class="qa-item">
<button class="qa-toggle" aria-expanded="false" aria-controls="qa2" id="q2">
<span class="qa-q">Le composant est-il accessible au clavier ?</span>
<svg class="qa-ico" viewBox="0 0 24 24" aria-hidden="true">
<path d="M6 9l6 6 6-6" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
</button>
<div class="qa-panel" id="qa2" role="region" aria-labelledby="q2">
<p>Oui. Utilisez <kbd>Entrée</kbd>/<kbd>Espace</kbd> pour ouvrir/fermer, <kbd>↑</kbd>/<kbd>↓</kbd> pour naviguer, <kbd>Home</kbd>/<kbd>End</kbd> pour aller au premier/dernier.</p>
</div>
</div>
<div class="qa-item">
<button class="qa-toggle" aria-expanded="false" aria-controls="qa3" id="q3">
<span class="qa-q">Peut-on n’avoir qu’un panneau ouvert à la fois ?</span>
<svg class="qa-ico" viewBox="0 0 24 24" aria-hidden="true">
<path d="M6 9l6 6 6-6" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
</button>
<div class="qa-panel" id="qa3" role="region" aria-labelledby="q3">
<p>Par défaut oui (<code>data-accordion="single"</code>). Supprimez cet attribut pour autoriser plusieurs panneaux ouverts.</p>
</div>
</div>
</section>
<style>
.qa{
--accent: #0a84ff;
--text: #0b0b0c;
--muted: #6b7280;
--line: rgba(0,0,0,.08);
--bg-hov: #f8fafc;
font-family: -apple-system, BlinkMacSystemFont, "SF Pro Text", "SF Pro Display",
"Helvetica Neue", Arial, system-ui, sans-serif;
width: min(720px, 92vw);
margin: 24px auto;
color: var(--text);
background:#fff;
border-radius: 16px;
border: 1px solid var(--line);
}
.qa-item + .qa-item { border-top: 1px solid var(--line); }
.qa-toggle{
-webkit-tap-highlight-color: transparent;
width: 100%; background: transparent; border: 0; text-align: left;
display:flex; align-items:center; justify-content:space-between; gap:16px;
padding: 18px 20px; cursor: pointer; font-size: 17px; line-height: 1.3;
}
.qa-toggle:hover{ background: var(--bg-hov); }
.qa-toggle:focus-visible{
outline: 2px solid color-mix(in oklab, var(--accent) 70%, white);
outline-offset: 2px; border-radius: 10px;
}
.qa-q{ font-weight: 600; letter-spacing:.2px; }
.qa-ico{
flex: 0 0 auto; width: 20px; height: 20px; color: #222;
transition: transform .24s cubic-bezier(.2,.8,.2,1);
}
.qa-toggle[aria-expanded="true"] .qa-ico{ transform: rotate(180deg); }
.qa-panel{
height: 0; overflow: hidden;
padding: 0 20px;
transition: height .24s cubic-bezier(.2,.8,.2,1), padding .24s ease;
}
.qa-panel > *{ margin: 0 0 14px 0; color: var(--muted); }
.qa-panel code{ background:#f3f4f6; padding:2px 6px; border-radius:6px; }
@media (prefers-reduced-motion: reduce){
.qa-ico, .qa-panel{ transition: none !important; }
}
</style>
<script>
(function(){
const acc = document.querySelector('.qa');
const items = [...acc.querySelectorAll('.qa-item')];
const toggles = items.map(i => i.querySelector('.qa-toggle'));
const panels = items.map(i => i.querySelector('.qa-panel'));
const single = acc.getAttribute('data-accordion') === 'single';
function setExpanded(btn, expand){
const panel = document.getElementById(btn.getAttribute('aria-controls'));
btn.setAttribute('aria-expanded', expand);
// animation height auto
if (expand){
panel.style.display = 'block';
const h = panel.scrollHeight;
panel.style.height = '0px'; panel.style.paddingTop = '0px'; panel.style.paddingBottom = '0px';
requestAnimationFrame(()=>{
panel.style.height = h + 'px';
panel.style.paddingTop = '12px';
panel.style.paddingBottom = '16px';
});
panel.addEventListener('transitionend', function te(e){
if(e.propertyName === 'height'){ panel.style.height = 'auto'; }
panel.removeEventListener('transitionend', te);
});
} else {
const h = panel.scrollHeight;
panel.style.height = h + 'px';
requestAnimationFrame(()=>{
panel.style.height = '0px';
panel.style.paddingTop = '0px';
panel.style.paddingBottom = '0px';
});
}
}
function closeAll(exceptBtn){
toggles.forEach(b=>{
if(b !== exceptBtn && b.getAttribute('aria-expanded') === 'true'){
setExpanded(b, false);
}
});
}
toggles.forEach(btn=>{
btn.addEventListener('click', ()=>{
const expanded = btn.getAttribute('aria-expanded') === 'true';
if(single) closeAll(btn);
setExpanded(btn, !expanded);
});
// Navigation clavier (↑ ↓ Home End)
btn.addEventListener('keydown', (e)=>{
const i = toggles.indexOf(btn);
if(e.key === 'ArrowDown'){ e.preventDefault(); toggles[(i+1)%toggles.length].focus(); }
else if(e.key === 'ArrowUp'){ e.preventDefault(); toggles[(i-1+toggles.length)%toggles.length].focus(); }
else if(e.key === 'Home'){ e.preventDefault(); toggles[0].focus(); }
else if(e.key === 'End'){ e.preventDefault(); toggles[toggles.length-1].focus(); }
else if(e.key === 'Enter' || e.key === ' '){ e.preventDefault(); btn.click(); }
});
});
// État initial : tout fermé (assure tailles correctes)
panels.forEach(p=>{ p.style.height = '0px'; p.style.paddingTop = '0px'; p.style.paddingBottom = '0px'; });
// Correction responsive quand la fenêtre change
let rAF;
window.addEventListener('resize', ()=>{
cancelAnimationFrame(rAF);
rAF = requestAnimationFrame(()=>{
toggles.forEach(btn=>{
if(btn.getAttribute('aria-expanded') === 'true'){
const panel = document.getElementById(btn.getAttribute('aria-controls'));
panel.style.height = 'auto'; const h = panel.scrollHeight;
panel.style.height = h + 'px';
}
});
});
});
})();
</script>