Guía Completa de Diseño Mobile Responsive
Construye layouts responsive que funcionan en cualquier dispositivo. Cubre CSS Grid, Flexbox, container queries, fluid typography, mobile-first breakpoints y responsive images.
Nota para desarrolladores hispanohablantes: Esta guía incluye ejemplos y convenciones de nomenclatura adaptadas a equipos que trabajan en español. Cuando existen diferencias significativas en terminología técnica entre el inglés y el español, se indican explícitamente para facilitar la comunicación en equipos multiculturales.
Guía Completa de Diseño Mobile Responsive
Introducción
Diseño responsive significa construir layouts que se adaptan a cualquier tamaño de pantalla — desde teléfonos de 320px hasta monitores 4K. CSS moderno nos da Grid, Flexbox, container queries y fluid typography para construir interfaces responsive sin JavaScript. Esta guía cubre estrategia mobile-first, layouts con CSS Grid, patrones de Flexbox, container queries, fluid typography, responsive images y estrategias de testing.
Estrategia Mobile-First
Empezar con la pantalla más pequeña y progresivamente enhancar para pantallas más grandes. Esto te fuerza a priorizar contenido y mantiene el CSS lean.
/* Base styles — mobile first */
.card {
padding: 1rem;
font-size: 0.875rem;
}
/* Tablet y superior */
@media (min-width: 768px) {
.card {
padding: 1.5rem;
font-size: 1rem;
}
}
/* Desktop y superior */
@media (min-width: 1024px) {
.card {
padding: 2rem;
font-size: 1.125rem;
}
}
Breakpoints comunes
/* Breakpoints inspirados en Tailwind */
/* sm: 640px */
/* md: 768px */
/* lg: 1024px */
/* xl: 1280px */
/* 2xl: 1536px */
/* Usar min-width (mobile-first), no max-width (desktop-first) */
Layouts con CSS Grid
Grid básico
.grid {
display: grid;
gap: 1rem;
grid-template-columns: 1fr;
}
@media (min-width: 640px) {
.grid {
grid-template-columns: repeat(2, 1fr);
}
}
@media (min-width: 1024px) {
.grid {
grid-template-columns: repeat(3, 1fr);
}
}
Auto-fit grid (sin media queries)
.auto-grid {
display: grid;
gap: 1rem;
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
}
Holy grail layout
.layout {
display: grid;
grid-template-areas:
"header header header"
"nav main aside"
"footer footer footer";
grid-template-columns: 200px 1fr 250px;
grid-template-rows: auto 1fr auto;
min-height: 100vh;
}
@media (max-width: 768px) {
.layout {
grid-template-areas:
"header"
"main"
"nav"
"aside"
"footer";
grid-template-columns: 1fr;
}
}
.header { grid-area: header; }
.nav { grid-area: nav; }
.main { grid-area: main; }
.aside { grid-area: aside; }
.footer { grid-area: footer; }
Grid con named lines
.grid {
display: grid;
grid-template-columns: [sidebar-start] 250px [sidebar-end content-start] 1fr [content-end];
grid-template-rows: [header-start] 80px [header-end body-start] 1fr [body-end];
}
.header { grid-column: sidebar-start / content-end; grid-row: header-start / header-end; }
.sidebar { grid-column: sidebar-start / sidebar-end; grid-row: body-start / body-end; }
.content { grid-column: content-start / content-end; grid-row: body-start / body-end; }
Patrones de Flexbox
Centrado
.center {
display: flex;
align-items: center;
justify-content: center;
min-height: 100vh;
}
Sticky footer
body {
display: flex;
flex-direction: column;
min-height: 100vh;
}
main {
flex: 1;
}
Navigation bar
.nav {
display: flex;
align-items: center;
justify-content: space-between;
gap: 1rem;
padding: 0 1.5rem;
}
.nav-links {
display: flex;
gap: 1.5rem;
}
@media (max-width: 768px) {
.nav-links {
display: none;
}
.nav-menu-toggle {
display: block;
}
}
Card con contenido flexible
.card {
display: flex;
flex-direction: column;
gap: 0.75rem;
}
.card-content {
flex: 1;
}
.card-footer {
display: flex;
justify-content: space-between;
align-items: center;
}
Container Queries
Container queries permiten a los componentes responder al tamaño de su container, no del viewport. Esto habilita responsiveness a nivel componente real.
/* Definir un contexto de containment */
.card-container {
container-type: inline-size;
container-name: card;
}
/* Query el tamaño del container */
@container card (min-width: 400px) {
.card {
display: grid;
grid-template-columns: 120px 1fr;
gap: 1.5rem;
}
}
@container card (max-width: 399px) {
.card {
display: flex;
flex-direction: column;
gap: 0.75rem;
}
}
Container query units
.sidebar {
/* cqw = container query width */
font-size: clamp(0.875rem, 3cqw, 1.25rem);
padding: 2cqi;
}
Fluid Typography
clamp()
/* min, preferred (viewport-relative), max */
h1 {
font-size: clamp(1.5rem, 5vw, 3.5rem);
}
h2 {
font-size: clamp(1.25rem, 4vw, 2.5rem);
}
p {
font-size: clamp(1rem, 2.5vw, 1.125rem);
}
Fluid spacing
.section {
padding: clamp(1rem, 5vw, 4rem);
margin-bottom: clamp(1.5rem, 4vw, 3rem);
}
Responsive Images
srcset y sizes
<img
src="image-800.jpg"
srcset="image-400.jpg 400w, image-800.jpg 800w, image-1200.jpg 1200w, image-1600.jpg 1600w"
sizes="(max-width: 600px) 100vw, (max-width: 1200px) 50vw, 33vw"
alt="Description"
loading="lazy"
decoding="async"
/>
picture element (art direction)
<picture>
<source media="(max-width: 600px)" srcset="mobile.jpg" />
<source media="(max-width: 1200px)" srcset="tablet.jpg" />
<img src="desktop.jpg" alt="Description" />
</picture>
Aspect ratio
.image-container {
aspect-ratio: 16 / 9;
overflow: hidden;
}
.image-container img {
width: 100%;
height: 100%;
object-fit: cover;
}
Responsive Tables
.table-wrapper {
overflow-x: auto;
-webkit-overflow-scrolling: touch;
}
table {
width: 100%;
border-collapse: collapse;
min-width: 600px;
}
/* Card layout en mobile */
@media (max-width: 640px) {
table, thead, tbody, th, td, tr {
display: block;
}
thead {
display: none;
}
tr {
margin-bottom: 1rem;
border: 1px solid #e2e8f0;
border-radius: 0.5rem;
padding: 0.75rem;
}
td {
display: flex;
justify-content: space-between;
padding: 0.5rem 0;
}
td::before {
content: attr(data-label);
font-weight: 600;
margin-right: 1rem;
}
}
Testing
Browser DevTools
- Chrome DevTools: Device Mode (Ctrl+Shift+M)
- Firefox: Responsive Design Mode (Ctrl+Shift+M)
- Safari: Responsive Design Mode (Cmd+Ctrl+R)
CSS para testing
/* Debug grid lines */
* {
outline: 1px solid red;
}
/* Mostrar container query boundaries */
@container card (min-width: 400px) {
.card {
outline: 2px dashed blue;
}
}
Pautas
- Empezar mobile-first — los base styles apuntan a la pantalla más pequeña,
min-widthmedia queries enhancan hacia arriba - Usar
auto-fitgrids — eliminar media queries para card layouts - Preferir
clamp()para tipografía — scaling smooth sin saltos de breakpoint - Setear
aspect-ratioen imágenes — prevenir layout shift (CLS) - Usar
srcsetpara imágenes — servir la resolución apropiada por dispositivo - Añadir
loading="lazy"— deferir imágenes off-screen - Usar container queries para componentes — desacoplar del viewport
- Testear en dispositivos reales — los emuladores miss touch behavior y rendering bugs
- Evitar fixed pixel widths — usar
%,fr,vw,clamp()en su lugar - Manejar overflow explícitamente —
overflow-x: autoen tablas y code blocks - Usar
gapen lugar de margins — spacing más limpio en contextos flex/grid - Setear
min-width: 0en flex children — prevenir overflow en nested flex
Errores Comunes
- Usar
max-widthmedia queries (desktop-first) — los overrides son más difíciles y el CSS es más grande - Fixed pixel widths en containers — rompe en pantallas más pequeñas
- No setear dimensiones de imagen — causa layout shift
- Usar
display: nonepara navegación mobile sin toggle — los usuarios no pueden navegar - Olvidar
overflow-x: autoen tablas — horizontal scroll rompe la página - No testear en dispositivos reales — los emuladores miss performance y touch issues
- Usar
vhunits sin fallback — el mobile browser chrome cambia el viewport height - Overusar media queries — container queries y
auto-fitgrids reducen la necesidad - No setear
min-width: 0en flex children — el contenido overflow los containers - Ignorar landscape orientation — los teléfonos en landscape tienen constraints diferentes
Preguntas Frecuentes
¿Debo usar container queries o media queries?
Usar media queries para layout a nivel página (header, sidebar, main grid). Usar container queries para responsiveness a nivel componente (cards, widgets, sidebars que aparecen en diferentes contexts). Son complementarios — no un reemplazo.
¿Cuál es la diferencia entre auto-fit y auto-fill en CSS Grid?
auto-fit colapsa los tracks vacíos a zero, stretchando los items restantes para llenar la row. auto-fill preserva los tracks vacíos como gaps. Usar auto-fit cuando quieres que los items crezcan y llenen el espacio available. Usar auto-fill cuando quieres que los items mantengan su size y dejen gaps.
¿Cómo manejo el issue de mobile viewport height?
Los mobile browsers dinámicamente muestran/ocultan la address bar, cambiando vh. Usar 100dvh (dynamic viewport height) en lugar de 100vh. Para soporte de browsers más viejos, usar el viewport meta tag con interactive-widget=resizes-content o JavaScript con window.innerHeight.