Media queries
Lección 20 de 37 · 3 min
Una media query aplica estilos solo cuando se cumple una condición: que la pantalla tenga cierto ancho, que quien lee use tema oscuro o que prefiera menos movimiento.
Qué es una media query
Una media query (@media) envuelve reglas que solo valen si su condición es verdadera. La más común pregunta por el ancho del viewport, el área visible de la página.
Lo recomendado es mobile-first: escribí primero los estilos para pantallas chicas, sin ninguna media query, y sumá cambios a medida que hay más lugar. Cada media query pide un ancho mínimo, con min-width o con la sintaxis de rango (width >= 640px). Las dos dicen lo mismo, 640px incluido, pero la de rango se lee mejor y funciona en todos los navegadores actuales.
.tarjetas {display: grid;gap: 16px;}@media (min-width: 640px) {.tarjetas { grid-template-columns: repeat(2, 1fr); }}/* lo mismo, con sintaxis de rango */@media (width >= 640px) {.tarjetas { grid-template-columns: repeat(2, 1fr); }}
Mové el ancho del viewport y mirá cuándo la grilla pasa de 1 a 2 y a 3 columnas. Desde 1024px se cumplen las dos media queries, y gana la última porque viene después en el archivo.
.pagina {background: #fff;color: #111;}.programa {display: grid;grid-template-columns: repeat(1, 1fr);gap: 16px;}@media (width >= 640px) {.programa {grid-template-columns: repeat(2, 1fr);}}@media (width >= 1024px) {.programa {grid-template-columns: repeat(3, 1fr);}}@media (prefers-color-scheme: dark) {.pagina {background: #111;color: #eee;}}@media (prefers-reduced-motion: no-preference) {.indicador {animation: girar 1s linear infinite;}}@keyframes girar {to {rotate: 360deg;}}
<body class="bg-white text-neutral-900 dark:bg-neutral-950 dark:text-neutral-100"><span class="size-4 rounded-full border-2 border-current border-t-transparent motion-safe:animate-spin"></span><div class="grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3"><section>Viernes 14</section><section>Sábado 15</section><section>Domingo 16</section></div></body>
Preferencias de quien lee
Las media queries también leen ajustes del sistema o del navegador. Las más útiles:
- Tema
prefers-color-schemelightodark, según el tema del sistema. Este sitio la usa: cambiá el tema de tu sistema y la página pasa de claro a oscuro. - Movimiento
prefers-reduced-motionValereducecuando la persona pidió menos movimiento, yno-preferencesi no pidió nada. - Contraste
prefers-contrastmoreolesscuando pidió más o menos contraste. - Puntero
hover · pointerQué puede hacer el dispositivo de entrada principal:(hover: hover)si puede pasar el cursor por encima, y(pointer: coarse)si apunta con poca precisión, como un dedo.
@media (prefers-color-scheme: dark) {.pagina { background: #111; color: #eee; }}@media (prefers-reduced-motion: no-preference) {.indicador { animation: girar 1s linear infinite; }}@media (pointer: coarse) {button { min-height: 44px; }}
Poné la animación adentro de no-preference en vez de sacarla con reduce: si un navegador no conoce la consulta, la condición da falso y no anima.
Cómo elegir los breakpoints
Un breakpoint es el ancho donde cambia el diseño. No hay una lista correcta de tamaños de dispositivos, y cambia cada año. Achicá la ventana hasta que algo se rompa (líneas muy largas, columnas apretadas) y poné el breakpoint ahí: decide el contenido, no el modelo de teléfono.
Dentro de una media query, em y rem no dependen del CSS de la página: se calculan con el tamaño de letra inicial del navegador, 16px por defecto. Con (width >= 40em), si alguien sube ese tamaño a 20px, el breakpoint pasa de 640px a 800px y el diseño cambia cuando el texto más grande ya no entra. Con el zoom no hay diferencia: el viewport medido en px también se achica, así que los breakpoints en px reaccionan igual.
Una media query mira la ventana entera. Si un componente puede ir en una barra lateral angosta o en el área principal, lo que importa es el lugar que le dan: para eso están las container queries, el tema de la próxima lección.
En Tailwind
En Tailwind cada media query es una variante: un prefijo delante de la clase. Las de ancho son mobile-first, igual que el demo: sm: aplica desde 40rem (640px) y lg: desde 64rem (1024px).
| CSS | Tailwind |
|---|---|
| (width >= 640px) · 768px · 1024px | sm: · md: · lg: |
| (width < 640px) | max-sm: |
| (width >= 900px) | min-[900px]: |
| prefers-color-scheme: dark | dark: |
| prefers-reduced-motion: no-preference · reduce | motion-safe: · motion-reduce: |
| prefers-contrast: more · pointer: coarse | contrast-more: · pointer-coarse: |
<body class="bg-white dark:bg-neutral-950 dark:text-neutral-100"><span class="size-4 rounded-full border-2 motion-safe:animate-spin"></span><div class="grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3"><article>…</article></div><button class="hover:bg-neutral-100 pointer-coarse:min-h-11">Enviar</button></body>
Media queries
Lesson 20 of 37 · 3 min
A media query applies styles only when a condition holds: the screen is a certain width, the reader uses a dark theme, or they prefer less motion.
What a media query is
A media query (@media) wraps rules that only apply when its condition is true. The most common one asks about the width of the viewport, the visible area of the page.
The recommended approach is mobile-first: write the styles for small screens first, with no media query, and add changes as more room appears. Each media query asks for a minimum width, with min-width or with the range syntax (width >= 640px). Both mean the same, 640px included, but the range syntax reads better and works in every current browser.
.cards {display: grid;gap: 16px;}@media (min-width: 640px) {.cards { grid-template-columns: repeat(2, 1fr); }}/* the same, with range syntax */@media (width >= 640px) {.cards { grid-template-columns: repeat(2, 1fr); }}
Move the viewport width and watch when the grid goes from 1 to 2 and to 3 columns. From 1024px on, both media queries match, and the last one wins because it comes later in the file.
.page {background: #fff;color: #111;}.programme {display: grid;grid-template-columns: repeat(1, 1fr);gap: 16px;}@media (width >= 640px) {.programme {grid-template-columns: repeat(2, 1fr);}}@media (width >= 1024px) {.programme {grid-template-columns: repeat(3, 1fr);}}@media (prefers-color-scheme: dark) {.page {background: #111;color: #eee;}}@media (prefers-reduced-motion: no-preference) {.spinner {animation: spin 1s linear infinite;}}@keyframes spin {to {rotate: 360deg;}}
<body class="bg-white text-neutral-900 dark:bg-neutral-950 dark:text-neutral-100"><span class="size-4 rounded-full border-2 border-current border-t-transparent motion-safe:animate-spin"></span><div class="grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3"><section>Friday 14</section><section>Saturday 15</section><section>Sunday 16</section></div></body>
Reader preferences
Media queries also read settings from the system or the browser. The most useful ones:
- Theme
prefers-color-schemelightordark, following the system theme. This site uses it: change your system theme and the page switches between light and dark. - Motion
prefers-reduced-motionreducewhen the person asked for less motion, andno-preferencewhen they didn't ask for anything. - Contrast
prefers-contrastmoreorlesswhen they asked for more or less contrast. - Pointer
hover · pointerWhat the primary input device can do:(hover: hover)when it can hover over elements, and(pointer: coarse)when it points with little precision, like a finger.
@media (prefers-color-scheme: dark) {.page { background: #111; color: #eee; }}@media (prefers-reduced-motion: no-preference) {.spinner { animation: spin 1s linear infinite; }}@media (pointer: coarse) {button { min-height: 44px; }}
Put the animation inside no-preference instead of removing it with reduce: if a browser doesn't know the query, the condition is false and nothing animates.
Choosing breakpoints
A breakpoint is the width where the design changes. There's no correct list of device sizes, and it changes every year. Shrink the window until something breaks (lines too long, cramped columns) and put the breakpoint there: the content decides, not the phone model.
Inside a media query, em and rem don't depend on the page's CSS: they use the browser's initial font size, 16px by default. With (width >= 40em), if someone raises that size to 20px, the breakpoint moves from 640px to 800px, and the design changes when the larger text no longer fits. Zoom makes no difference: the viewport measured in px shrinks too, so px breakpoints react the same way.
A media query looks at the whole window. If a component can sit in a narrow sidebar or in the main area, what matters is the room it gets: that's what container queries are for, the topic of the next lesson.
In Tailwind
In Tailwind each media query is a variant: a prefix in front of the class. The width ones are mobile-first, like the demo: sm: applies from 40rem (640px) and lg: from 64rem (1024px).
| CSS | Tailwind |
|---|---|
| (width >= 640px) · 768px · 1024px | sm: · md: · lg: |
| (width < 640px) | max-sm: |
| (width >= 900px) | min-[900px]: |
| prefers-color-scheme: dark | dark: |
| prefers-reduced-motion: no-preference · reduce | motion-safe: · motion-reduce: |
| prefers-contrast: more · pointer: coarse | contrast-more: · pointer-coarse: |
<body class="bg-white dark:bg-neutral-950 dark:text-neutral-100"><span class="size-4 rounded-full border-2 motion-safe:animate-spin"></span><div class="grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3"><article>…</article></div><button class="hover:bg-neutral-100 pointer-coarse:min-h-11">Send</button></body>
