Panel acordeón
Componente de acordeón de preguntas frecuentes con operación mediante teclado y despliegue suave.
Tras activar JavaScript, puedes editar, ejecutar y guardar este ejemplo. El código fuente fijado es el siguiente:
<!doctype html>
<html lang="es" dir="ltr">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Panel acordeón</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>Preguntas frecuentes</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>¿Cómo elegir la ubicación de las plantas de interior?</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>Primero observa cómo cambia la luz natural a lo largo del día. La mayoría de las plantas de follaje prefieren una luz brillante pero indirecta; como punto de partida cómodo, suele funcionar situarse a uno o dos metros de una ventana orientada al sur.</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>¿Cada cuánto conviene regar?</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>No te limites a mirar el calendario. Introduce un dedo en la tierra unos dos centímetros; riega a fondo cuando la capa superficial ya esté seca. En invierno la evaporación es más lenta, así que conviene alargar el intervalo.</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>¿Qué hacer si los bordes de las hojas se vuelven amarillos?</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>Primero comprueba si hay encharcamiento, aire demasiado seco o exposición repentina al sol. Corta las hojas muy dañadas y mantén una buena ventilación; por lo general, en una o dos semanas verás que las hojas nuevas recuperan la salud.</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>¿Cuándo es necesario cambiar la maceta?</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>Si las raíces salen por los orificios de drenaje, el agua se escurre enseguida o el crecimiento se detiene claramente, en primavera puedes trasplantarla a una maceta nueva de dos a cuatro centímetros más de diámetro.</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>