Módulo 6: Responsive And Dark Mode
Los prefijos responsive y el enfoque mobile-first
Descripción
Una utilidad de Tailwind sin prefijo —p-4, text-lg, grid-cols-1— aplica siempre, en cualquier ancho de pantalla. Un prefijo responsive —md:p-4, lg:text-lg— hace que esa utilidad aplique solo a partir de cierto ancho. Esa es toda la mecánica, y de ella sale la idea central de esta lección: en Tailwind, las clases sin prefijo son tu base móvil, y los prefijos agregan estilos hacia arriba, a pantallas más grandes. No diseñas para desktop y quitas cosas para el teléfono; diseñas para el teléfono y sumas cuando hay espacio. Eso es mobile-first.
p-2 md:p-4 lg:p-6 no son tres decisiones que compiten: son una cascada. p-2 es el padding base —el del móvil, y el de todo ancho por debajo del primer breakpoint—. md:p-4 dice "a partir de 768px, sube el padding a p-4". lg:p-6 dice "a partir de 1024px, súbelo a p-6". El navegador aplica la que corresponda al ancho actual, y como los prefijos son de min-width (a partir de), la más grande que se cumpla es la que gana. Móvil recibe p-2, tablet p-4, desktop p-6 —con una sola cadena de clases, no tres componentes—.
Conexión con el módulo. Esta lección abre el primer mecanismo del módulo 6: los prefijos responsive. Es el cimiento de todo lo que sigue —la lección 3 los organiza como una escala de breakpoints, y desde la lección 4 se combinan con dark:—. Se apoya en web-fundamentals-html-css módulo 7 ("Responsive design"), donde aprendiste el viewport, las media queries y el enfoque mobile-first a mano: aquí no re-enseñamos eso; vemos cómo Tailwind lo escribe con un prefijo, y por qué la cadena p-2 md:p-4 lg:p-6 es exactamente la media query de min-width que escribías con @media. La resolución de qué prefijo queda activo se ejecuta en Node; el JSX real se muestra.
Una analogía: la casa base y los cuartos que se agregan cuando hay terreno
Vuelve a la casa del módulo. Cuando construyes con poco terreno —un lote chico—, levantas lo esencial: recámara, cocina, baño. No es una casa "incompleta"; es una casa que funciona en el espacio que hay. Si más adelante consigues terreno alrededor, no derrumbas nada: le agregas cuartos hacia afuera. Con algo de terreno extra, un estudio. Con mucho, además una sala grande. Cada ampliación suma sobre la casa base, y la base sigue sirviendo tal cual para quien tenga el lote chico.
Tu class="" es esa casa. La versión sin prefijo es la casa base: la que aplica siempre, empezando por el teléfono (el lote chico). Los prefijos son los cuartos que se agregan cuando hay terreno: md: suma estilos cuando hay al menos 768px de ancho (algo de terreno), lg: suma más cuando hay 1024px (mucho terreno). p-2 md:p-4 lg:p-6 es "el padding base es p-2; cuando haya terreno mediano, súbelo a p-4; cuando haya bastante, a p-6".
La analogía tiene una moraleja que es exactamente la técnica de esta lección: construyes la casa base primero y agregas hacia arriba, no al revés. Si diseñaras la mansión de desktop primero y luego intentaras "quitarle cuartos" para que quepa en el lote chico, cada teléfono sería una excepción remendada —paredes tapiadas, cuartos que estorban—. Construir la base móvil y ampliar cuando hay espacio deja el móvil limpio (es la base, no un recorte) y el desktop como una suma natural. Eso es mobile-first: la base es el móvil, y los prefijos agregan hacia arriba.
El mecanismo: un prefijo es un min-width
Recuerda lo que aprendiste en web-fundamentals módulo 7: una media query de min-width aplica sus reglas a partir de cierto ancho:
/* CSS a mano (web-fundamentals modulo 7): el padding sube en pantallas medianas */
.card { padding: 0.5rem; } /* base: aplica siempre, empezando por movil */
@media (min-width: 768px) {
.card { padding: 1rem; } /* a partir de 768px, sobreescribe */
}
Tailwind escribe exactamente esto con un prefijo. md:p-4 genera, por debajo, la regla p-4 metida dentro de @media (min-width: 768px). Escrito en el marcado, la casa base y sus ampliaciones caben en un solo atributo:
<!-- el padding del product-card sube con el ancho: una sola class="" -->
<article class="p-2 md:p-4 lg:p-6">...</article>
La clave de por qué esto es mobile-first está en la palabra min-width. Los prefijos de Tailwind son todos de mínimo ancho: md: es "a partir de 768px", no "hasta 768px". Por eso la versión sin prefijo (p-2) es la base que cubre desde 0px hacia arriba, y cada prefijo la reemplaza a partir de su breakpoint. Un teléfono de 360px no cumple ningún breakpoint, así que se queda con la base. Un desktop de 1200px cumple md (768) y lg (1024), y como las dos son "a partir de", gana la de mayor ancho que se cumpla: lg:p-6.
Compara esto con el error inverso. Si escribieras la base como el estilo de desktop y usaras prefijos de máximo ancho para "arreglar" el móvil, tendrías una cascada que deshace la base en cada pantalla chica —más difícil de leer y de mantener—. Tailwind te empuja a min-width precisamente para que la base sea móvil y los prefijos solo agreguen. Diseñas hacia arriba, no hacia abajo.
Ejemplo trabajado: qué padding queda activo en cada ancho
Vamos a medir la cascada. Tomamos la cadena p-2 md:p-4 lg:p-6 y, para tres anchos —un teléfono (360px), un tablet (768px) y un desktop (1200px)—, resolvemos qué clase de padding queda activa. resolveClasses es el modelo pedagógico del cascade de prefijos: parsea cada clase en su breakpoint, filtra las que el ancho cumple, y para cada propiedad se queda con la de mayor breakpoint (la regla min-width, "la más grande que se cumpla gana"):
// L2 — el cascade de prefijos responsive de Tailwind, mobile-first (modelo pedagogico).
const BREAKPOINTS = { sm: 640, md: 768, lg: 1024, xl: 1280 };
function propertyOf(base) {
if (['flex', 'inline-flex', 'grid', 'block'].includes(base)) return 'display';
if (base.startsWith('grid-cols-')) return 'grid-cols';
if (base.startsWith('gap-')) return 'gap';
if (base.startsWith('p-')) return 'padding';
if (base.startsWith('bg-')) return 'background';
if (/^text-(xs|sm|base|lg|xl|2xl|3xl)$/.test(base)) return 'font-size';
if (base.startsWith('text-')) return 'text-color';
return base;
}
function parseClass(raw) {
const parts = raw.split(':');
const base = parts.pop();
let bp = null, dark = false;
for (const p of parts) { if (p in BREAKPOINTS) bp = p; else if (p === 'dark') dark = true; }
return { raw, base, bp, dark };
}
function resolveClasses(classList, { viewport, theme }) {
const parsed = classList.split(/\s+/).filter(Boolean).map(parseClass);
const active = parsed.filter(c =>
(c.bp === null || viewport >= BREAKPOINTS[c.bp]) && (!c.dark || theme === 'dark'));
const winners = new Map();
for (const c of active) {
const prop = propertyOf(c.base);
const score = (c.bp ? BREAKPOINTS[c.bp] : 0) * 2 + (c.dark ? 1 : 0);
const cur = winners.get(prop);
if (!cur || score > cur.score) winners.set(prop, { raw: c.raw, score });
}
const keep = new Set([...winners.values()].map(w => w.raw));
return parsed.filter(c => keep.has(c.raw)).map(c => c.raw);
}
// el padding del product-card: base = movil, los prefijos agregan aire hacia arriba.
const padding = 'p-2 md:p-4 lg:p-6';
console.log('=== mobile-first: la base es movil, los prefijos suman hacia arriba ===\n');
console.log('cadena: "' + padding + '"\n');
for (const viewport of [360, 768, 1200]) {
const active = resolveClasses(padding, { viewport, theme: 'light' });
console.log(`viewport ${String(viewport).padStart(4)}px -> ${active.join(' ')}`);
}
Qué esperar. Al correr el archivo con Node, la salida es exactamente esta:
=== mobile-first: la base es movil, los prefijos suman hacia arriba ===
cadena: "p-2 md:p-4 lg:p-6"
viewport 360px -> p-2
viewport 768px -> md:p-4
viewport 1200px -> lg:p-6
Lee las tres líneas como la cascada en acción. A 360px (un teléfono), la única clase activa es p-2 —la base—: ni md: (768) ni lg: (1024) se cumplen a ese ancho, así que el móvil se queda con el padding base. A 768px (un tablet), se cumple md (768 >= 768) pero no lg, y como md:p-4 es un min-width mayor que la base, gana: el padding sube a p-4. A 1200px (un desktop), se cumplen tanto md (768) como lg (1024), y de las dos que se cumplen gana la de mayor breakpoint: lg:p-6. El padding creció de p-2 a p-4 a p-6 conforme apareció espacio, con una sola cadena de clases y sin que tú "quitaras" nada en ningún lado.
Fíjate en lo que el modelo hace y que es el corazón de mobile-first: para la propiedad padding hay tres candidatas, y para cada ancho se queda con una —la de mayor breakpoint que el ancho cumple—. Eso es lo que el navegador hace por debajo con las media queries de min-width: las reglas dentro de @media (min-width: 768px) y @media (min-width: 1024px) se acumulan, y la última que aplica (la de mayor ancho) sobreescribe a las anteriores para esa propiedad. La base (p-2, sin @media) siempre aplica; los prefijos la reemplazan hacia arriba.
Una pregunta para razonar el enfoque: ¿qué pasaría si un teléfono muy angosto midiera 320px? (Nada nuevo: sigue sin cumplir ningún breakpoint, así que se queda con la base p-2. Ese es el punto de mobile-first —la base cubre todo ancho por debajo del primer breakpoint, por chico que sea, sin que tengas que preverlo—. Si hubieras diseñado desktop-first, cada ancho no previsto sería una posible ruptura; con base móvil, el ancho más chico es simplemente la base.)
Profundización: la base no lleva prefijo, y por qué eso importa
El detalle que más se confunde al empezar es querer poner un prefijo a la versión móvil. No existe un prefijo "para móvil" en Tailwind —no escribes sm:p-2 pensando "el padding chico del teléfono"—. La versión móvil es la que va sin prefijo. sm: no significa "small screens" en el sentido de "pantallas chicas": significa "a partir de 640px", que ya es un tablet chico. Todos los prefijos apuntan hacia arriba; el móvil es la ausencia de prefijo.
Esto tiene una consecuencia práctica: cuando lees una cadena bien escrita, la primera versión de cada propiedad es siempre la móvil, y los prefijos cuentan la historia de cómo esa propiedad crece con la pantalla. text-sm md:text-base lg:text-lg se lee "texto chico en el teléfono, normal en tablet, grande en desktop". flex-col md:flex-row se lee "apilado en vertical en el móvil, en fila cuando hay ancho". La cadena es una narración de menor a mayor, y su punto de partida —lo sin prefijo— es siempre el caso más restringido.
Y conecta con lo que ya sabes del sistema. En web-fundamentals módulo 7 viste que mobile-first no es solo una técnica de CSS, es una disciplina de diseño: empezar por la pantalla con menos espacio te obliga a decidir qué es esencial (lo que cabe en el lote chico) antes de agregar lo deseable (los cuartos extra). Tailwind codifica esa disciplina en su sintaxis: la base es lo esencial, los prefijos son lo que agregas cuando hay espacio. No es casualidad que la herramienta empuje al mismo hábito que la buena práctica de CSS.
Errores comunes
Escribir desktop-first y pelear la cascada con prefijos de máximo ancho. Qué pasa: se pone el estilo de desktop como base (grid-cols-4) y luego se "arregla" el móvil con max-lg:grid-cols-1. Por qué pasa: uno diseña mirando su monitor grande, así que lo natural es escribir primero lo que ve. Cómo detectarlo: tu class="" tiene overrides max-* que deshacen la base, y el móvil se siente como una excepción, no como el punto de partida. Cómo corregirlo: invierte la dirección —la base es el caso móvil (grid-cols-1), y los prefijos de min-width agregan hacia arriba (md:grid-cols-4)—. Tailwind soporta max-*, pero como herramienta excepcional, no como la forma normal de escribir responsive. Si tu cadena está llena de max-*, diseñaste al revés. El fondo de por qué min-width se lee mejor está en web-fundamentals módulo 7.
Poner un prefijo a la versión móvil. Qué pasa: se escribe sm:p-2 md:p-4 creyendo que sm: es "el padding del móvil". Por qué pasa: sm suena a "small", y "small" suena a teléfono. Cómo detectarlo: en un teléfono de 360px, el padding no aparece —porque sm: empieza a 640px, y 360 no lo cumple—. Cómo corregirlo: la versión móvil va sin prefijo (p-2 md:p-4). sm: es "a partir de 640px" (tablet chico), no "para pantallas chicas". El móvil es la base, no un breakpoint.
Repetir la misma propiedad sin que haya diferencia. Qué pasa: se escribe p-4 md:p-4 lg:p-4 —la misma clase en cada breakpoint—. Por qué pasa: uno cree que "hay que declarar cada tamaño". Cómo detectarlo: los prefijos repiten el valor de la base sin cambiarlo. Cómo corregirlo: si una propiedad no cambia con el ancho, va una sola vez, sin prefijo (p-4). Los prefijos son para cuando el valor cambia; repetir la base infla la cadena sin efecto. Escribe solo los puntos donde algo crece.
Ejercicios
Ejercicio 1 — Lee la cascada. Con el resolveClasses del ejemplo, sin correr nada, di qué clase de la cadena text-sm md:text-base lg:text-lg queda activa en cada ancho:
- (a) 500px
- (b) 800px
- (c) 1300px
Ver solución
Recordando los breakpoints (sm 640, md 768, lg 1024):
| viewport | activa | por qué |
|---|---|---|
| 500px | text-sm | no cumple ningún breakpoint (500 < 640); se queda con la base |
| 800px | md:text-base | cumple md (768) pero no lg (1024); gana el min-width mayor que se cumple |
| 1300px | lg:text-lg | cumple md y lg; de los dos, gana lg (1024), el mayor |
La lección: la base (text-sm) cubre todo ancho por debajo del primer breakpoint que aparece en la cadena, y de ahí cada prefijo la reemplaza a partir de su min-width. El texto crece con la pantalla, con una sola cadena.
Ejercicio 2 — Reescribe desktop-first a mobile-first. Alguien escribió el layout de una lista así, pensando en desktop primero:
<ul class="flex-row max-md:flex-col">...</ul>
Reescríbelo mobile-first (base móvil, prefijo hacia arriba) para que en el teléfono la lista sea vertical y en pantallas medianas hacia arriba sea horizontal.
Ver solución
<ul class="flex-col md:flex-row">...</ul>
La base es el caso móvil —flex-col (apilado en vertical)—, y el prefijo agrega hacia arriba —md:flex-row (en fila a partir de 768px)—. La versión original usaba max-md: para "arreglar" el móvil sobre una base de desktop; la mobile-first pone el móvil como base y agrega el layout de escritorio cuando hay ancho. Se lee de menor a mayor: "vertical en el teléfono, horizontal cuando hay espacio". Cero max-*, cero pelea con la cascada.
Ejercicio 3 — Detecta la propiedad que no cambia. En esta cadena, una de las propiedades está declarada en cada breakpoint sin cambiar de valor. Identifícala y di cómo simplificar la cadena:
rounded-md md:rounded-md lg:rounded-md p-2 md:p-4 lg:p-6
Ver solución
La propiedad que no cambia es el radio de borde: rounded-md md:rounded-md lg:rounded-md repite el mismo valor en los tres breakpoints. Como no cambia con el ancho, va una sola vez, sin prefijo. El padding sí cambia (p-2 → p-4 → p-6), así que ese se queda. La cadena simplificada:
rounded-md p-2 md:p-4 lg:p-6
La lección: los prefijos son para los puntos donde una propiedad crece con la pantalla. Una propiedad constante va una vez, sin prefijo —repetirla en cada breakpoint infla la cadena y no cambia nada—. Escribe solo las transiciones reales.
Resumen y siguiente paso
En esta lección viste el primer mecanismo del módulo: las clases sin prefijo son tu base móvil, y los prefijos responsive (sm:/md:/lg:) agregan estilos hacia arriba, a pantallas más grandes. Con la casa base y los cuartos que se agregan cuando hay terreno entendiste el principio de mobile-first: construyes lo esencial para el lote chico (el móvil, sin prefijo) y amplías cuando hay espacio (los prefijos, de min-width), en vez de diseñar la mansión y recortarla. Y lo comprobaste ejecutando: la cadena p-2 md:p-4 lg:p-6 resolvió a p-2 en el teléfono, p-4 en el tablet y p-6 en el desktop —una propiedad que crece con el ancho, con una sola cadena y sin quitar nada—.
Antes de avanzar deberías poder: explicar por qué la versión móvil va sin prefijo; leer una cadena responsive de menor a mayor; y decir por qué min-width (agregar hacia arriba) es preferible a max-width (recortar hacia abajo).
La lección 3 organiza esos prefijos como lo que son: una escala de breakpoints compartida por todo el sistema. sm, md, lg, xl no son valores sueltos que eliges por componente; son un juego de anchos —como la escala de espaciado del módulo 4— que garantiza que todo el storefront quiebre en los mismos puntos. Vas a ver esa escala, cómo elegir dónde poner los quiebres, y cómo el catálogo de Mercado pasa de 1 a 2 a 4 columnas usando el mismo set de breakpoints en todos lados.
Recursos
- Tailwind CSS, "Responsive Design" — tailwindcss.com/docs/responsive-design. Los prefijos
sm:/md:/lg:, el enfoque mobile-first ("Working mobile-first") y por qué la versión sin prefijo es la base. En inglés. - MDN, "Media queries" — developer.mozilla.org/en-US/docs/Web/CSS/CSS_media_queries/Using_media_queries. El
@media (min-width: ...)que Tailwind genera por debajo de cada prefijo; el mecanismo del navegador. En inglés. - web.dev, "Responsive web design basics" — web.dev/articles/responsive-web-design-basics. El fundamento de mobile-first y por qué diseñar hacia arriba; el prerequisito de web-fundamentals módulo 7. En inglés.