Panneau repliable accordéon
Composant FAQ à accordéon avec prise en charge du clavier et déploiement fluide.
Après activation de JavaScript, ce cas peut être modifié, exécuté et enregistré. Code source fixe ci-dessous :
<!doctype html>
<html lang="fr" dir="ltr">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Panneau repliable accordéon</title>
<style>
* { box-sizing: border-box; }
body {
min-height: 100vh;
margin: 0;
padding: clamp(24px, 7vw, 76px) 20px;
color: #18302a;
font-family: "Gill Sans", "Microsoft YaHei", sans-serif;
background: linear-gradient(135deg, #f4f0d8 0 55%, #d7ead5 55%);
}
main { width: min(820px, 100%); margin: auto; }
header { display: grid; grid-template-columns: 1fr auto; gap: 24px; align-items: end; margin-bottom: 34px; }
.kicker { color: #d5523f; font-size: 12px; font-weight: 800; letter-spacing: .2em; }
h1 { margin: 8px 0 0; font: 800 clamp(36px, 8vw, 66px)/1 "STKaiti", "KaiTi", serif; }
.leaf { width: 80px; height: 80px; border-radius: 80% 10% 80% 10%; background: #2e8169; transform: rotate(10deg); box-shadow: inset 14px 14px rgba(255,255,255,.16); }
.accordion { border-top: 2px solid #18302a; }
.item { border-bottom: 1px solid rgba(24, 48, 42, .35); }
.trigger {
width: 100%;
display: grid;
grid-template-columns: 48px 1fr 34px;
gap: 12px;
align-items: center;
padding: 24px 4px;
border: 0;
color: inherit;
text-align: left;
font: inherit;
font-size: 19px;
font-weight: 750;
background: transparent;
cursor: pointer;
}
.index { color: #d5523f; font: 700 13px/1 Georgia, serif; }
.plus { position: relative; width: 28px; height: 28px; border: 1px solid currentColor; border-radius: 50%; transition: transform .25s ease; }
.plus::before, .plus::after { content: ""; position: absolute; left: 7px; right: 7px; top: 13px; height: 1px; background: currentColor; }
.plus::after { transform: rotate(90deg); transition: opacity .2s ease; }
.trigger[aria-expanded="true"] .plus { transform: rotate(90deg); color: #d5523f; }
.trigger[aria-expanded="true"] .plus::after { opacity: 0; }
.trigger:focus-visible { outline: 3px solid #d5523f; outline-offset: -3px; }
.panel { display: grid; grid-template-rows: 0fr; transition: grid-template-rows .3s ease; }
.panel.is-open { grid-template-rows: 1fr; }
.panel-content { overflow: hidden; }
.panel p { max-width: 650px; margin: 0; padding: 0 48px 24px 64px; color: #4d625c; line-height: 1.8; }
@media (max-width: 520px) { header { grid-template-columns: 1fr; } .leaf { display: none; } .panel p { padding-left: 4px; } }
@media (prefers-reduced-motion: reduce) { .panel, .plus { transition: none; } }
</style>
</head>
<body>
<main>
<header>
<div><span class="kicker">GARDEN NOTES</span><h1>Foire aux questions</h1></div>
<div class="leaf" aria-hidden="true"></div>
</header>
<section class="accordion">
<div class="item">
<button id="trigger-1" class="trigger" type="button" aria-expanded="true" aria-controls="panel-1"><span class="index">01</span><span>Comment choisir l’emplacement des plantes d’intérieur ?</span><span class="plus" aria-hidden="true"></span></button>
<div id="panel-1" class="panel is-open" role="region" aria-labelledby="trigger-1" aria-hidden="false"><div class="panel-content"><p>Observez d’abord l’évolution de la lumière naturelle au cours de la journée. La plupart des plantes à feuillage apprécient une lumière vive mais indirecte, et un emplacement à un ou deux mètres d’une fenêtre orientée au sud constitue souvent un point de départ confortable.</p></div></div>
</div>
<div class="item">
<button id="trigger-2" class="trigger" type="button" aria-expanded="false" aria-controls="panel-2"><span class="index">02</span><span>À quelle fréquence faut-il arroser ?</span><span class="plus" aria-hidden="true"></span></button>
<div id="panel-2" class="panel" role="region" aria-labelledby="trigger-2" aria-hidden="true"><div class="panel-content"><p>Ne vous fiez pas uniquement au calendrier. Enfoncez un doigt dans la terre sur environ deux centimètres ; arrosez abondamment seulement lorsque la surface est déjà sèche ; en hiver, l’évaporation est plus lente, il faut donc espacer davantage les arrosages.</p></div></div>
</div>
<div class="item">
<button id="trigger-3" class="trigger" type="button" aria-expanded="false" aria-controls="panel-3"><span class="index">03</span><span>Que faire lorsque le bord des feuilles jaunit ?</span><span class="plus" aria-hidden="true"></span></button>
<div id="panel-3" class="panel" role="region" aria-labelledby="trigger-3" aria-hidden="true"><div class="panel-content"><p>Commencez par vérifier l’eau stagnante, l’air trop sec et l’exposition soudaine au soleil. Coupez les feuilles gravement endommagées et maintenez une bonne aération ; en général, de nouvelles feuilles en meilleure santé apparaissent en une à deux semaines.</p></div></div>
</div>
<div class="item">
<button id="trigger-4" class="trigger" type="button" aria-expanded="false" aria-controls="panel-4"><span class="index">04</span><span>Quand faut-il remplacer le pot de fleurs ?</span><span class="plus" aria-hidden="true"></span></button>
<div id="panel-4" class="panel" role="region" aria-labelledby="trigger-4" aria-hidden="true"><div class="panel-content"><p>Lorsque les racines sortent par les trous de drainage, que l’eau s’écoule rapidement après l’arrosage, ou que la croissance ralentit nettement, vous pouvez rempoter au printemps dans un nouveau pot de 2 à 4 centimètres de diamètre supérieur.</p></div></div>
</div>
</section>
</main>
<script>
const triggers = [...document.querySelectorAll('.trigger')];
triggers.forEach((trigger, index) => {
trigger.addEventListener('click', () => {
const willOpen = trigger.getAttribute('aria-expanded') !== 'true';
triggers.forEach(item => {
item.setAttribute('aria-expanded', 'false');
const panel = document.getElementById(item.getAttribute('aria-controls'));
panel.classList.remove('is-open');
panel.setAttribute('aria-hidden', 'true');
});
if (willOpen) {
trigger.setAttribute('aria-expanded', 'true');
const panel = document.getElementById(trigger.getAttribute('aria-controls'));
panel.classList.add('is-open');
panel.setAttribute('aria-hidden', 'false');
}
});
trigger.addEventListener('keydown', event => {
const direction = event.key === 'ArrowDown' ? 1 : event.key === 'ArrowUp' ? -1 : 0;
if (direction) {
event.preventDefault();
triggers[(index + direction + triggers.length) % triggers.length].focus();
}
});
});
</script>
</body>
</html>