Diseño responsive
En esta lección
Una web responsive se adapta al tamaño de la pantalla: se ve bien en un móvil, en una tablet y en un monitor enorme. Hoy más de la mitad de las visitas llegan desde el móvil, así que no es opcional. En esta lección verás las piezas que lo hacen posible: la etiqueta viewport, el enfoque mobile first, las media queries (también las modernas), las unidades relativas, la tipografía fluida y cómo probarlo todo sin tener diez móviles en la mesa.
Paso 1: la etiqueta viewport
Sin esta línea en el <head>, el móvil muestra la página como si fuera un ordenador, en miniatura:
<meta name="viewport" content="width=device-width, initial-scale=1">
¿Por qué pasa eso? Cuando aparecieron los primeros smartphones, casi ninguna web estaba pensada para ellos. Los navegadores móviles decidieron “fingir” una pantalla de unos 980px de ancho y encoger el resultado. Con width=device-width le dices al navegador: “mi página sí está preparada, usa el ancho real del dispositivo”. Y initial-scale=1 arranca sin zoom.
Cuidado: no añadas
user-scalable=nonimaximum-scale=1. Impiden que la gente amplíe la página con los dedos, y eso es un problema grave de accesibilidad para quien ve mal.
Paso 2: nada de anchos fijos
Evita cosas como width: 960px. En un móvil de 390px, se sale de la pantalla y aparece la temida barra de desplazamiento horizontal. Usa en su lugar:
.contenedor {
max-width: 1100px; /* como mucho 1100px... */
margin: 0 auto; /* ...centrado */
padding: 0 16px; /* y con aire en los lados */
}
img {
max-width: 100%; /* las imágenes nunca más anchas que su caja */
height: auto; /* mantiene la proporción */
}
La idea clave: deja que el contenido fluya y pon solo límites (max-width, min-width). Flexbox y Grid ayudan muchísimo porque reparten el espacio disponible sin que tengas que calcular píxeles. Por ejemplo, repeat(auto-fit, minmax(200px, 1fr)) crea una galería que cambia de columnas sola (lo tienes en Grid).
Paso 3: media queries
Una media query aplica estilos solo si se cumple una condición, normalmente un ancho de pantalla:
/* Estilos para todas las pantallas */
.menu { display: flex; flex-direction: column; }
/* Solo en pantallas de 768px o más */
@media (min-width: 768px) {
.menu { flex-direction: row; }
}
min-width: 768px: pantallas de 768px o más.max-width: 767px: pantallas de 767px o menos.
Las reglas de dentro de la media query no tienen más especificidad que las de fuera. Si ganan es porque están escritas después en el archivo (la cascada, que viste en Cascada, especificidad y herencia). Por eso las media queries van siempre al final de cada bloque o del archivo.
La sintaxis moderna de rangos
Desde 2023 todos los navegadores actuales entienden una forma más legible de escribir los rangos, con operadores de comparación:
/* Igual que (min-width: 600px) */
@media (width >= 600px) { ... }
/* Entre 600px y 899px, de un vistazo */
@media (600px <= width < 900px) {
.lateral { display: none; }
}
Con min-width y max-width el rango “de 600 a 899” obligaba a escribir (min-width: 600px) and (max-width: 899.98px). La versión nueva se lee como una fórmula de matemáticas y evita el lío de los decimales.
Combinar condiciones
/* Las dos condiciones: and */
@media (width >= 600px) and (orientation: landscape) { ... }
/* Una u otra: coma */
@media (width < 400px), (height < 500px) { ... }
/* Solo al imprimir */
@media print {
nav, footer { display: none; }
}
Otras características que puedes consultar
Las media queries no solo miran el ancho. Estas son las más útiles:
| Consulta | Se cumple cuando… |
|---|---|
(orientation: portrait) | La ventana es más alta que ancha |
(orientation: landscape) | La ventana es más ancha que alta |
(prefers-color-scheme: dark) | El sistema del usuario está en modo oscuro |
(prefers-reduced-motion: reduce) | El usuario ha pedido menos animaciones |
(hover: hover) | El puntero principal puede pasar por encima (ratón) |
(pointer: coarse) | El puntero principal es poco preciso (un dedo) |
Modo oscuro automático
Combinado con variables CSS, cambiar de tema es cuestión de pocas líneas:
:root {
--fondo: #ffffff;
--texto: #1b1b1b;
}
@media (prefers-color-scheme: dark) {
:root {
--fondo: #16181d;
--texto: #e8e8e8;
}
}
body {
background: var(--fondo);
color: var(--texto);
}
Tienes más sobre esto en Variables CSS.
Menos movimiento
Hay personas a las que las animaciones les marean. Si lo han indicado en su sistema, respétalo:
@media (prefers-reduced-motion: reduce) {
* { animation: none; transition: none; }
}
Lo ves con más detalle en Transiciones y animaciones.
Ratón o dedo
Un efecto :hover en un móvil no tiene sentido: no hay “pasar por encima”. Y un botón de 24px es fácil de pulsar con ratón, pero no con el dedo:
/* Efecto solo donde hay ratón */
@media (hover: hover) {
.tarjeta:hover { transform: translateY(-4px); }
}
/* Botones más grandes en pantallas táctiles */
@media (pointer: coarse) {
.boton { min-height: 44px; padding: 12px 20px; }
}
Esto es mejor que suponer “móvil = táctil”: hay portátiles con pantalla táctil y tablets con ratón.
Mobile first
La forma recomendada es escribir primero el CSS para móvil y añadir cambios para pantallas grandes con min-width. Así:
- El móvil, que suele tener peor conexión y procesador, aplica el CSS más sencillo.
- Pensar primero en la pantalla pequeña te obliga a decidir qué es lo importante.
- El CSS base suele ser una sola columna, que es lo que el HTML hace por defecto. Escribes menos.
Compara los dos enfoques para una rejilla de productos:
/* Mobile first: se añade complejidad al crecer */
.productos { display: grid; gap: 16px; }
@media (width >= 600px) {
.productos { grid-template-columns: repeat(2, 1fr); }
}
@media (width >= 900px) {
.productos { grid-template-columns: repeat(4, 1fr); }
}
/* Desktop first: se deshace lo que sobra al encoger */
.productos { display: grid; gap: 16px; grid-template-columns: repeat(4, 1fr); }
@media (width < 900px) {
.productos { grid-template-columns: repeat(2, 1fr); }
}
@media (width < 600px) {
.productos { grid-template-columns: 1fr; }
}
Los dos funcionan, pero en el segundo pasas el tiempo anulando lo anterior. Con mobile first casi siempre escribes solo min-width (o width >=).
Puntos de corte
Un punto de corte (breakpoint) es el ancho en el que cambia el diseño. Unos habituales son 600px (tablets pequeñas), 900px (tablets y portátiles pequeños) y 1200px (escritorio). Pero lo mejor es poner el punto de corte donde tu diseño empiece a verse mal, no donde diga una lista.
El método práctico: abre la página, estrecha y ensancha la ventana poco a poco, y en cuanto algo se vea apretado o demasiado estirado, ahí va una media query. No diseñes para “el iPhone 15”: mañana saldrá otro con otro tamaño.
Consejo: con dos o tres puntos de corte basta para casi cualquier web. Si necesitas ocho, probablemente el diseño base no es lo bastante flexible.
Unidades relativas y tipografía fluida
%: porcentaje del ancho del elemento padre.rem: relativo al tamaño de letra base (normalmente 16px). Si el usuario aumenta la letra en su navegador, todo crece con él.vw/vh: porcentaje del ancho y el alto de la ventana.100vhes toda la altura (en móvil, mejor100dvh).clamp(): un valor que crece con la pantalla pero con mínimo y máximo.
Tienes todas explicadas en Unidades de medida. Aquí nos interesa una aplicación concreta: la tipografía fluida. En lugar de cambiar el tamaño de los títulos a saltos con media queries, dejas que crezca poco a poco:
h1 {
/* mínimo 1.8rem, ideal 1rem + 4vw, máximo 3.5rem */
font-size: clamp(1.8rem, 1rem + 4vw, 3.5rem);
}
.seccion {
/* el espaciado también puede ser fluido */
padding-block: clamp(2rem, 6vw, 5rem);
}
Sumar rem y vw (en vez de usar solo vw) tiene un porqué: si el valor ideal fuera solo 5vw, al hacer zoom el texto no crecería, porque la ventana no cambia de ancho. Con la parte en rem, el zoom sigue funcionando.
Imágenes flexibles
Con max-width: 100% y height: auto las imágenes ya no rompen el diseño. Pero hay dos mejoras importantes:
- Servir la imagen del tamaño adecuado a cada pantalla, para que el móvil no descargue una foto de 3000px. Eso se hace en el HTML con
srcsety<picture>: lo tienes en Imágenes responsive: srcset y picture. - Recortar las imágenes para que encajen en una caja sin deformarse (
object-fit: cover) y reservar su proporción (aspect-ratio): lo ves en Imágenes: object-fit, aspect-ratio y filtros.
.tarjeta img {
width: 100%;
aspect-ratio: 16 / 9;
object-fit: cover;
}
Un menú responsive
El caso clásico: en móvil los enlaces van uno debajo de otro; en pantallas anchas, en fila con el logo a la izquierda.
<header class="cabecera">
<a class="logo" href="/">GymFit</a>
<nav>
<ul class="menu">
<li><a href="/clases">Clases</a></li>
<li><a href="/horarios">Horarios</a></li>
<li><a href="/tarifas">Tarifas</a></li>
</ul>
</nav>
</header>
.cabecera { padding: 12px 16px; }
.menu {
list-style: none;
margin: 0;
padding: 0;
display: flex;
flex-direction: column; /* móvil: en columna */
gap: 8px;
}
@media (width >= 700px) {
.cabecera {
display: flex;
justify-content: space-between;
align-items: center;
}
.menu { flex-direction: row; gap: 24px; }
}
Muchas webs esconden el menú del móvil tras un botón “hamburguesa”. Mostrarlo y ocultarlo necesita un poco de JavaScript (o el elemento <details>), pero el CSS de base es exactamente este. Tienes una maqueta completa en el proyecto de maquetación.
Media queries frente a container queries
Las media queries miran la ventana. Pero a veces lo que importa es el hueco donde está un componente: una tarjeta puede estar en una columna estrecha lateral o en el contenido principal ancho, dentro de la misma pantalla. Para eso existen las container queries, que preguntan por el tamaño del contenedor:
.zona-tarjeta { container-type: inline-size; }
@container (width >= 400px) {
.tarjeta { display: flex; }
}
Úsalas para componentes reutilizables y deja las media queries para el diseño general de la página. Las explicamos a fondo en CSS moderno.
Cómo probarlo
En el navegador, pulsa F12 y activa el modo dispositivo (el icono del móvil y la tablet, o Ctrl + Mayús + M). Podrás:
- Elegir modelos de móvil o escribir un ancho a mano.
- Arrastrar el borde para ver en qué ancho se rompe el diseño.
- Simular una conexión lenta y el modo oscuro (en el panel Rendering, opción prefers-color-scheme).
Aun así, prueba de vez en cuando en un móvil de verdad: el tacto, el tamaño real del texto y la barra del navegador no se simulan del todo.
Errores frecuentes
- Olvidar la etiqueta viewport. Las media queries parecen no funcionar en el móvil porque este cree que mide 980px.
- Media queries antes que los estilos base. Como tienen la misma especificidad, la regla que va después las pisa. Ponlas al final.
- Anchos fijos en píxeles en contenedores, tablas o imágenes, que provocan scroll horizontal. Busca el culpable en DevTools.
- Mezclar
min-widthymax-widthsin orden, con rangos que se solapan (max-width: 600pxymin-width: 600pxse cumplen a la vez justo a 600px). La sintaxiswidth < 600px/width >= 600pxlo evita. - Ocultar contenido importante en móvil con
display: none. Si es importante, reorganízalo; no lo quites.
Resumen
| Herramienta | Para qué |
|---|---|
<meta name="viewport"> | Que el móvil use su ancho real |
max-width, %, Flexbox, Grid | Diseños fluidos sin anchos fijos |
@media (width >= 600px) | Cambiar el diseño a partir de un ancho |
| Mobile first | CSS base para móvil, min-width para crecer |
prefers-color-scheme, prefers-reduced-motion | Respetar las preferencias del usuario |
hover, pointer | Adaptar a ratón o dedo |
clamp() | Tipografía y espacios fluidos |
max-width: 100%, srcset, object-fit | Imágenes flexibles |
@container | Adaptar un componente a su hueco |
| DevTools (modo dispositivo) | Probar distintos tamaños |
Pruébalo tú
Cambia el código y pulsa Ejecutar (o Ctrl + Enter).
Pon a prueba lo que has aprendido
¿Te ha quedado claro? Márcala y verás tu progreso en el explorador.