Logo de Condor CodersCondor
Coders
← Volver a recursos

Breakpoints reutilizables con @custom-media

Guía práctica para crear breakpoints reutilizables con @custom-media en css.

Por Sofia Grijalva

El problema de los números mágicos

Cuando construimos un layout responsive, es muy común escribir algo así:

@media (min-width: 768px) {
  /* navbar */
}
 
@media (min-width: 640px) {
  /* cards: 2 columnas */
}
 
@media (min-width: 1024px) {
  /* cards: 3 columnas */
}
 
@media (min-width: 1280px) {
  /* cards: centrado */
}

Al principio parece simple, pero pronto aparece el problema: esos valores aparecen repetidos en varios puntos y no dicen nada sobre su intención. ¿768px significa tablet? ¿o solo un ajuste de navegación? Cuando el diseño cambia, hay que buscar y reemplazar esos números a lo largo del proyecto. Esa es la parte frustrante: el código funciona, pero es difícil de mantener y aún más difícil de leer.

La buena noticia es que hay una manera más clara de nombrar esos puntos de quiebre.


La solución: nombrar los breakpoints

En lugar de repetir min-width: 768px una y otra vez, definimos un nombre que explique su propósito. Así, el CSS se vuelve más expresivo y mucho más fácil de mantener.

@custom-media --tablet (width >= 48rem);
 
@media (--tablet) {
  .elemento {
    /* estilos para tablet */
  }
}

Esto no solo hace el código más legible. También centraliza la definición del breakpoint. Si más adelante cambia el valor, lo actualizas en un solo lugar y todo el resto del proyecto sigue funcionando.


Qué es @custom-media

@custom-media es un at-rule de CSS que nos permite crear un alias para una media query. En otras palabras: en vez de repetir la condición entera cada vez, le damos un nombre y lo reutilizamos donde haga falta.

@custom-media --nombre-descriptivo (condición);
 
@media (--nombre-descriptivo) {
  .elemento {
    /* ... */
  }
}

La idea es bastante simple: el nombre del breakpoint comunica el contexto, no solo un número. --tablet, --desktop, --mobile-lg tienen mucho más sentido que 768px o 1024px dispersos por el archivo.

Eso sí: @custom-media es una sintaxis experimental. Aunque forma parte de la especificación de Media Queries Level 5, todavía no está soportada de forma nativa en todos los navegadores, así que vale la pena revisar su estado real en Can I Use. La referencia oficial está en la documentación de MDN para @custom-media y en la especificación de Media Queries Level 5. Cuando se necesita usarlo en producción, la solución usual es procesarlo con PostCSS y el plugin postcss-custom-media.


¿Y cómo funciona en la práctica?

La sintaxis más moderna para estas condiciones usa rangos, que se leen mucho mejor:

/* Clásica */
@media (min-width: 768px) { ... }
@media (max-width: 767.98px) { ... }
 
/* Rango */
@media (width >= 48rem) { ... }
@media (width < 48rem) { ... }
 
/* Rango entre dos valores */
@media (48rem <= width < 64rem) { ... }

La sintaxis de rango es muy útil porque expresa la condición de una forma más natural. Ya no hace falta estar “restando” valores para evitar solapamientos. Además, la parte de @custom-media es precisamente la que nos permite encapsular esas condiciones con nombres claros.


Setup mínimo para usarlo en un proyecto real

Lo más sencillo es mantener los aliases separados en un archivo de tokens y luego importarlos en tu CSS principal.

mi-proyecto/
├── tokens.css
├── styles.css
├── dist/
│   └── styles.css
├── index.html
├── package.json
└── postcss.config.js

1. Inicializar el proyecto e instalar dependencias

pnpm init
pnpm add -D postcss postcss-cli postcss-import postcss-custom-media
  • postcss es el motor que procesa el CSS.
  • postcss-cli permite correr PostCSS desde la terminal con un comando simple.
  • postcss-import resuelve los @import, así styles.css puede traer tokens.css antes de procesar los breakpoints.
  • postcss-custom-media transforma los aliases @custom-media en @media estándar.

2. Configurar PostCSS

// postcss.config.js
module.exports = {
  plugins: [require("postcss-import"), require("postcss-custom-media")],
};

Punto importante: el orden importa. postcss-import debe ir primero para inyectar el contenido de tokens.css dentro de styles.css antes de que postcss-custom-media resuelva los aliases. Si inviertes ese orden, el plugin de custom media no alcanza a ver definiciones que todavía no fueron importadas.

3. Agregar scripts al package.json

{
  "scripts": {
    "build:css": "postcss styles.css -o dist/styles.css",
    "watch:css": "postcss styles.css -o dist/styles.css --watch"
  }
}

Con eso ya puedes generar el archivo final una vez con pnpm build:css o dejar el proceso escuchando cambios con pnpm watch:css mientras trabajas.

4. Actualizar el HTML para usar el CSS compilado

Si tu index.html todavía apunta a styles.css, hay que cambiarlo para que cargue el archivo generado en dist:

<link rel="stylesheet" href="./dist/styles.css" />

Ese detalle es importante porque el navegador no entiende @custom-media igual que PostCSS. El archivo que debe consumir la página es el CSS ya procesado dentro de dist.


Ejemplo completo

En tokens.css definimos los breakpoints:

@custom-media --tablet (width >= 48rem);

En styles.css los usamos:

@import "./tokens.css";
 
body {
  background: pink;
}
 
@media (--tablet) {
  body {
    background: lightgreen;
  }
}

Y el CSS generado en /dist queda así:

body {
  background: pink;
}
 
@media (width >= 48rem) {
  body {
    background: lightgreen;
  }
}

La mejora aquí es muy clara: el código fuente sigue siendo limpio y legible, y el build se encarga de traducir la sintaxis moderna a algo compatible.


Configuración de breakpoints

En un proyecto real, lo más útil es definir una base de breakpoints desde el inicio y mantenerla en un solo archivo. Así, todo el diseño comparte la misma escala y los nombres se vuelven más significativos que los números puros.

Para esta base, usé como referencia los breakpoints de Tailwind, que están documentados en Responsive design. Estos son breakpoints “desde” (width >= ...), pensados para un flujo mobile first: primero construimos el layout base para móvil y luego añadimos ajustes a partir de cada punto de quiebre.

TailwindValorNombre
sm40rem (640px)--mobile-lg
md48rem (768px)--tablet
lg64rem (1024px)--desktop
xl80rem (1280px)--desktop-lg
2xl96rem (1536px)--desktop-xl
/* tokens.css */
@custom-media --mobile-lg (width >= 40rem); /* 640px */
@custom-media --tablet (width >= 48rem); /* 768px */
@custom-media --desktop (width >= 64rem); /* 1024px */
@custom-media --desktop-lg (width >= 80rem); /* 1280px */
@custom-media --desktop-xl (width >= 96rem); /* 1536px */

Si escribimos estos tokens en un archivo central, el resto del proyecto solo necesita importarlos y usar los nombres. Eso hace que el CSS sea más legible y mucho más fácil de mantener.

Usar rem en lugar de px también tiene sentido porque se mantiene más consistente con la escala tipográfica del usuario y con el comportamiento del tamaño base del documento.


Variantes útiles para distintos casos

Además de los breakpoints “desde”, puedes definir versiones “hasta” o combinadas con orientación:

@custom-media --until-mobile-lg (width < 40rem);
@custom-media --until-tablet (width < 48rem);
@custom-media --until-desktop (width < 64rem);
 
@custom-media --only-tablet (48rem <= width < 64rem);
@custom-media --tablet-portrait (width >= 48rem) and (orientation: portrait);

Estas definiciones hacen que el CSS del proyecto se lea como un idioma propio: “si la pantalla es tablet”, “si está por debajo de desktop”, “si la vista es portrait”. Eso ayuda mucho en equipos grandes y en layouts que cambian con frecuencia.


Uso real en una interfaz

Imagina una navegación y un grid de tarjetas:

@import "./tokens.css";
 
.nav-links {
  display: none;
}
 
@media (--tablet) {
  .nav-links {
    display: flex;
  }
}
 
.cards {
  display: grid;
  grid-template-columns: 1fr;
}
 
@media (--mobile-lg) {
  .cards {
    grid-template-columns: repeat(2, 1fr);
  }
}
 
@media (--desktop) {
  .cards {
    grid-template-columns: repeat(3, 1fr);
  }
}
 
@media (--desktop-lg) {
  .cards {
    max-width: 1200px;
    margin-inline: auto;
  }
}

La diferencia no es solo estética: este enfoque hace que el código comunique el diseño de una manera mucho más clara para ti y para cualquier persona que vuelva a revisarlo después.