WhatsApp
Ir al contenido

  

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 de octubre de 2025
Compartir
Iniciar sesión dejar un comentario

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