Saltar al contenido
schedule 12 min CSS

Responsive design

Más del 60% del tráfico web viene de móviles. Si tu web no se ve bien en un iPhone, pierdes a más de la mitad de tus usuarios. Responsive design no es un extra — es lo mínimo. En esta lección vas a aprender cómo hacer que un layout se adapte a cualquier pantalla, desde un reloj hasta un ultrawide de 34".

El meta viewport

Sin esta línea en tu <head>, nada de responsive funciona:

<meta name="viewport" content="width=device-width, initial-scale=1">

Le dice al navegador móvil: "usa el ancho real del dispositivo, no simules ser un monitor de escritorio". Si no lo pones, el móvil renderiza la página a 980px y la encoge. Inusable.

Mobile first: diseña para móvil primero

La estrategia mobile first significa escribir los estilos base para móvil y luego añadir complejidad para pantallas más grandes con min-width:

/* Base: móvil (una columna) */
.card-grid {
    display: grid;
    grid-template-columns: 1fr;
    gap: 1rem;
    padding: 1rem;
}

/* Tablet: 2 columnas */
@media (min-width: 640px) {
    .card-grid {
        grid-template-columns: repeat(2, 1fr);
        gap: 1.5rem;
        padding: 2rem;
    }
}

/* Desktop: 3 columnas */
@media (min-width: 1024px) {
    .card-grid {
        grid-template-columns: repeat(3, 1fr);
    }
}

¿Por qué mobile first y no al revés?

  • Los estilos móviles son más simples (una columna, sin sidebar). Es más fácil empezar simple y añadir.
  • Si el CSS no carga, el usuario móvil tiene una experiencia funcional.
  • Obliga a priorizar el contenido: ¿qué es lo más importante?

Media queries

Las media queries aplican estilos solo cuando se cumple una condición:

/* Por ancho de pantalla */
@media (min-width: 768px) { /* tablets y superior */ }
@media (min-width: 1024px) { /* desktop */ }
@media (min-width: 1280px) { /* desktop grande */ }

/* Por preferencia del usuario */
@media (prefers-color-scheme: dark) {
    :root {
        --color-bg: #0a0a0b;
        --color-text: #e0e0e0;
    }
}

@media (prefers-reduced-motion: reduce) {
    * {
        animation-duration: 0.01ms !important;
        transition-duration: 0.01ms !important;
    }
}

/* Por orientación */
@media (orientation: landscape) {
    .hero { min-height: 100dvh; }
}

/* Combinar condiciones */
@media (min-width: 768px) and (max-width: 1023px) {
    /* Solo tablets */
}

prefers-reduced-motion es crítica para accesibilidad. Hay personas con trastornos vestibulares para quienes las animaciones causan mareo real. Siempre respétala.

Unidades relativas

Las unidades fijas (px) no se adaptan. Las relativas sí:

Unidad Relativa a Uso típico
rem font-size del <html> Tamaños de texto, spacing, padding
em font-size del elemento padre Márgenes relativos al texto
% Tamaño del contenedor padre Anchos fluidos
vw / vh Viewport (ventana del navegador) Heroes, secciones a pantalla completa
dvh Viewport dinámico Altura real en móvil (sin barra de navegación)
ch Ancho del carácter "0" Ancho de lectura óptimo (65ch)

Viewport units: vh vs dvh vs svh vs lvh

.hero {
    /* vh: puede dejar espacio o solaparse con la barra del navegador en móvil */
    min-height: 100vh;

    /* dvh: se ajusta dinámicamente cuando la barra del navegador aparece/desaparece */
    min-height: 100dvh;    /* ✅ Usa esta */

    /* svh: la altura más pequeña posible (con barra visible) */
    min-height: 100svh;

    /* lvh: la altura más grande posible (sin barra visible) */
    min-height: 100lvh;
}

En móvil, la barra de dirección del navegador aparece y desaparece al hacer scroll. vh no tiene esto en cuenta, lo que causa un bug clásico donde el hero "rebota". dvh lo resuelve.

clamp(): valores fluidos sin media queries

.hero-title {
    /* clamp(mínimo, preferido, máximo) */
    font-size: clamp(2rem, 5vw, 4.5rem);
    /* En móvil: 2rem, crece con el viewport, máximo 4.5rem */
}

.container {
    width: clamp(320px, 90%, 1200px);
    margin-inline: auto;
}

.card {
    padding: clamp(1rem, 3vw, 2.5rem);
}

clamp() es una de las funciones más útiles de CSS moderno. Un valor que se adapta al viewport sin escribir una sola media query.

Container queries: responsive basado en el componente

Las media queries miran el viewport (la ventana del navegador). Pero ¿y si una card está en un sidebar estrecho en desktop? Media queries no lo detectan. Container queries sí:

/* Definir el contenedor */
.card-wrapper {
    container-type: inline-size;
    container-name: card;
}

/* Estilar según el tamaño del CONTENEDOR, no del viewport */
@container card (min-width: 400px) {
    .card {
        display: flex;
        gap: 1.5rem;
    }
    .card-image {
        flex: 0 0 150px;
    }
}

@container card (max-width: 399px) {
    .card {
        display: block;
    }
    .card-image {
        width: 100%;
    }
}
<!-- La misma card se adapta al espacio disponible -->
<div class="card-wrapper" style="width: 600px">
    <article class="card"><!-- Layout horizontal --></article>
</div>

<div class="card-wrapper" style="width: 280px">
    <article class="card"><!-- Layout vertical --></article>
</div>

Container queries hacen que los componentes sean verdaderamente reutilizables: se adaptan al espacio donde los pongas, no a la pantalla global.

Container query units

.card-wrapper {
    container-type: inline-size;
}

.card-title {
    font-size: clamp(1rem, 4cqi, 1.5rem);  /* cqi = 1% del inline-size del container */
}

Imágenes responsive

/* Regla básica: las imágenes nunca desbordan su contenedor */
img {
    max-width: 100%;
    height: auto;
    display: block;
}

/* Imagen que llena su contenedor (como background pero mejor) */
.card-image img {
    width: 100%;
    height: 200px;
    object-fit: cover;      /* Recorta para llenar sin deformar */
    object-position: center;
}

.avatar img {
    width: 48px;
    height: 48px;
    border-radius: 50%;
    object-fit: cover;
}

Un patrón responsive completo

styles.css
/* === Base: móvil === */
.page {
    display: grid;
    grid-template-areas:
        "header"
        "main"
        "footer";
    min-height: 100dvh;
}

.header  { grid-area: header; }
.main    { grid-area: main; }
.sidebar { display: none; }   /* Oculto en móvil */
.footer  { grid-area: footer; }

.container {
    width: clamp(320px, 90%, 1200px);
    margin-inline: auto;
    padding-inline: 1rem;
}

.card-grid {
    display: grid;
    grid-template-columns: 1fr;
    gap: 1rem;
}

/* === Tablet === */
@media (min-width: 768px) {
    .card-grid {
        grid-template-columns: repeat(2, 1fr);
        gap: 1.5rem;
    }

    .container {
        padding-inline: 2rem;
    }
}

/* === Desktop === */
@media (min-width: 1024px) {
    .page {
        grid-template-areas:
            "header  header"
            "sidebar main"
            "footer  footer";
        grid-template-columns: 280px 1fr;
    }

    .sidebar {
        display: block;
        grid-area: sidebar;
    }

    .card-grid {
        grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
    }
}
code

Haz responsive la tienda de vinilos

Medio schedule 20 min

Toma el layout de Vinyl Paradise y hazlo completamente responsive:

  • Mobile first: todo en una columna por defecto
  • La navbar: en móvil, logo arriba y links debajo (flex-direction: column). En desktop, en fila con space-between
  • Cards: 1 columna en móvil, 2 en tablet (640px), 3+ en desktop con auto-fit
  • Título hero con clamp() que escale fluidamente
  • Al menos una card con container query que cambie de layout vertical a horizontal cuando el contenedor es ancho
  • Imágenes con max-width: 100% y object-fit: cover
  • min-height: 100dvh en el hero (no vh)
  • Respeta prefers-reduced-motion si tienes hover effects
lightbulb Pistas

Empieza escribiendo todo el CSS sin media queries — ese es tu layout móvil. Luego añade un breakpoint a 640px para tablet y otro a 1024px para desktop. Para la container query, envuelve la card en un div con container-type: inline-size.

Newsletter

Recibe nuevos cursos, actualizaciones, artículos del blog y promociones en tu correo.