CSS Modern Layout: Grid, Flexbox, Container Queries
Dominá modern CSS layout: CSS Grid, Flexbox, container queries, subgrid, logical properties y responsive design patterns sin media queries.
Nota para desarrolladores hispanohablantes: Esta guía incluye ejemplos y convenciones de nomenclatura adaptadas a equipos que trabajan en español. Cuando existen diferencias significativas en terminología técnica entre el inglés y el español, se indican explícitamente para facilitar la comunicación en equipos multiculturales.
Introducción
Modern CSS provee layout tools que hacen media-query-heavy stylesheets largely innecesarios. CSS Grid maneja two-dimensional layouts. Flexbox maneja one-dimensional distribution. Container queries dejan a los components responder al size de su parent en vez del viewport. Subgrid permite a nested grids alinear con parent grid tracks. A continuación: practical patterns para cada uno, con real-world component examples.
CSS Grid
Grid básico
/* 3-column grid con gaps */
.grid-3 {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 1.5rem;
}
/* Responsive sin media queries */
.grid-auto {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
gap: 1.5rem;
}
/* Columns automáticamente adjust: 1 column en mobile, 2 en tablet, 3+ en desktop */
Named grid areas
.layout {
display: grid;
grid-template-areas:
"header header header"
"sidebar main main"
"sidebar footer footer";
grid-template-columns: 250px 1fr 1fr;
grid-template-rows: auto 1fr auto;
min-height: 100vh;
gap: 1rem;
}
.header { grid-area: header; }
.sidebar { grid-area: sidebar; }
.main { grid-area: main; }
.footer { grid-area: footer; }
/* Responsive: stack en mobile */
@media (max-width: 768px) {
.layout {
grid-template-areas:
"header"
"main"
"sidebar"
"footer";
grid-template-columns: 1fr;
}
}
Grid alignment
.card-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
gap: 1.5rem;
align-items: start; /* Items align al top de su row */
justify-items: stretch; /* Items stretch para fill column width */
}
.center-grid {
display: grid;
place-items: center; /* Shorthand para align-items + justify-items */
min-height: 100vh;
}
span y line-based placement
.feature-grid {
display: grid;
grid-template-columns: repeat(4, 1fr);
grid-template-rows: repeat(3, 200px);
gap: 1rem;
}
.featured { grid-column: span 2; grid-row: span 2; }
.wide { grid-column: 1 / -1; } /* Full width */
.sidebar-item { grid-column: 4; grid-row: 1 / 4; } /* Right column, full height */
Flexbox
One-dimensional distribution
/* Horizontal distribution */
.toolbar {
display: flex;
align-items: center;
justify-content: space-between;
gap: 1rem;
padding: 0.75rem 1.5rem;
}
/* Center anything */
.center {
display: flex;
align-items: center;
justify-content: center;
min-height: 100vh;
}
/* Sticky footer con flex */
.page {
display: flex;
flex-direction: column;
min-height: 100vh;
}
.page__content { flex: 1; }
Flex wrap y gap
.tag-list {
display: flex;
flex-wrap: wrap;
gap: 0.5rem;
}
.tag {
padding: 0.25rem 0.75rem;
background: #e0f2fe;
border-radius: 9999px;
font-size: 0.875rem;
}
Flex grow/shrink ratios
.message-layout {
display: flex;
align-items: flex-start;
gap: 1rem;
}
.avatar { flex: 0 0 48px; } /* Fixed width, no grow/shrink */
.content { flex: 1 1 auto; } /* Grow para fill, shrink si needed */
.timestamp { flex: 0 0 auto; } /* Fixed width basado en content */
Container Queries
/* Definí un containment context */
.card-container {
container-type: inline-size;
container-name: card;
}
/* Component responde a su container, no al viewport */
@container card (min-width: 400px) {
.card {
display: grid;
grid-template-columns: 120px 1fr;
gap: 1rem;
}
}
@container card (max-width: 399px) {
.card {
display: flex;
flex-direction: column;
gap: 0.5rem;
}
}
<!-- El mismo card component adapta basado en dónde está placed -->
<div class="sidebar" style="container-type: inline-size;">
<div class="card">...</div> <!-- Narrow: stacked layout -->
</div>
<div class="main-content" style="container-type: inline-size;">
<div class="card">...</div> <!-- Wide: side-by-side layout -->
</div>
Container query units
.sidebar-widget {
container-type: inline-size;
}
.widget-title {
font-size: clamp(1rem, 5cqi, 1.5rem);
/* cqi = 1% del container's inline size */
/* Adapta font size al container width */
}
.widget-body {
padding: 2cqb; /* 2% del container's block size */
}
Subgrid
/* Parent grid define los tracks */
.product-grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 1.5rem;
}
/* Child grid hereda parent tracks */
.product-card {
display: grid;
grid-template-rows: subgrid;
grid-row: span 3;
}
/* Ahora todos los cards en la misma row tienen aligned headers, images y footers */
.product-image { grid-row: 1; }
.product-title { grid-row: 2; }
.product-actions { grid-row: 3; }
Logical Properties
/* Physical properties (direction-specific) */
.old-way {
margin-left: 1rem;
margin-right: 1rem;
padding-top: 0.5rem;
text-align: left;
border-left: 1px solid #ccc;
}
/* Logical properties (direction-aware, funciona con RTL) */
.new-way {
margin-inline: 1rem; /* left + right en LTR, right + left en RTL */
padding-block: 0.5rem; /* top + bottom */
text-align: start; /* left en LTR, right en RTL */
border-inline-start: 1px solid #ccc;
}
/* Logical sizing */
.sidebar {
inline-size: 250px; /* width en LTR */
block-size: 100vh; /* height */
max-inline-size: 100%; /* max-width */
}
Responsive Patterns Sin Media Queries
Fluid typography
/* clamp() para fluid font sizes */
h1 {
font-size: clamp(1.5rem, 4vw, 3rem);
/* Min 1.5rem, scales con viewport, max 3rem */
}
p {
font-size: clamp(1rem, 2.5vw, 1.125rem);
line-height: 1.6;
}
Auto-fit grid (no breakpoints)
.responsive-cards {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(min(100%, 280px), 1fr));
gap: 1.5rem;
}
/* Cards wrap automáticamente. min() previene overflow en very small screens */
Aspect ratio
.video-embed {
aspect-ratio: 16 / 9;
width: 100%;
background: #000;
}
.avatar-square {
aspect-ratio: 1;
width: 48px;
object-fit: cover;
}
Real-World Components
Holy Grail layout
.holy-grail {
display: grid;
grid-template:
"header header header" auto
"nav main aside" 1fr
"footer footer footer" auto
/ 200px 1fr 200px;
min-height: 100vh;
gap: 1rem;
}
.holy-grail > header { grid-area: header; }
.holy-grail > nav { grid-area: nav; }
.holy-grail > main { grid-area: main; }
.holy-grail > aside { grid-area: aside; }
.holy-grail > footer { grid-area: footer; }
@media (max-width: 768px) {
.holy-grail {
grid-template:
"header" auto
"main" 1fr
"nav" auto
"aside" auto
"footer" auto
/ 1fr;
}
}
Card con container queries
.product-listing {
container-type: inline-size;
display: grid;
gap: 1rem;
}
@container (min-width: 500px) {
.product {
display: grid;
grid-template-columns: 150px 1fr auto;
align-items: center;
gap: 1rem;
}
.product__image { grid-column: 1; }
.product__info { grid-column: 2; }
.product__price { grid-column: 3; }
}
@container (max-width: 499px) {
.product {
display: flex;
flex-direction: column;
gap: 0.75rem;
}
}
Sticky sidebar con scrollable main
.app-layout {
display: grid;
grid-template-columns: 280px 1fr;
height: 100vh;
}
.sidebar {
overflow-y: auto;
position: sticky;
top: 0;
height: 100vh;
}
.main-content {
overflow-y: auto;
padding: 2rem;
}
Best Practices
-
For a deeper guide, see Complete Guide to CSS Grid and Flexbox.
-
Usá Grid para 2D layouts (rows + columns), Flexbox para 1D (row OR column)
-
Usá
auto-fitconminmax()para responsive grids sin media queries -
Usá container queries para component-level responsiveness — los components deberían adaptar a su container, no al viewport
-
Usá logical properties (
margin-inline,padding-block) para RTL support -
Usá
gapen vez de margins para spacing entre flex/grid items — cleaner y no collapse -
Usá
clamp()para fluid typography — no media queries needed -
Usá
subgridcuando nested grids necesitan alinear con parent tracks -
Usá
aspect-ratioen vez de padding hacks para video embeds -
Usá
min()insideminmax()para prevenir grid overflow en small screens -
Testeá con browser DevTools device emulation — verificá layouts a 320px, 768px, 1024px, 1440px
Common Mistakes
- Usar Flexbox para 2D layouts: Flexbox wrappea items independently. Grid alinea rows y columns together.
- Fixed pixel widths: break en different screen sizes. Usá
fr,auto,minmax(), o percentages. - Media queries para todo: container queries y
auto-fitgrids eliminan most breakpoints. - Olvidar
gap: usar margins para spacing entre items causa double-spacing en edges.gapsolo aplica entre items. - No usar
min()enminmax():minmax(280px, 1fr)overflow en screens < 280px. Usáminmax(min(100%, 280px), 1fr).
FAQ
Grid vs. Flexbox — ¿cuándo usar cuál?
Usá Grid cuando necesitás control sobre rows y columns (page layouts, card grids, dashboards). Usá Flexbox cuando necesitás one-dimensional distribution (toolbars, nav bars, tag lists, centering).
¿Qué son container queries?
CSS queries que aplican styles basados en el size de un parent container, no del viewport. Un card component puede render diferente en un narrow sidebar vs. un wide main content area, sin saber dónde está placed.
¿Qué es subgrid?
Un CSS Grid feature que deja a un child grid heredar el parent grid’s track definitions. Esto asegura que nested grids alinean sus rows o columns con el parent, que era previously impossible.
¿Qué son logical properties?
CSS properties que adaptan al writing direction. margin-inline-start es margin-left en LTR y margin-right en RTL. Eliminan la need de separate RTL stylesheets.
¿Cómo se diferencia auto-fit de auto-fill?
auto-fit collapsea empty tracks a zero width, stretchando remaining items. auto-fill mantiene empty tracks con su minimum width. Usá auto-fit cuando querés que items grow para fill el row, auto-fill cuando querés consistent item sizes.
Recursos Relacionados
Complete Guide to Web Accessibility: WCAG 2.2 Compliance
Master web accessibility with WCAG 2.2: ARIA roles, keyboard navigation, screen reader support, color contrast, focus management, and accessible forms.
RecipeContainer Queries for Component Responsiveness
How to use CSS container queries for component-level responsive layouts that adapt to their container size instead of the viewport.
RecipeDark Mode with prefers-color-scheme and CSS Variables
How to implement dark mode using CSS prefers-color-scheme media query, CSS custom properties, and manual toggle with localStorage persistence.