WhatsApp

  

Expanding Cards

crea una galería interactiva con 30 líneas de código

En este tutorial construimos un componente de “tarjetas expandibles” con HTML, CSS y JavaScript puro. Es perfecto para destacar categorías, fotos o proyectos en un portafolio. Incluye accesibilidad por teclado y trucos de rendimiento.

¿Qué son las Expanding Cards?

Son un conjunto de paneles en fila (o columna en móvil) que se expanden suavemente cuando el usuario interactúa (clic o teclado). La expansión atrae la atención a un elemento sin recargar la interfaz. Ideal para:

  • Portafolios (UX/UI, foto, 3D, arquitectura)

  • Secciones de “categorías destacadas” en blogs o tiendas

  • Landing pages minimalistas con hero gallery

Vista previa del resultado

Cada panel muestra una imagen a pantalla ancha, un título y un gradiente para mejorar la legibilidad. Al activar una tarjeta, su ancho aumenta y el resto se contrae.

Sugerencia: usa 4–6 paneles para un balance visual óptimo.

Estructura del proyecto

Crea una carpeta con tres archivos:

  • index.html

  • style.css

  • script.js

1) HTML básico

<!DOCTYPE html>
<html lang="es">
<head>
  <meta charset="utf-8" />
  <meta name="viewport" content="width=device-width, initial-scale=1" />
  <title>Expanding Cards</title>
  <link rel="stylesheet" href="style.css" />
</head>
<body>
  <main class="container" role="list" aria-label="Galería de tarjetas">
    <button class="panel active" aria-pressed="true" role="listitem"
            style="--bg:url('https://images.unsplash.com/photo-1507525428034-b723cf961d3e?q=80&w=1600&auto=format&fit=crop');">
      <h3>Playa</h3>
    </button>
    <button class="panel" aria-pressed="false" role="listitem"
            style="--bg:url('https://images.unsplash.com/photo-1491553895911-0055eca6402d?q=80&w=1600&auto=format&fit=crop');">
      <h3>Montaña</h3>
    </button>
    <button class="panel" aria-pressed="false" role="listitem"
            style="--bg:url('https://images.unsplash.com/photo-1526778548025-fa2f459cd5c1?q=80&w=1600&auto=format&fit=crop');">
      <h3>Ciudad</h3>
    </button>
    <button class="panel" aria-pressed="false" role="listitem"
            style="--bg:url('https://images.unsplash.com/photo-1519681393784-d120267933ba?q=80&w=1600&auto=format&fit=crop');">
      <h3>Bosque</h3>
    </button>
    <button class="panel" aria-pressed="false" role="listitem"
            style="--bg:url('https://images.unsplash.com/photo-1493246318656-5bfd4cfb29ab?q=80&w=1600&auto=format&fit=crop');">
      <h3>Lago</h3>
    </button>
  </main>
  <script src="script.js"></script>
</body>
</html>


2) CSS (estilo y transición)

 

* { box-sizing: border-box; }
:root { --radius: 24px; }
body {
  margin: 0;
  font-family: system-ui, -apple-system, Segoe UI, Roboto, Arial, sans-serif;
  background: #111;
  color: #fff;
  min-height: 100svh;
  display: grid;
  place-items: center;
}
.container {
  display: flex;
  gap: 16px;
  width: min(1200px, 92vw);
  height: clamp(260px, 50vw, 520px);
  padding: 8px;
}
.panel {
  flex: 1;
  border: 0;
  border-radius: var(--radius);
  position: relative;
  cursor: pointer;
  outline-offset: 4px;
  display: flex;
  align-items: flex-end;
  justify-content: flex-start;
  padding: 16px;
  text-align: left;
  transition: flex 0.6s ease;
  background: #333 center/cover no-repeat;
  background-image: var(--bg);
}
.panel h3 {
  margin: 0;
  font-size: clamp(1rem, 2.5vw, 1.5rem);
  text-shadow: 0 2px 10px rgba(0,0,0,.6);
}
/* activa: ocupa más espacio */
.panel.active { flex: 5; }
/* gradiente para mejorar contraste del texto */
.panel::after{
  content: "";
  position: absolute;
  inset: 0;
  border-radius: inherit;
  background: linear-gradient(to top, rgba(0,0,0,.55), rgba(0,0,0,0) 60%);
  pointer-events: none;
}
/* móvil: apila en columna */
@media (max-width: 540px) {
  .container { flex-direction: column; height: auto; }
  .panel { min-height: 38svh; }
}


3) JavaScript (interacción + accesibilidad)

 

const panels = Array.from(document.querySelectorAll(".panel"));
function activate(panel) {
  panels.forEach(p => {
    const isActive = p === panel;
    p.classList.toggle("active", isActive);
    p.setAttribute("aria-pressed", isActive ? "true" : "false");
  });
}
panels.forEach(p => {
  p.addEventListener("click", () => activate(p));
  // soporte por teclado
  p.addEventListener("keydown", (e) => {
    if (e.key === "Enter" || e.key === " ") {
      e.preventDefault();
      activate(p);
    }
  });
});


Cómo correrlo: guarda los 3 archivos y abre index.html en tu navegador.

Buenas prácticas de accesibilidad (A11y)

  • Usa button (no div) para que sean focusables y anuncien interacción.

  • Añade role="list" al contenedor y role="listitem" a cada tarjeta para una estructura semántica.

  • Sin ratón, el usuario puede navegar con Tab y activar con Enter o Espacio.

  • Asegura contraste suficiente: el gradiente oscuro ayuda a que el texto se lea sobre la imagen.

Rendimiento y calidad de imagen

  • Prefiere imágenes optimizadas (formato AVIF/WebP si tu flujo lo permite).

  • Usa tamaños coherentes: 1600px de ancho suele ser suficiente para una imagen principal.

  • Si alojas las imágenes tú, añade loading="lazy" en <img> (si las usas) o sirve recursos con HTTP caching.


Finalmente debe quedar algo así 



 


Expanding Cards
Paris Minero 16 octubre, 2025
Compartir
Iniciar sesión dejar un comentario

  
Cómo crear un demonio (daemon) en Linux — guía práctica y amigable