Cambio de pestañas
Componente de pestañas con navegación por teclas de flecha y animación de cambio de contenido.
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>Cambio de pestañas</title>
<style>
* { box-sizing: border-box; }
body {
min-height: 100vh;
margin: 0;
display: grid;
place-items: center;
padding: 22px;
color: #eff4ff;
font-family: "Futura", "Microsoft YaHei", sans-serif;
background: #101119;
}
.window { width: min(820px, 100%); overflow: hidden; border: 1px solid #343747; border-radius: 22px; background: #181a24; box-shadow: 0 30px 80px rgba(0,0,0,.42); }
.titlebar { display: flex; align-items: center; gap: 8px; padding: 16px 20px; border-bottom: 1px solid #343747; }
.dot { width: 11px; height: 11px; border-radius: 50%; background: #ff6b67; }
.dot:nth-child(2) { background: #f7c95c; }
.dot:nth-child(3) { background: #61d88c; }
.titlebar strong { margin-left: auto; color: #7d829a; font-size: 11px; letter-spacing: .16em; }
.layout { display: grid; grid-template-columns: 210px 1fr; min-height: 440px; }
[role="tablist"] { padding: 26px 14px; border-right: 1px solid #343747; background: #141620; }
[role="tab"] { width: 100%; display: flex; gap: 12px; align-items: center; padding: 13px 14px; border: 0; border-radius: 12px; color: #9197ad; text-align: left; font: inherit; font-weight: 700; background: transparent; cursor: pointer; }
[role="tab"] + [role="tab"] { margin-top: 8px; }
[role="tab"][aria-selected="true"] { color: #fff; background: #292d3e; box-shadow: inset 3px 0 #67e8b8; }
[role="tab"]:focus-visible { outline: 2px solid #67e8b8; outline-offset: 2px; }
.tab-icon { display: grid; width: 30px; height: 30px; place-items: center; border: 1px solid #41465c; border-radius: 9px; }
.content { padding: clamp(28px, 6vw, 54px); }
[role="tabpanel"] { animation: reveal .32s ease both; }
[role="tabpanel"][hidden] { display: none; }
.label { color: #67e8b8; font-size: 12px; font-weight: 800; letter-spacing: .16em; }
h1 { margin: 14px 0 16px; font: 700 clamp(34px, 6vw, 54px)/1.02 Georgia, "STSong", serif; }
p { color: #a5aac0; line-height: 1.75; }
.metric-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 12px; margin-top: 28px; }
.metric { padding: 18px; border: 1px solid #343747; border-radius: 14px; background: #1e2130; }
.metric strong { display: block; margin-bottom: 5px; color: #fff; font-size: 24px; }
.metric span { color: #7f859d; font-size: 12px; }
@keyframes reveal { from { opacity: 0; transform: translateY(10px); } }
@media (max-width: 650px) { .layout { grid-template-columns: 1fr; } [role="tablist"] { display: flex; overflow-x: auto; padding: 12px; border-right: 0; border-bottom: 1px solid #343747; } [role="tab"] { min-width: max-content; } [role="tab"] + [role="tab"] { margin: 0 0 0 6px; } .metric-grid { grid-template-columns: 1fr; } }
@media (prefers-reduced-motion: reduce) { [role="tabpanel"] { animation: none; } }
</style>
</head>
<body>
<main class="window">
<div class="titlebar"><i class="dot"></i><i class="dot"></i><i class="dot"></i><strong>PROJECT ATLAS</strong></div>
<div class="layout">
<div role="tablist" aria-label="Panel del proyecto">
<button id="tab-overview" role="tab" aria-selected="true" aria-controls="panel-overview"><span class="tab-icon">⌁</span>Overview</button>
<button id="tab-progress" role="tab" aria-selected="false" aria-controls="panel-progress" tabindex="-1"><span class="tab-icon">↗</span>Progreso</button>
<button id="tab-team" role="tab" aria-selected="false" aria-controls="panel-team" tabindex="-1"><span class="tab-icon">◎</span>Equipo</button>
</div>
<section class="content">
<div id="panel-overview" role="tabpanel" aria-labelledby="tab-overview"><span class="label">01 / OVERVIEW</span><h1>Todo va según lo previsto</h1><p>Atlas es un plan de lanzamiento de producto para un equipo pequeño. Nos estamos concentrando en completar la experiencia principal y preparándonos para la siguiente ronda de pruebas de usuario.</p><div class="metric-grid"><div class="metric"><strong>82%</strong><span>Grado de finalización general</span></div><div class="metric"><strong>14</strong><span>Tareas de esta semana</span></div><div class="metric"><strong>6</strong><span>Miembros colaboradores</span></div></div></div>
<div id="panel-progress" role="tabpanel" aria-labelledby="tab-progress" hidden><span class="label">02 / PROGRESS</span><h1>A 12 días del lanzamiento</h1><p>El sistema de diseño y los flujos principales ya están completos. Actualmente se están realizando pruebas de regresión en varios dispositivos y resolviendo los últimos problemas de accesibilidad.</p><div class="metric-grid"><div class="metric"><strong>31</strong><span>Completado</span></div><div class="metric"><strong>7</strong><span>En curso</span></div><div class="metric"><strong>3</strong><span>Pendiente de confirmar</span></div></div></div>
<div id="panel-team" role="tabpanel" aria-labelledby="tab-team" hidden><span class="label">03 / TEAM</span><h1>Seis personas, una obra</h1><p>Producto, diseño y desarrollo mantienen conjuntamente la misma lista de lanzamiento; cada decisión se puede rastrear y cada avance es claramente visible.</p><div class="metric-grid"><div class="metric"><strong>2</strong><span>Diseñador</span></div><div class="metric"><strong>3</strong><span>Desarrollador</span></div><div class="metric"><strong>1</strong><span>Gerente de producto</span></div></div></div>
</section>
</div>
</main>
<script>
const tabs = [...document.querySelectorAll('[role="tab"]')];
function selectTab(tab) {
tabs.forEach(item => {
const selected = item === tab;
item.setAttribute('aria-selected', String(selected));
item.tabIndex = selected ? 0 : -1;
document.getElementById(item.getAttribute('aria-controls')).hidden = !selected;
});
tab.focus();
}
tabs.forEach((tab, index) => {
tab.addEventListener('click', () => selectTab(tab));
tab.addEventListener('keydown', event => {
const direction = event.key === 'ArrowRight' || event.key === 'ArrowDown' ? 1 : event.key === 'ArrowLeft' || event.key === 'ArrowUp' ? -1 : 0;
if (direction) { event.preventDefault(); selectTab(tabs[(index + direction + tabs.length) % tabs.length]); }
});
});
</script>
</body>
</html>