Responsive Design
Responsive Design es la práctica de diseñar y desarrollar páginas web que se adaptan al dispositivo del usuario—desktop, tablet, mobile—sin necesidad de versiones separadas. En lugar de crear tres sitios distintos, escribís un solo HTML/CSS que reorganiza, escala y reordena los componentes según el espacio disponible. Esto se logra combinando media queries (condiciones basadas en el viewport), unidades relativas (rem, em, vw, vh), layouts flexibles (Flexbox y Grid) y imágenes adaptativas (srcset, picture).
El enfoque moderno es mobile-first: escribís los estilos base para la pantalla más chica, y luego usás media queries con min-width para ir agregando estilos a medida que la pantalla crece. Esto tiene dos ventajas: los estilos base son más ligeros (mobile tiene menos funcionalidad), y las media queries son progresivas (agregan, no reescriben). El enfoque contrario (desktop-first con max-width) obliga a sobreescribir muchos estilos y genera CSS más frágil y difícil de mantener.
Media Queries: sintaxis y breakpoints
Las media queries son la herramienta fundamental del responsive design: permiten aplicar estilos condicionalmente según características del dispositivo o del viewport. La sintaxis es @media (condicion) { ... }, donde la condición puede ser el ancho del viewport (min-width, max-width), la orientación (orientation), la densidad de píxeles (resolution), preferencias del usuario (prefers-color-scheme, prefers-reduced-motion), y muchas más. Se pueden combinar con and, or (coma) y not.
Sintaxis básica
/* ============================================
Sintaxis basica de @media
============================================ */
/* Estilo simple: se aplica si el ancho >= 768px */
@media (min-width: 768px) {
.container {
max-width: 720px;
margin: 0 auto;
}
}
/* Combinacion con and: ancho >= 768px Y orientacion landscape */
@media (min-width: 768px) and (orientation: landscape) {
.sidebar {
display: none;
}
}
/* Lista separada por comas (OR): se aplica si CUALQUIERA es true */
@media (min-width: 1024px), (orientation: landscape) {
.grid {
grid-template-columns: repeat(3, 1fr);
}
}
/* NOT: invierte la condicion */
@media not (min-width: 768px) {
/* Solo se aplica si el ancho es MENOR a 768px */
.mobile-only {
display: block;
}
}
/* ============================================
Media query en el tag link (CSS externo)
============================================ */
/* <link rel="stylesheet" href="desktop.css" media="(min-width: 768px)">
El navegador solo descarga desktop.css si la condicion es true.
Ahorra ancho de banda en mobile! */
/* ============================================
Media query inline en style
============================================ */
/* <style media="(max-width: 600px)">
.hide-mobile { display: none; }
</style> */
Breakpoints comunes
Los breakpoints son los puntos de corte donde el layout cambia significativamente. No hay breakpoints universales—dependen del diseño—pero existen convenciones basadas en los anchos de dispositivo más comunes. Lo importante es que los breakpoints respondan al contenido, no a dispositivos específicos. No agregués un breakpoint porque "así lo dice el iPhone", agregálo porque el contenido se rompe a ese ancho.
/* ============================================
Breakpoints del proyecto WebForge (mobile-first)
============================================ */
/* sm: 640px — teléfonos grandes (landscape) */
@media (min-width: 640px) {
.container { max-width: 640px; }
}
/* md: 768px — tablets portrait */
@media (min-width: 768px) {
.container { max-width: 768px; }
.grid-2col {
grid-template-columns: repeat(2, 1fr);
}
}
/* lg: 1024px — tablets landscape, laptops */
@media (min-width: 1024px) {
.container { max-width: 1024px; }
.page-layout {
grid-template-columns: 250px 1fr;
}
/* Sidebar visible en desktop */
.site-sidebar {
transform: translateX(0);
}
}
/* xl: 1280px — desktops grandes */
@media (min-width: 1280px) {
.container { max-width: 1280px; }
}
/* 2xl: 1536px — monitores widescreen */
@media (min-width: 1536px) {
.container { max-width: 1536px; }
}
/* ============================================
Breakpoints con Custom Properties (recomendado)
============================================ */
:root {
--bp-sm: 640px;
--bp-md: 768px;
--bp-lg: 1024px;
--bp-xl: 1280px;
--bp-2xl: 1536px;
}
/* NOTA: CSS no permite var() dentro de @media todavia
(está en draft). Pero podés usar Sass/EJS para generarlo. */
| Breakpoint | Ancho | Dispositivo típico | Qué cambia |
|---|---|---|---|
| Base | 0 - 639px |
Mobile portrait | Layout de 1 columna, sidebar oculto, tipografía chica, cards apiladas. |
sm |
≥ 640px |
Mobile landscape | Container más ancho, tipografía ligeramente mayor, 2 columnas en grids simples. |
md |
≥ 768px |
Tablet portrait | Grids de 2 columnas, padding más generoso, elementos ocultos en mobile aparecen. |
lg |
≥ 1024px |
Tablet landscape / laptop | Layout con sidebar, navbar completa, grids de 3 columnas. |
xl |
≥ 1280px |
Desktop | Container máximo, espaciado generoso, layout de página completo. |
2xl |
≥ 1536px |
Widescreen | Contenido centrado con max-width, más espacio en grids. |
Enfoque Mobile-First
El enfoque mobile-first significa que los estilos base (fuera de cualquier media query) están escritos para la pantalla más pequeña. Luego, usás @media (min-width: ...) para agregar estilos progresivamente a medida que la pantalla crece. Esto produce un CSS más limpio, más mantenible y más rápido en mobile (los estilos base son mínimos y las media queries solo se descargan/evaluan si aplican). El enfoque desktop-first (max-width) es lo contrario: los estilos base son para desktop y sobreescribís para mobile, lo que genera más CSS y es más propenso a errores.
/* ============================================
MOBILE-FIRST vs DESKTOP-FIRST: comparacion
============================================ */
/* --- MOBILE-FIRST (recomendado) --- */
/* Estilos base: mobile (sin media query) */
.card-grid {
display: grid;
grid-template-columns: 1fr; /* 1 columna en mobile */
gap: 1rem;
padding: 1rem;
}
/* Tablet: 2 columnas */
@media (min-width: 768px) {
.card-grid {
grid-template-columns: repeat(2, 1fr);
gap: 1.5rem;
padding: 1.5rem;
}
}
/* Desktop: 3 columnas + sidebar */
@media (min-width: 1024px) {
.card-grid {
grid-template-columns: repeat(3, 1fr);
padding: 2rem;
}
}
/* --- DESKTOP-FIRST (no recomendado) --- */
/* Estilos base: desktop */
.card-grid-old {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 2rem;
padding: 2rem;
}
/* Tablet: hay que REESCRIBIR todo */
@media (max-width: 1023px) {
.card-grid-old {
grid-template-columns: repeat(2, 1fr);
gap: 1.5rem;
padding: 1.5rem;
}
}
/* Mobile: REESCRIBIR otra vez */
@media (max-width: 767px) {
.card-grid-old {
grid-template-columns: 1fr;
gap: 1rem;
padding: 1rem;
}
}
/* ============================================
MOBILE-FIRST: ejemplo real de componente
============================================ */
/* Base (mobile): todo apilado */
.page-layout {
display: grid;
grid-template-areas:
"header"
"content"
"sidebar"
"footer";
grid-template-columns: 1fr;
gap: 1rem;
}
/* md: sidebar pasa a la derecha */
@media (min-width: 768px) {
.page-layout {
grid-template-areas:
"header header"
"content sidebar"
"footer footer";
grid-template-columns: 2fr 1fr;
}
}
/* lg: sidebar a la izquierda */
@media (min-width: 1024px) {
.page-layout {
grid-template-areas:
"header header"
"sidebar content"
"footer footer";
grid-template-columns: 250px 1fr;
}
}
min-width vs max-width
Usá siempre min-width (mobile-first). Con min-width, las media queries se apilan progresivamente y no se anulan entre sí. Con max-width (desktop-first), las queries se solapan y necesitás ordenarlas de mayor a menor para evitar conflictos, lo que es propenso a errores. Excepción: max-width está bien para un ajuste rápido o cuando heredás un proyecto legacy desktop-first.
Unidades relativas: rem, em, vw, vh, %
Las unidades relativas son la base del responsive design sin media queries. A diferencia de px (que es un tamaño fijo), las unidades relativas se calculan en relación a otro valor: el tamaño de fuente del root (rem), el tamaño de fuente del elemento padre (em), el ancho del viewport (vw), la altura del viewport (vh), o el tamaño del contenedor padre (%). Usar unidades relativas permite que el layout escale naturalmente con el viewport sin necesidad de breakpoints para cada cambio.
rem y em
rem (root em) es relativo al tamaño de fuente del elemento raíz (:root / <html>). Si el html tiene font-size: 16px, entonces 1rem = 16px, 2rem = 32px, 0.5rem = 8px. em es relativo al font-size del elemento padre directo. Si un padre tiene font-size: 20px, entonces 1em = 20px para ese hijo. La diferencia clave es que rem es predecible (siempre relativo al root), mientras que em es contextual (cambia según el padre).
/* ============================================
rem: relativo al root (html)
============================================ */
/* Por defecto, los navegadores tienen:
html { font-size: 16px; }
Entonces: 1rem = 16px */
html {
font-size: 16px; /* 1rem = 16px (base) */
}
/* Tipografia con rem */
h1 { font-size: 2rem; } /* 32px */
h2 { font-size: 1.5rem; } /* 24px */
h3 { font-size: 1.25rem; } /* 20px */
p { font-size: 1rem; } /* 16px */
small { font-size: 0.875rem; } /* 14px */
/* Espaciado con rem */
.container {
padding: 2rem; /* 32px */
margin-bottom: 1.5rem; /* 24px */
}
/* ============================================
em: relativo al PADRE (contextual)
============================================ */
.card {
font-size: 1rem; /* 16px - este es el "contexto" */
}
.card .title {
font-size: 1.5em; /* 1.5 * 16px = 24px (relativo al .card) */
}
.card .meta {
font-size: 0.75em; /* 0.75 * 16px = 12px */
}
/* CUIDADO con em anidados */
.sidebar {
font-size: 0.875em; /* 14px si el padre es 16px */
}
.sidebar .menu-item {
font-size: 1.2em; /* 1.2 * 14px = 16.8px (NO 19.2px!) */
/* Cada nivel de anidacion cambia la referencia */
}
/* ============================================
rem vs em: cuando usar cada uno
============================================ */
/* Usá rem para: tipografia, espaciado, padding, margin
Es predecible porque no cambia con el contexto */
.component {
font-size: 1rem; /* Predecible: siempre 16px */
padding: 1rem 1.5rem; /* Predecible: 16px 24px */
margin-bottom: 2rem; /* Predecible: 32px */
}
/* Usá em para: componentes escalables, botones dentro de cards
Si cambias el font-size del padre, todo escala proporcionalmente */
.btn {
font-size: 1em; /* Igual al texto del contexto */
padding: 0.5em 1em; /* Proporcional al texto */
}
.card-large {
font-size: 1.25rem;
/* El .btn dentro escala automaticamente
1em ahora = 1.25rem = 20px */
}
vw, vh y unidades del viewport
vw (viewport width) y vh (viewport height) son relativos al tamaño del viewport. 1vw = 1% del ancho del viewport, 1vh = 1% de la altura. Estas unidades son ideales para elementos que deben escalar con la pantalla: heroes de ancho completo, tipografía que escala con el viewport, o layouts que deben llenar toda la pantalla. vmin es el menor de vw/vh, y vmax es el mayor. También existen dvw, dvh, lvw, lvh (dynamic/large viewport) para manejar la barra de direcciones de navegadores mobile.
/* ============================================
vw: viewport width (1vw = 1% del ancho)
============================================ */
/* Hero que siempre mide 100% del ancho */
.hero {
width: 100vw; /* Ocupa todo el viewport */
/* CUIDADO: si el elemento tiene padding o esta dentro de
un contenedor con scroll, puede causar overflow horizontal */
max-width: 100%;
}
/* Tipografia que escala con el viewport (sin media queries!) */
.hero-title {
font-size: 5vw; /* En 375px mobile: 18.75px
En 1440px desktop: 72px */
/* PROBLEMA: en mobile es muy chica, en desktop muy grande
Solucion: usar clamp() (ver seccion siguiente) */
}
/* ============================================
vh: viewport height (1vh = 1% de la altura)
============================================ */
/* Seccion que ocupa toda la pantalla */
.full-screen {
min-height: 100vh; /* La altura completa del viewport */
display: flex;
align-items: center;
justify-content: center;
}
/* Panel lateral que ocupa 80% de la altura */
.side-panel {
height: 80vh;
overflow-y: auto;
}
/* ============================================
vmin y vmax: el menor/mayor entre vw y vh
============================================ */
/* Elemento cuadrado que siempre entra en pantalla */
.square {
width: 80vmin; /* 80% del menor entre ancho y alto */
height: 80vmin; /* En portrait: 80vh, en landscape: 80vw */
}
/* ============================================
dvh, lvh, svh: unidades de viewport modernas
Solucionan el problema de la barra de direccion mobile
============================================ */
/* En mobile, 100vh incluye el area detras de la barra de URL
del navegador, causando que el contenido se oculte.
dvh (dynamic viewport height):
- Cambia dinamicamente cuando la barra se muestra/oculta */
.safe-height {
min-height: 100dvh; /* Altura real visible, sin barra */
/* Soporte: Chrome 108+, Safari 15.4+, Firefox 101+ */
}
/* Fallback para navegadores sin soporte de dvh */
@supports (min-height: 100dvh) {
.safe-height {
min-height: 100dvh;
}
@supports not (min-height: 100dvh) {
.safe-height {
min-height: 100vh;
min-height: -webkit-fill-available; /* iOS Safari */
}
}
}
| Unidad | Relativa a | Uso principal | Precaución |
|---|---|---|---|
rem |
font-size de :root |
Tipografía, espaciado, padding, margin. Es predecible y consistente. | Necesitás definir un font-size base en :root. |
em |
font-size del padre |
Componentes escalables (botones, badges). Si el padre cambia, todo escala. | Se acumula con el anidamiento. Difícil de debuggear. |
% |
Dimensión del padre | Anchos de layout (width: 50%), grids flexibles. |
Para padding-top/bottom, es relativo al ancho del padre (no alto). |
vw |
1% del ancho del viewport | Tipografía fluida, elementos full-width. | En mobile muy chico puede ser demasiado pequeño. Usá con clamp(). |
vh |
1% de la altura del viewport | Secciones full-height, overlay modales. | En mobile incluye la barra de direcciones. Usá dvh cuando sea posible. |
dvh |
Altura dinámica del viewport | Reemplazo moderno de vh en mobile. |
Soporte reciente. Necesita fallback. |
100vh en mobile iOS
En iPhones con la barra de direcciones dinámica, 100vh es más alto que el área visible porque incluye el espacio detrás de la barra de URL. Esto causa que parte del contenido quede oculto y no accesible sin scroll. La solución moderna es 100dvh (dynamic viewport height). Para navegadores que no lo soportan, el fallback es height: -webkit-fill-available (Safari) o simplemente 100vh.
Tipografía fluida con clamp()
clamp(min, preferred, max) es una función CSS que reemplaza la necesidad de media queries para tipografía fluida. Define un valor que escala entre un mínimo y un máximo, usando un valor preferido como punto intermedio. Si el viewport es chico, el valor se queda en el mínimo. Si es grande, no supera el máximo. En el rango intermedio, escala proporcionalmente. Esto elimina la necesidad de escribir 4 media queries para ajustar el tamaño de fuente en cada breakpoint.
/* ============================================
clamp(min, preferred, max)
- Si el valor calculado < min: usa min
- Si el valor calculado > max: usa max
- Si esta entre min y max: usa el preferido
============================================ */
/* ANTES: tipografia con media queries (4 reglas) */
h1 {
font-size: 2rem; /* 32px base (mobile) */
}
@media (min-width: 768px) {
h1 { font-size: 2.5rem; } /* 40px */
}
@media (min-width: 1024px) {
h1 { font-size: 3rem; } /* 48px */
}
@media (min-width: 1280px) {
h1 { font-size: 3.5rem; } /* 56px */
}
/* DESPUES: tipografia fluida con clamp() (1 sola regla!) */
h1 {
font-size: clamp(2rem, 5vw, 3.5rem);
/* min: 2rem (32px) — nunca mas chico que esto
preferred: 5vw — escala con el viewport
max: 3.5rem (56px) — nunca mas grande que esto */
}
/* ============================================
Sistema tipografico fluido completo
============================================ */
:root {
/* Tipografia fluida */
--text-xs: clamp(0.75rem, 0.7rem + 0.25vw, 0.875rem);
--text-sm: clamp(0.875rem, 0.8rem + 0.375vw, 1rem);
--text-base: clamp(1rem, 0.9rem + 0.5vw, 1.125rem);
--text-lg: clamp(1.125rem, 1rem + 0.625vw, 1.25rem);
--text-xl: clamp(1.25rem, 1rem + 1.25vw, 1.5rem);
--text-2xl: clamp(1.5rem, 1rem + 2.5vw, 2rem);
--text-3xl: clamp(2rem, 1.5rem + 2.5vw, 3rem);
--text-4xl: clamp(2.5rem, 1.5rem + 5vw, 4rem);
/* Espaciado fluido */
--space-sm: clamp(0.5rem, 0.4rem + 0.5vw, 0.75rem);
--space-md: clamp(1rem, 0.8rem + 1vw, 1.5rem);
--space-lg: clamp(1.5rem, 1rem + 2.5vw, 3rem);
--space-xl: clamp(2rem, 1rem + 5vw, 5rem);
}
body {
font-size: var(--text-base);
line-height: 1.6;
}
h1 { font-size: var(--text-4xl); }
h2 { font-size: var(--text-3xl); }
h3 { font-size: var(--text-2xl); }
h4 { font-size: var(--text-xl); }
/* ============================================
clamp() para layouts (no solo tipografia)
============================================ */
/* Container que escala entre 320px y 1200px */
.fluid-container {
width: clamp(320px, 90%, 1200px);
margin: 0 auto;
}
/* Gap que escala entre 1rem y 2rem */
.fluid-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
gap: clamp(1rem, 2vw, 2rem);
padding: clamp(1rem, 3vw, 3rem);
}
/* Sidebar con ancho fluido */
.sidebar {
width: clamp(200px, 20%, 300px);
/* Min 200px, escala con viewport, max 300px */
}
/* ============================================
Formulas utiles para clamp()
============================================ */
/*
Para calcular el valor "preferred" que escala linealmente:
preferred = min + (max - min) * ((viewport - viewport_min) / (viewport_max - viewport_min))
Ejemplo: quiero que h1 pase de 2rem (a 320px) a 3.5rem (a 1440px)
preferred = 2 + (3.5 - 2) * ((100vw - 320px) / (1440 - 320))
= 2rem + 1.5 * ((100vw - 320px) / 1120px)
Simplificado con la formula comun:
font-size: clamp(2rem, calc(2rem + 1.5 * (100vw - 320px) / 1120), 3.5rem);
O mas simple (aproximacion con vw directo):
font-size: clamp(2rem, 5vw, 3.5rem);
*/
clamp() también funciona con min(), max()
clamp() es en realidad un atajo de max(min, min(preferred, max)). Las funciones min() y max() también se pueden usar solas: font-size: max(1rem, 3vw) garantiza un mínimo de 1rem pero permite crecer con el viewport. width: min(90%, 1200px) limita el ancho al menor entre 90% y 1200px.
Container Queries
Las Container Queries (@container) son una de las adiciones más revolucionarias a CSS en los últimos años. A diferencia de las media queries que responden al tamaño del viewport, las container queries responden al tamaño del contenedor padre. Esto significa que un componente puede adaptarse a su contenedor, no a la pantalla. Una card en el sidebar (estrecho) se ve distinta que la misma card en el main content (ancho), sin necesidad de clases extra ni JavaScript.
El soporte es amplio desde 2023: Chrome 105+, Safari 16+, Firefox 110+. Para navegadores sin soporte, las container queries simplemente se ignoran (graceful degradation), y los estilos base del componente se aplican sin cambios.
/* ============================================
Container Queries: concepto basico
============================================ */
/* Paso 1: Definir el contenedor */
.card-wrapper {
container-type: inline-size; /* El contenedor puede cambiar de tamaño */
container-name: card-container; /* Nombre opcional para referenciarlo */
}
/* Paso 2: Query basada en el contenedor, no el viewport */
@container card-container (min-width: 400px) {
.card {
display: grid;
grid-template-columns: 150px 1fr; /* Layout horizontal */
}
}
/* Si el contenedor mide menos de 400px:
los estilos base se aplican (layout vertical) */
/* ============================================
Ejemplo: card que se adapta a su contenedor
============================================ */
/* Definimos el contenedor */
.post-card-container {
container-type: inline-size;
container-name: post;
}
/* Estilos base: layout vertical (contenedor chico) */
.post-card {
display: flex;
flex-direction: column;
gap: 0.75rem;
padding: 1rem;
}
.post-card .card-image {
width: 100%;
height: 200px;
object-fit: cover;
}
.post-card .card-title {
font-size: 1.125rem;
}
/* Cuando el contenedor es >= 400px: layout horizontal */
@container post (min-width: 400px) {
.post-card {
flex-direction: row;
align-items: center;
}
.post-card .card-image {
width: 150px;
height: 150px;
flex-shrink: 0;
}
.post-card .card-title {
font-size: 1.25rem;
}
}
/* Cuando el contenedor es >= 700px: layout con sidebar de info */
@container post (min-width: 700px) {
.post-card .card-image {
width: 250px;
height: 200px;
}
.post-card .card-meta {
display: flex;
gap: 1rem;
}
}
/* ============================================
Container Query Units (cqw, cqh)
============================================ */
/* Unidades relativas al contenedor:
1cqw = 1% del ancho del contenedor
1cqh = 1% de la altura del contenedor */
.card-wrapper {
container-type: inline-size;
}
.card-wrapper .card-title {
font-size: clamp(1rem, 3cqw, 1.5rem);
/* Escala con el ancho del contenedor, no del viewport */
}
/* ============================================
container-type: inline-size vs size
============================================ */
/* inline-size: el contenedor responde al ancho (el mas comun)
Solo tracking de ancho, menor impacto en performance */
/* size: responde al ancho Y la altura
Menos comun, puede causar loops de layout */
| Feature | Media Queries | Container Queries |
|---|---|---|
| Responde a | Tamaño del viewport (pantalla) | Tamaño del contenedor padre |
| Componentes | Todos los componentes del mismo tipo se ven iguales | Cada instancia se adapta a su contexto |
| Reusabilidad | Baja: necesitás clases distintas para cada contexto | Alta: el componente se adapta automáticamente |
| Sintaxis | @media (min-width: 768px) |
@container (min-width: 400px) |
| Setup | Ninguno | container-type: inline-size en el padre |
| Soporte | Universal (todos los navegadores) | Chrome 105+, Safari 16+, Firefox 110+ |
Imágenes responsivas: picture y srcset
Servir la misma imagen de 2000px a un mobile de 375px desperdicia ancho de banda y empeora el tiempo de carga. Las imágenes responsivas permiten servir diferentes versiones de una imagen según el tamaño de la pantalla, la densidad de píxeles o las condiciones del navegador. HTML proporciona dos herramientas para esto: <img srcset> para variaciones de la misma imagen (resoluciones distintas) y <picture> para imágenes completamente diferentes (recortes, formatos distintos, art direction).
srcset con descriptores de ancho
El atributo srcset en <img> permite definir múltiples versiones de la misma imagen con diferentes anchos. El navegador elige automáticamente la mejor opción basándose en el ancho del viewport y la densidad de píxeles. Usá descriptores de ancho (w) junto con sizes para dar contexto al navegador sobre cuánto espacio ocupará la imagen en cada breakpoint.
<!-- ============================================
srcset con descriptores de ancho (w)
El navegador elige la mejor imagen
============================================ -->
<!-- sizes: dice al navegador cuanto espacio ocupa la imagen
en diferentes breakpoints -->
<!-- srcset: lista de imagenes y sus anchos reales -->
<img
src="hero-800.jpg"
srcset="hero-400.jpg 400w,
hero-800.jpg 800w,
hero-1200.jpg 1200w,
hero-1600.jpg 1600w"
sizes="(max-width: 640px) 100vw,
(max-width: 1024px) 50vw,
33vw"
alt="Hero image responsiva"
>
<!-- Logica del navegador:
- En mobile (<=640px): la imagen ocupa 100vw
Si la pantalla es 375px: 375px, elige hero-400.jpg
- En tablet (641-1024px): la imagen ocupa 50vw
Si la pantalla es 768px: 384px, elige hero-400.jpg
- En desktop (>1024px): la imagen ocupa 33vw
Si la pantalla es 1440px: 475px, elige hero-800.jpg
- En un Retina 2x desktop: necesitaria 950px, elige hero-1200.jpg -->
<!-- ============================================
srcset con descriptores de densidad (x)
Mas simple, para densidades de pixeles fijas
============================================ -->
<img
src="logo.png"
srcset="[email protected] 1x,
[email protected] 2x,
[email protected] 3x"
alt="Logo"
>
<!-- 1x: pantalla normal, 2x: Retina, 3x: Retina HD -->
picture para art direction
<picture> permite servir imágenes completamente diferentes según condiciones del navegador. Es ideal para art direction: donde no solo cambia la resolución sino el recorte o la composición de la imagen. Por ejemplo, en mobile querés un recorte cuadrado centrado en el sujeto, y en desktop un panorama amplio. <picture> también permite servir formatos modernos (WebP, AVIF) con fallback a JPEG/PNG.
<!-- ============================================
picture: art direction
Diferente recorte para mobile y desktop
============================================ -->
<picture>
<!-- Primero: condiciones especificas (media queries) -->
<source
media="(max-width: 640px)"
srcset="hero-cuadrado-mobile.webp"
type="image/webp"
>
<source
media="(max-width: 640px)"
srcset="hero-cuadrado-mobile.jpg"
>
<source
media="(min-width: 641px)"
srcset="hero-panoramica-desktop.webp"
type="image/webp"
>
<source
media="(min-width: 641px)"
srcset="hero-panoramica-desktop.jpg"
>
<!-- Fallback: la imagen que se muestra si no hay soporte -->
<img
src="hero-fallback.jpg"
alt="Imagen hero responsiva"
>
</picture>
<!-- ============================================
picture: formato moderno con fallback
AVIF > WebP > JPEG
============================================ -->
<picture>
<source srcset="photo.avif" type="image/avif">
<source srcset="photo.webp" type="image/webp">
<img src="photo.jpg" alt="Foto optimizada">
</picture>
<!-- El navegador usa la primera que soporte:
AVIF (mejor compresion) > WebP > JPEG (fallback) -->
/* ============================================
Imagenes responsivas con CSS
============================================ */
/* Imagen que siempre llena su contenedor */
img.responsive {
max-width: 100%; /* Nunca mas ancha que su contenedor */
height: auto; /* Mantiene la proporcion */
display: block; /* Elimina el espacio bajo la imagen */
}
/* Background images responsivas */
.hero {
background-image: url("hero-mobile.jpg");
background-size: cover;
background-position: center;
min-height: 300px;
}
@media (min-width: 768px) {
.hero {
background-image: url("hero-desktop.jpg");
min-height: 500px;
}
}
/* Object-fit: como la imagen llena su contenedor */
.image-cover {
width: 100%;
height: 100%;
object-fit: cover; /* Recorta para llenar (como background-size: cover) */
/* object-fit: contain — Muestra toda la imagen, puede dejar barras
object-fit: fill — Estira para llenar (puede distorsionar)
object-fit: none — Tamanio original
object-fit: scale-down — El menor entre none y contain */
}
/* object-position: controla el punto focal del recorte */
.image-cover-focus {
width: 100%;
height: 300px;
object-fit: cover;
object-position: center top; /* Enfoca la parte superior */
/* object-position: 50% 0; — Igual que center top */
}
Media queries de preferencias del usuario
Las media queries de preferencias permiten respetar la configuración del sistema operativo o del navegador del usuario. Esto es fundamental para la accesibilidad: si un usuario activó el modo oscuro, prefiere movimiento reducido, o tiene contraste alto configurado, tu sitio debería respetar esas preferencias automáticamente. No es opcional—es parte de un buen desarrollo web responsable.
/* ============================================
prefers-color-scheme: modo oscuro/claro
============================================ */
/* Detecta la preferencia del sistema operativo */
@media (prefers-color-scheme: dark) {
:root {
--bg: #0d1117;
--fg: #e6edf3;
--surface: #161b22;
--border-color: #30363d;
}
}
@media (prefers-color-scheme: light) {
:root {
--bg: #ffffff;
--fg: #1f2937;
--surface: #f3f4f6;
--border-color: #d1d5db;
}
}
/* ============================================
prefers-reduced-motion: reducir animaciones
IMPORTANTE para accesibilidad (vestibular disorders)
============================================ */
/* Si el usuario prefiere menos movimiento:
desactiva todas las animaciones y transiciones */
@media (prefers-reduced-motion: reduce) {
*,
*::before,
*::after {
animation-duration: 0.01ms !important;
animation-iteration-count: 1 !important;
transition-duration: 0.01ms !important;
scroll-behavior: auto !important;
}
/* Pero NO desactives:
- Transiciones de opacity para mostrar/ocultar (no marean)
- Cambios de color que no involucran movimiento */
}
/* ============================================
prefers-contrast: contraste del usuario
============================================ */
@media (prefers-contrast: more) {
:root {
--border-color: #000000; /* Bordes mas visibles */
--fg: #000000; /* Texto mas oscuro */
}
a {
text-decoration: underline; /* Links siempre subrayados */
}
.btn {
border: 2px solid currentColor; /* Bordes claros en botones */
}
}
@media (prefers-contrast: less) {
:root {
--border-color: #e5e7eb; /* Bordes mas sutiles */
}
}
/* ============================================
prefers-color-scheme + forced-colors (Windows High Contrast)
============================================ */
@media (forced-colors: active) {
/* El usuario tiene Modo de Alto Contraste de Windows activado
Los colores del sistema reemplazan los del CSS */
.btn {
border: 1px solid ButtonText; /* Usa colores del sistema */
background: ButtonFace;
color: ButtonText;
}
}
/* ============================================
Combinar con otras media queries
============================================ */
/* Mobile + modo oscuro + sin animaciones */
@media (max-width: 640px) and (prefers-color-scheme: dark) and (prefers-reduced-motion: reduce) {
.hero {
/* Layout simplificado para este escenario */
padding: 1rem;
animation: none;
}
}
| Media query | Qué detecta | Acción recomendada |
|---|---|---|
prefers-color-scheme |
Modo oscuro o claro del sistema. | Alternar las custom properties del tema (colores, fondos, bordes). |
prefers-reduced-motion |
El usuario prefiere menos movimiento (accesibilidad vestibular). | Desactivar animaciones, transiciones, parallax y scroll behaviors. |
prefers-contrast |
Nivel de contraste preferido (more, less, custom). | Aumentar bordes, subrayar links, usar colores de alto contraste. |
prefers-color-gamut |
Gama de color del display (srgb, p3, rec2020). | Servir colores HDR cuando el display lo soporta. |
forced-colors |
Modo de Alto Contraste de Windows está activo. | Usar colores del sistema (ButtonText, ButtonFace, Canvas, etc.). |
prefers-reduced-data |
El usuario prefiere menos consumo de datos (experimental). | No cargar videos autoplay, reducir resolución de imágenes. |
prefers-reduced-motion no es opcional
Approximadamente el 5% de los usuarios tiene algún tipo de trastorno vestibular (mareos, vértigo) que se agrava con animaciones en pantalla. Ignorar prefers-reduced-motion puede hacer que tu sitio sea literalmente inusable para estas personas. Implementálo como un reset global en tu CSS base, no como un afterthought. Las animaciones decorativas se desactivan, pero las transiciones funcionales (mostrar/ocultar) pueden mantenerse.
Buenas prácticas y herramientas
El responsive design no es solo agregar media queries—es una filosofía de desarrollo que abarca desde las unidades que elegís hasta cómo probás tu sitio. Estas son las buenas prácticas que hacen la diferencia entre un sitio "funciona en mobile" y un sitio que está verdaderamente optimizado para cualquier dispositivo.
/* ============================================
1. SIEMPRE incluye el viewport meta tag
============================================ */
/* <meta name="viewport" content="width=device-width, initial-scale=1.0">
Sin esto, los navegadores mobile renderizan la pagina
a 980px de ancho y la achican, haciendo que todo sea tiny. */
/* ============================================
2. Evita anchos fijos en elementos
============================================ */
/* MAL: ancho fijo */
.container-bad {
width: 1200px;
}
/* BIEN: max-width con auto margins */
.container-good {
max-width: 1200px; /* No supera 1200px */
width: 100%; /* Pero puede ser menor */
margin: 0 auto; /* Centrado */
}
/* ============================================
3. Usa overflow-wrap para texto largo
============================================ */
.long-text {
overflow-wrap: break-word; /* Parte palabras largas */
word-break: break-word; /* Fallback para browsers antiguos */
hyphens: auto; /* Guiones automaticos si el idioma lo permite */
}
/* ============================================
4. Imagenes siempre responsivas
============================================ */
img, video, svg {
max-width: 100%;
height: auto;
}
/* ============================================
5. Tablas responsivas: wrapper con overflow
============================================ */
.table-wrapper {
overflow-x: auto; /* Scroll horizontal si la tabla es muy ancha */
-webkit-overflow-scrolling: touch; /* Scroll suave en iOS */
}
/* ============================================
6. Evita el scroll horizontal no deseado
============================================ */
*, *::before, *::after {
box-sizing: border-box;
/* El padding no suma al ancho total */
}
html, body {
overflow-x: hidden; /* Previene scroll horizontal */
/* Pero CUIDADO: esto oculta el problema, no lo arregla
Es mejor encontrar el elemento que causa el overflow */
}
/* ============================================
7. Touch targets: minimo 44x44px
============================================ */
/* Los botones y links en mobile deben ser grandes
enough para ser tocados con el dedo (WCAG) */
.btn, a, button, input[type="checkbox"],
input[type="radio"], .nav-link {
min-height: 44px;
min-width: 44px;
/* Espacio entre targets: minimo 8px */
}
| Práctica | Por qué importa | Cómo implementarla |
|---|---|---|
| Viewport meta tag | Sin él, mobile renderiza a 980px y escala. | <meta name="viewport" content="width=device-width, initial-scale=1.0"> en todo proyecto. |
| Mobile-first | CSS más limpio, progresivo, mejor performance en mobile. | Estilos base para mobile, min-width para breakpoints mayores. |
| Unidades relativas | Layout que escala sin media queries adicionales. | rem para tipografía/espaciado, % para anchos, clamp() para valores fluidos. |
| max-width en vez de width | Previene overflow horizontal en pantallas chicas. | max-width: 1200px; width: 100%; en containers. |
| Touch targets ≥ 44px | WCAG: los botones deben ser tocables con el dedo. | min-height: 44px en botones, links, inputs. |
| prefers-reduced-motion | Accesibilidad: usuarios con vestibular disorders. | Reset global: desactivar animaciones/transiciones. |
| Imágenes responsivas | Ahorra ancho de banda, mejora tiempo de carga en mobile. | srcset para resoluciones, <picture> para art direction. |
Chrome DevTools para responsive
Presioná Ctrl+Shift+M (o Cmd+Shift+M en Mac) para abrir el modo responsive de DevTools. Podés simular diferentes dispositivos, probar breakpoints en tiempo real, y ver cómo se reorganiza el layout. También podés usar Ctrl+Shift+I > click en el ícono del dispositivo en la toolbar. Herramientas como Responsively App (open source) muestran múltiples viewports simultáneamente.