CSS Responsive Layout with Flexbox/Grid
A step-by-step practical guide to building responsive layouts that adapt naturally across mobile, tablet, and desktop using a combination of CSS Flexbox and Grid
Problem
Required Tools
A CSS module optimized for one-dimensional (row or column) layouts. Ideal for navigation bars, card internal alignment, and centering.
A CSS module for controlling two-dimensional (row and column) layouts simultaneously. Best for full page layouts and card grid placement.
A CSS feature that applies different styles based on viewport size. Essential for breakpoint-based layout transitions.
A modern CSS feature that changes styles based on the parent container size. Useful for designing reusable components.
CSS variables used to consistently manage spacing, column counts, and other values across breakpoints.
Solution Steps
Set up CSS variables and base reset
Set up the foundation for responsive layout. Define breakpoint-specific values with CSS variables, and unify box-sizing to border-box so layout calculations are predictable. Using rem units as the default respects the user's font size settings while maintaining consistent proportions.
/* === Base reset and CSS variable definitions === */
:root {
/* Breakpoint reference values */
--sidebar-width: 260px;
--header-height: 64px;
/* Spacing system */
--gap-sm: 0.5rem;
--gap-md: 1rem;
--gap-lg: 1.5rem;
--gap-xl: 2rem;
/* Card grid */
--card-min-width: 280px;
--card-columns: 1;
}
*,
*::before,
*::after {
box-sizing: border-box;
margin: 0;
padding: 0;
}
html {
font-size: 16px;
-webkit-text-size-adjust: 100%;
}
body {
min-height: 100vh;
line-height: 1.5;
}Build full page layout with CSS Grid
CSS Grid is the best choice for the overall page structure (sidebar + header + main). Using grid-template-areas allows intuitive placement by area names, and switching layouts only requires changing the area arrangement in media queries. On mobile, hide the sidebar and switch to a single-column layout.
/* === Full page layout (Grid) === */
.dashboard {
display: grid;
min-height: 100vh;
/* Mobile default: single column */
grid-template-columns: 1fr;
grid-template-rows: var(--header-height) 1fr;
grid-template-areas:
"header"
"main";
}
.dashboard__header {
grid-area: header;
position: sticky;
top: 0;
z-index: 100;
background: #fff;
border-bottom: 1px solid #e5e7eb;
}
.dashboard__sidebar {
grid-area: sidebar;
background: #1f2937;
color: #f9fafb;
overflow-y: auto;
display: none; /* Hidden on mobile */
}
.dashboard__main {
grid-area: main;
padding: var(--gap-lg);
overflow-y: auto;
}
/* Tablet and above (768px+): show sidebar */
@media (min-width: 768px) {
.dashboard {
grid-template-columns: var(--sidebar-width) 1fr;
grid-template-rows: var(--header-height) 1fr;
grid-template-areas:
"sidebar header"
"sidebar main";
}
.dashboard__sidebar {
display: block;
position: sticky;
top: 0;
height: 100vh;
}
}Implement header navigation with Flexbox
Flexbox is ideal for horizontal alignment of the logo, search bar, and user menu inside the header. Use justify-content: space-between for side alignment and gap for item spacing. On mobile, the search bar should be hidden or replaced with an icon for responsive handling.
/* === Header navigation (Flexbox) === */
.header-nav {
display: flex;
align-items: center;
justify-content: space-between;
height: 100%;
padding: 0 var(--gap-lg);
gap: var(--gap-md);
}
.header-nav__logo {
flex-shrink: 0;
font-size: 1.25rem;
font-weight: 700;
}
.header-nav__search {
flex: 1;
max-width: 400px;
display: none; /* Hidden on mobile */
}
.header-nav__actions {
display: flex;
align-items: center;
gap: var(--gap-sm);
flex-shrink: 0;
}
/* Show search bar on tablet and above */
@media (min-width: 768px) {
.header-nav__search {
display: block;
}
}
/* === Sidebar menu (Flexbox vertical) === */
.sidebar-menu {
display: flex;
flex-direction: column;
padding: var(--gap-md);
gap: var(--gap-sm);
}
.sidebar-menu__item {
display: flex;
align-items: center;
gap: var(--gap-sm);
padding: 0.625rem var(--gap-md);
border-radius: 0.5rem;
color: #d1d5db;
text-decoration: none;
transition: background 0.2s;
}
.sidebar-menu__item:hover,
.sidebar-menu__item--active {
background: rgba(255, 255, 255, 0.1);
color: #fff;
}Auto-responsive card grid with auto-fill/auto-fit
For card grids, combining CSS Grid's auto-fill (or auto-fit) with minmax() automatically adjusts the number of columns without media queries. auto-fill keeps empty tracks, while auto-fit lets existing items fill the remaining space. Using Flexbox for internal card alignment maintains uniform layout even with varying content amounts.
/* === Auto-responsive card grid === */
.card-grid {
display: grid;
/* Key: minmax + auto-fill for responsive without media queries */
grid-template-columns: repeat(
auto-fill,
minmax(var(--card-min-width), 1fr)
);
gap: var(--gap-lg);
}
/* === Card component (Flexbox internal alignment) === */
.card {
display: flex;
flex-direction: column;
background: #fff;
border: 1px solid #e5e7eb;
border-radius: 0.75rem;
overflow: hidden;
transition: box-shadow 0.2s;
}
.card:hover {
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
}
.card__header {
padding: var(--gap-md) var(--gap-lg);
border-bottom: 1px solid #f3f4f6;
}
.card__body {
flex: 1; /* Takes remaining space -> uniform card height */
padding: var(--gap-lg);
}
.card__footer {
padding: var(--gap-md) var(--gap-lg);
border-top: 1px solid #f3f4f6;
display: flex;
justify-content: flex-end;
gap: var(--gap-sm);
}Component-level responsiveness with Container Queries
Container Queries change styles based on the parent container size rather than the viewport. This is useful when the same card component needs different layouts in a narrow sidebar versus a wide main area. Set container-type: inline-size on the parent and write conditions with @container.
/* === Container Query based responsive component === */
/* Container setup */
.widget-container {
container-type: inline-size;
container-name: widget;
}
/* Default: vertical layout */
.widget {
display: flex;
flex-direction: column;
gap: var(--gap-sm);
padding: var(--gap-md);
}
.widget__chart {
width: 100%;
aspect-ratio: 16 / 9;
background: #f3f4f6;
border-radius: 0.5rem;
}
/* When container is 500px or wider: horizontal layout */
@container widget (min-width: 500px) {
.widget {
flex-direction: row;
align-items: center;
}
.widget__chart {
width: 50%;
aspect-ratio: 4 / 3;
}
}
/* When container is 800px or wider: show extra info */
@container widget (min-width: 800px) {
.widget__extra {
display: block;
}
.widget__chart {
width: 40%;
}
}Establish media query breakpoint strategy
Use a mobile-first (min-width) approach to define styles from small screens and progressively enhance. Breakpoints should be based on when content breaks, not on specific device sizes. Using the clamp() function allows fluid font sizes and spacing without media queries.
/* === Mobile-first breakpoints === */
/* Fluid typography (no media queries needed) */
h1 { font-size: clamp(1.5rem, 4vw, 2.5rem); }
h2 { font-size: clamp(1.25rem, 3vw, 2rem); }
p { font-size: clamp(0.875rem, 2vw, 1rem); }
/* Fluid spacing */
.section {
padding: clamp(1rem, 4vw, 3rem);
}
/* Breakpoint: sm (640px) */
@media (min-width: 640px) {
:root { --card-min-width: 240px; }
.card-grid { gap: var(--gap-md); }
}
/* Breakpoint: md (768px) */
@media (min-width: 768px) {
:root { --card-min-width: 280px; }
.dashboard__main { padding: var(--gap-xl); }
}
/* Breakpoint: lg (1024px) */
@media (min-width: 1024px) {
:root { --card-min-width: 300px; }
.card-grid { gap: var(--gap-xl); }
}
/* Breakpoint: xl (1280px) */
@media (min-width: 1280px) {
.dashboard__main {
max-width: 1200px;
margin-inline: auto;
}
}
/* Dark mode support */
@media (prefers-color-scheme: dark) {
:root {
--bg-primary: #111827;
--bg-card: #1f2937;
--border-color: #374151;
--text-primary: #f9fafb;
}
}Core Code
The core responsive layout pattern: CSS Grid for page skeleton, Flexbox for component internals. auto-fill + minmax() provides automatic grid behavior without media queries.
/* ====================================
Core: Grid (page layout) + Flexbox (components) combo
==================================== */
/* Full page: Grid */
.dashboard {
display: grid;
min-height: 100vh;
grid-template-columns: 1fr;
grid-template-rows: 64px 1fr;
grid-template-areas: "header" "main";
}
@media (min-width: 768px) {
.dashboard {
grid-template-columns: 260px 1fr;
grid-template-areas: "sidebar header" "sidebar main";
}
}
/* Header internals: Flexbox */
.header-nav {
display: flex;
align-items: center;
justify-content: space-between;
height: 100%;
padding: 0 1.5rem;
}
/* Card grid: Grid auto-fill (no media queries needed) */
.card-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
gap: 1.5rem;
}
/* Card internals: Flexbox */
.card {
display: flex;
flex-direction: column;
}
.card__body { flex: 1; } /* Equalizes height */Common Mistakes
Forcing a 2D grid layout with Flexbox resulting in uneven heights
For layouts requiring both rows and columns like card grids, use CSS Grid. Mimicking a grid with flex-wrap causes the last row items to stretch unevenly or have mismatched heights.
Not setting box-sizing: border-box causing layouts to break when adding padding
The default box-sizing is content-box, which adds padding and border to the width. Declare *, *::before, *::after { box-sizing: border-box; } globally.
Relying only on fixed px breakpoints causing awkward layouts at intermediate sizes
Using fluid CSS functions like clamp(), minmax(), and auto-fill enables smooth transitions between breakpoints. Only use media queries when the layout structure needs to fundamentally change.