Diseño responsive
Breakpoints, container queries y patrones mobile-first en Tailwind CSS v4.
Receta
Tarjeta de referencia rápida — lista para copiar y pegar.
// Breakpoints mobile-first (min-width)
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4">
// Breakpoints por defecto: sm(40rem) md(48rem) lg(64rem) xl(80rem) 2xl(96rem)
// Variante max-width
<div className="max-lg:hidden"> {/* oculto por debajo de lg */}
// Rango
<div className="md:max-xl:flex"> {/* flex solo entre md y xl */}
// Container queries
<div className="@container">
<div className="@sm:flex @lg:grid @lg:grid-cols-2">
{/* responde al ancho del contenedor, no al viewport */}
</div>
</div>
// Contenedores con nombre
<div className="@container/sidebar">
<div className="@md/sidebar:block">Cuándo usarlo: Cuando tu layout o componente necesita adaptarse a distintos tamaños de pantalla o de contenedor.
Ejemplo funcional
export function DashboardLayout({ children }: { children: React.ReactNode }) {
return (
<div className="flex min-h-screen flex-col lg:flex-row">
{/* Barra lateral — ancho completo en móvil, ancho fijo en escritorio */}
<aside className="border-b bg-gray-50 p-4 lg:w-64 lg:shrink-0 lg:border-b-0 lg:border-r">
<nav className="flex gap-2 overflow-x-auto lg:flex-col lg:gap-1 lg:overflow-visible">
<a href="#" className="whitespace-nowrap rounded px-3 py-2 text-sm hover:bg-gray-200 lg:whitespace-normal">
Panel
</a>
<a href="#" className="whitespace-nowrap rounded px-3 py-2 text-sm hover:bg-gray-200 lg:whitespace-normal">
Analíticas
</a>
<a href="#" className="whitespace-nowrap rounded px-3 py-2 text-sm hover:bg-gray-200 lg:whitespace-normal">
Configuración
</a>
</nav>
</aside>
{/* Contenido principal */}
<main className="flex-1 p-4 md:p-6 lg:p-8">
{/* Cuadrícula de estadísticas — columnas responsive */}
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2 xl:grid-cols-4">
<StatCard label="Ingresos" value="$12,345" />
<StatCard label="Usuarios" value="1,234" />
<StatCard label="Pedidos" value="567" />
<StatCard label="Conversión" value="3.2%" />
</div>
{/* Área de contenido con container queries */}
<div className="mt-6 grid gap-6 lg:grid-cols-[1fr_300px]">
<section className="@container">
<div className="@md:grid @md:grid-cols-2 @md:gap-4 space-y-4 @md:space-y-0">
{children}
</div>
</section>
<aside className="max-lg:hidden">
<div className="sticky top-4 rounded border p-4">
<h3 className="font-medium">Feed de actividad</h3>
</div>
</aside>
</div>
</main>
</div>
);
}
function StatCard({ label, value }: { label: string; value: string }) {
return (
<div className="rounded-lg border bg-white p-4 shadow-sm">
<p className="text-sm text-gray-500">{label}</p>
<p className="mt-1 text-2xl font-bold">{value}</p>
</div>
);
}Lo que esto demuestra:
- Layout mobile-first que pasa de apilado a barra lateral en
lg - Nav con scroll horizontal en móvil, vertical en escritorio
- Columnas de cuadrícula responsive (
grid-cols-1agrid-cols-4) - Container queries para el layout responsive del área de contenido
max-lg:hiddenpara ocultar elementos por debajo de un breakpoint- Barra lateral
stickyen escritorio
Análisis profundo
Cómo funciona
- Tailwind usa un enfoque mobile-first: las utilidades sin prefijo se aplican a todos los tamaños; los prefijos de breakpoint se aplican desde ese tamaño en adelante
- Los breakpoints se compilan a
@media (min-width: ...)—md:flexsignifica@media (width >= 48rem) { display: flex } - Las variantes
max-*se compilan a@media (width < ...)— útiles para estilos solo en móvil - Las container queries usan
@containeren el padre y@sm:,@md:, etc. en los hijos - Breakpoints de container query:
@3xs(12rem)@2xs(16rem)@xs(20rem)@sm(24rem)@md(28rem)@lg(32rem)@xl(36rem)@2xl(42rem)etc. - Los breakpoints de v4 se definen en
rempor defecto, no enpx
Variaciones
Breakpoints personalizados:
@theme {
--breakpoint-xs: 30rem; /* 480px */
--breakpoint-3xl: 120rem; /* 1920px */
}Container query con tamaños personalizados:
@theme {
--container-4xs: 8rem;
}Tipografía responsive:
<h1 className="text-2xl sm:text-3xl md:text-4xl lg:text-5xl">
Encabezado responsive
</h1>
{/* O usa clamp para un tamaño fluido */}
<h1 className="text-[clamp(1.5rem,4vw,3rem)]">
Encabezado fluido
</h1>Mostrar/ocultar responsive:
{/* Mostrar solo en móvil */}
<div className="md:hidden">Nav móvil</div>
{/* Mostrar solo en escritorio */}
<div className="hidden md:block">Nav de escritorio</div>
{/* Mostrar entre md y xl */}
<div className="hidden md:block xl:hidden">Solo tablet</div>Tarjeta con container query:
function AdaptiveCard({ children }: { children: React.ReactNode }) {
return (
<div className="@container">
<div className="flex flex-col @sm:flex-row @sm:items-center gap-4 rounded border p-4">
<div className="size-16 shrink-0 rounded bg-gray-200 @sm:size-20" />
<div className="flex-1">{children}</div>
</div>
</div>
);
}Notas de TypeScript
// Las variantes responsive son nombres de clase — sin impacto en TS
// Pero puedes tipar props dependientes de breakpoints:
interface LayoutProps {
columns?: {
default: number;
sm?: number;
md?: number;
lg?: number;
};
}
function Grid({ columns }: LayoutProps) {
const colClasses = {
1: "grid-cols-1",
2: "grid-cols-2",
3: "grid-cols-3",
4: "grid-cols-4",
} as const;
return (
<div className={cn(
"grid gap-4",
colClasses[columns.default as keyof typeof colClasses],
columns.sm && `sm:${colClasses[columns.sm as keyof typeof colClasses]}`,
)}>
{/* Nota: la construcción dinámica de clases no funciona con Tailwind — ver Errores comunes */}
</div>
);
}Errores comunes
-
Los nombres de clase dinámicos no se detectan —
sm:grid-cols-${n}nunca lo encuentra el escáner de Tailwind. Solución: Usa nombres de clase estáticos completos o inclúyelos en la safelist. -
max-*es<, no<=—max-md:hiddensignifica oculto cuando el viewport está estrictamente por debajo demd. En exactamente48rem,md:toma el control. -
Las container queries necesitan un padre
@container— Sin él,@sm:flexno hace nada. Solución: Añade siempre@containeral elemento cuyo ancho quieres consultar. -
hiddenen móvil + mostrar con breakpoint —hidden md:flexfunciona, perohidden md:blockse muestra comoblock. Asegúrate de que el valor dedisplaycoincida con lo que quieres. -
Breakpoint de impresión — Usa
print:para estilos específicos de impresión:print:hidden,print:text-black. A menudo se olvida, pero es importante para páginas imprimibles.
Alternativas
| Alternativa | Cuándo usarla | Cuándo no usarla |
|---|---|---|
CSS @media directamente | Necesitas consultas de medios complejas (hover, prefers-reduced-motion) | Los breakpoints estándar son suficientes |
CSS @container directamente | Necesitas funciones de container query más allá del soporte de Tailwind | Las utilidades @container de Tailwind cubren tus necesidades |
hook useMediaQuery | Necesitas lógica responsive a nivel de JS (componentes distintos) | Los cambios de layout solo con CSS son suficientes |
Imágenes responsive (srcset) | Necesitas archivos de imagen distintos por breakpoint | Solo necesitas cambios de layout |
Preguntas frecuentes
¿Qué significa "mobile-first" en el sistema de breakpoints de Tailwind?
Las utilidades sin prefijo se aplican a todos los tamaños de pantalla. Los prefijos de breakpoint como md: se aplican desde ese tamaño en adelante (min-width). Diseñas primero para móvil y luego añades capas de ajustes para pantallas más grandes.
¿Cuáles son los valores de breakpoint por defecto en Tailwind v4?
sm= 40rem,md= 48rem,lg= 64rem,xl= 80rem,2xl= 96rem- Se definen en
rem, no enpx.
¿Cómo mostrar un elemento solo entre md y xl?
<div className="hidden md:block xl:hidden">Solo tablet</div>¿Cuál es la diferencia entre max-lg:hidden y lg:hidden?
max-lg:hidden— oculto por debajo delg(viewport < 64rem)lg:hidden— oculto enlgy por encima (viewport >= 64rem)
¿Cómo funcionan las container queries en Tailwind v4?
- Añade
@containera un elemento padre - Usa
@sm:,@md:,@lg:etc. en los hijos - Los hijos responden al ancho del contenedor, no al viewport
¿Cómo definir un breakpoint personalizado en v4?
@theme {
--breakpoint-xs: 30rem;
--breakpoint-3xl: 120rem;
}¿Cuál es el enfoque de tipografía fluida con clamp()?
<h1 className="text-[clamp(1.5rem,4vw,3rem)]">Encabezado fluido</h1>El tamaño de fuente escala suavemente entre 1.5rem y 3rem según el ancho del viewport.
Trampa: Escribiste sm:grid-cols-${n} pero la clase no se aplica. ¿Por qué?
El escáner de Tailwind no puede detectar nombres de clase dinámicos construidos con template literals. Usa nombres de clase estáticos completos o un mapa de búsqueda en su lugar.
Trampa: Clases de container query como @sm:flex no hacen nada. ¿Qué falta?
El elemento padre necesita className="@container". Sin él, no hay contenedor contra el que consultar.
¿Cómo tipar props dependientes de breakpoints en TypeScript?
interface LayoutProps {
columns?: {
default: number;
sm?: number;
md?: number;
lg?: number;
};
}Luego usa un mapa de búsqueda estático para convertir números en cadenas de clase — nunca interpolar dinámicamente.
¿Cómo aplicar estilos solo para impresión?
Usa la variante print:: print:hidden, print:text-black. Se compila a @media print.
¿Qué es un contenedor con nombre y cuándo usarlo?
<div className="@container/sidebar">
<div className="@md/sidebar:block">Los contenedores con nombre te permiten consultar un antecesor específico cuando hay varios elementos @container anidados.
Relacionado
- Configuración — configurar breakpoints personalizados
- Utilidades — clases de utilidad principales
- Modo oscuro — patrones responsive en modo oscuro
- Utilidades personalizadas — utilidades responsive personalizadas