liminfo

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

CSS responsive layoutFlexbox layoutCSS Grid layoutresponsive designmedia queriescontainer queriesresponsive web designCSS grid auto-fill

Problem

You need to build a dashboard page for a web application. It must include a sidebar navigation, header, main content area, and a card grid. On mobile, the sidebar should collapse and cards should stack in a single column. On tablet, cards should arrange in 2 columns, and on desktop 3-4 columns, with card heights automatically adjusting based on content. You need to systematically determine where to use Flexbox vs Grid, and how to write the media queries.

Required Tools

CSS Flexbox

A CSS module optimized for one-dimensional (row or column) layouts. Ideal for navigation bars, card internal alignment, and centering.

CSS Grid

A CSS module for controlling two-dimensional (row and column) layouts simultaneously. Best for full page layouts and card grid placement.

Media Queries

A CSS feature that applies different styles based on viewport size. Essential for breakpoint-based layout transitions.

Container Queries

A modern CSS feature that changes styles based on the parent container size. Useful for designing reusable components.

CSS Custom Properties

CSS variables used to consistently manage spacing, column counts, and other values across breakpoints.

Solution Steps

1

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;
}
2

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;
  }
}
3

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;
}
4

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);
}
5

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%;
  }
}
6

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.

Related liminfo Services