Diseño responsive
Breakpoints, container queries y patrones mobile-first en Tailwind CSS v4.
Busca en todas las páginas de la documentación
Breakpoints, container queries y patrones mobile-first en Tailwind CSS v4.
🤖 Read the SystemsArchitect.io Blog for over 100+ cloud architecture articles 🔥
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.
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:
lggrid-cols-1 a grid-cols-4)max-lg:hidden para ocultar elementos por debajo de un breakpointsticky en escritorio@media (min-width: ...) - md:flex significa @media (width >= 48rem) { display: flex }max-* se compilan a @media (width < ...) - útiles para estilos solo en móvil@container en el padre y @sm:, @md:, etc. en los hijos@3xs(12rem) @2xs(16rem) @xs(20rem) @sm(24rem) @md(28rem) @lg(32rem) @xl(36rem) @2xl(42rem) etc.rem por defecto, no en pxBreakpoints 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>
);
}// 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>
);
}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:hidden significa oculto cuando el viewport está estrictamente por debajo de md. En exactamente 48rem, md: toma el control.
Las container queries necesitan un padre @container - Sin él, @sm:flex no hace nada. Solución: Añade siempre @container al elemento cuyo ancho quieres consultar.
hidden en móvil + mostrar con breakpoint - hidden md:flex funciona, pero hidden md:block se muestra como block. Asegúrate de que el valor de display coincida 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.
| 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 |
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.
sm = 40rem, md = 48rem, lg = 64rem, xl = 80rem, 2xl = 96remrem, no en px.<div className="hidden md:block xl:hidden">Solo tablet</div>max-lg:hidden - oculto por debajo de lg (viewport < 64rem)lg:hidden - oculto en lg y por encima (viewport >= 64rem)@container a un elemento padre@sm:, @md:, @lg: etc. en los hijos@theme {
--breakpoint-xs: 30rem;
--breakpoint-3xl: 120rem;
}<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.
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.
El elemento padre necesita className="@container". Sin él, no hay contenedor contra el que consultar.
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.
Usa la variante print:: print:hidden, print:text-black. Se compila a @media print.
<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.
Revisado por Chris St. John·Última actualización: 19 jul 2026
🤖 Read the SystemsArchitect.io Blog for over 100+ cloud architecture articles 🔥