Layout that adapts to the screen
The idea
CSS layout is like arranging objects on a table: the HTML supplies the objects, and CSS decides how they fit together. When the available space changes, the arrangement can change too. You will use Flexbox for a line of items, Grid for rows and columns, and CSS rules that respond to screen size, container size, or a visitor’s preferences.
For the examples, save the HTML as index.html and open it in a browser. Each example includes its own CSS in a <style> element so you can see the complete result in one place.
Flexbox
A flexible serving tray helps keep a row of dishes lined up even when one dish is wider than another. Flexbox does something similar: it arranges the children of one element along a main direction, either a row or a column. The parent becomes the flex container; its direct children become flex items.
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Desk toolbar</title>
<style>
.toolbar { display: flex; align-items: center; justify-content: space-between; gap: 1rem; border: 1px solid #555; padding: 1rem; }
</style>
</head>
<body>
<div class="toolbar"><strong>Library desk</strong><span>Closes at six</span></div>
</body>
</html>
The browser renders “Library desk” and “Closes at six” on one row. align-items lines them up across the row; justify-content spaces them along the row. The gap adds space between items. If the available space becomes too small, flex-wrap: wrap lets items move onto another row instead of overflowing.
Grid
A chessboard has rows and columns that line up in both directions. CSS Grid makes that kind of two-dimensional arrangement: the parent defines columns and rows, and its children occupy the resulting cells. A track is one row or column in the grid.
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Reference shelf</title>
<style>
.shelf { display: grid; grid-template-columns: repeat(3, 1fr); gap: 1rem; }
.card { padding: 1rem; background: #e5e7eb; }
</style>
</head>
<body>
<div class="shelf"><div class="card">Maps</div><div class="card">Plants</div><div class="card">Birds</div><div class="card">Rocks</div></div>
</body>
</html>
The shelf renders three equal columns. The fourth card starts a second row. repeat(3, 1fr) means “make three equal columns”; fr is a fraction of the space available to the grid. Use Grid when both row and column alignment matter, and Flexbox when arranging items along one direction is the main goal.
Mobile-first design and media queries
Packing for the smallest suitcase first means making sure the essentials fit before adding extras. Mobile-first design applies the same idea: write the default layout for a narrow screen, then add changes for wider screens. A media query applies CSS only when a condition, such as a minimum screen width, is true.
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Reading notes</title>
<style>
.notes { display: grid; grid-template-columns: 1fr; gap: .75rem; padding: 0; list-style: none; }
@media (min-width: 44rem) { .notes { grid-template-columns: repeat(3, 1fr); } }
</style>
</head>
<body>
<ul class="notes"><li>Maps</li><li>Plants</li><li>Birds</li><li>Rocks</li></ul>
</body>
</html>
On a narrow screen, the notes form one column. Once the screen is at least 44rem wide, the media query changes the list to three columns. The default rule comes first and works without a query; the wider-screen rule adds only the change that is needed.
Container queries and modern CSS
A storage cabinet’s shelves should adapt to the cabinet’s width, not to the size of the whole room. A container query lets an element’s style respond to the size of a marked ancestor called a container. This is useful when the same component can appear in both a narrow sidebar and a wide main area.
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Tool catalog</title>
<style>
.catalog { container-type: inline-size; }
.catalog__tool { padding: 1rem; font-size: clamp(1rem, 1.5vw, 1.25rem); }
.catalog__tool { & h2 { margin: 0; } &:has(.catalog__tag) { border-left: .3rem solid #7a4a00; } }
@container (min-width: 34rem) { .catalog__tool { display: grid; grid-template-columns: 8rem 1fr; gap: 1rem; } }
</style>
</head>
<body>
<div class="catalog"><div class="catalog__tool"><span class="catalog__tag">Staff pick</span><h2>Field guide</h2><p>Notes for a day outdoors.</p></div></div>
</body>
</html>
The tile has a warm border because it contains a catalog__tag. :has() matches an element based on something inside it. The nested & h2 rule styles a heading inside the tile; nesting keeps related rules together, and & stands for the selector being nested inside. clamp() keeps the text size between its minimum and maximum while allowing it to scale in between. When the catalog container reaches the query’s minimum width, the tile changes to two columns. Unlike a media query, this rule responds to the catalog’s width.
Responsive images and fluid type
A picture frame can be made smaller without changing the shape of the picture. For an image, setting a maximum width and keeping its height automatic lets it shrink with its parent without stretching. Responsive images can also offer several source sizes so the browser can choose one suited to the space and display.
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Landscape feature</title>
<style>
.field-image { display: block; max-width: 100%; height: auto; }
.feature-title { font-size: clamp(1.75rem, 4vw, 3rem); }
</style>
</head>
<body>
<h1 class="feature-title">Across the highlands</h1>
<img class="field-image" src="https://images.unsplash.com/photo-1500530855697-b586d89ba3ee?auto=format&fit=crop&w=960&q=80" srcset="https://images.unsplash.com/photo-1500530855697-b586d89ba3ee?auto=format&fit=crop&w=480&q=80 480w, https://images.unsplash.com/photo-1500530855697-b586d89ba3ee?auto=format&fit=crop&w=960&q=80 960w" sizes="(min-width: 60rem) 50vw, 100vw" width="960" height="640" alt="A wide mountain landscape">
</body>
</html>
The browser can select from the two srcset image widths; sizes tells it how wide the image is expected to appear at different screen widths. The width and height attributes describe the image’s shape before it loads. The image can shrink to fit the page without growing wider than its parent. The title’s clamp() value is fluid type: it scales with the viewport but stays within its stated limits.
Dark mode and reduced motion
A room can use a light or dark lamp setting without changing where the furniture sits. A site can do the same by checking the visitor’s color preference and changing its colors while leaving the layout alone. prefers-color-scheme is a media feature that reports whether the visitor prefers a light or dark color scheme.
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Reading panel</title>
<style>
:root { --paper: #fff; --ink: #202124; }
.panel { color: var(--ink); background: var(--paper); transition: background-color 180ms ease, color 180ms ease; padding: 1rem; }
@media (prefers-color-scheme: dark) { :root { --paper: #1e293b; --ink: #f8fafc; } }
@media (prefers-reduced-motion: reduce) { .panel { transition: none; } }
</style>
</head>
<body>
<div class="panel">Your reading place.</div>
</body>
</html>
In light mode, the panel uses a white background and dark text. In dark mode, the media query swaps those custom-property values. The transition animates the color change. prefers-reduced-motion reports that a visitor has asked for less motion; its query removes the transition so the color change does not animate.
Common mistakes
- Using Flexbox for a layout that needs several aligned rows and columns. Grid is usually a better fit for two-dimensional arrangements.
- Writing only a wide-screen rule. A narrow screen may then have no suitable layout.
- Using a media query when a component should respond to the width of its own container.
- Putting nested CSS rules outside their parent rule, or forgetting
&when writing a nested selector. - Setting an image width without keeping its proportions; this can stretch the image.
- Setting fluid text with only a preferred size and no lower or upper limit.
- Forcing a dark palette on every visitor instead of respecting their color preference.
- Keeping decorative transitions active for visitors who prefer reduced motion.
Try it
- Add a third item to the toolbar example and use Flexbox to keep the items spaced without changing their HTML order.
- Add two cards to the shelf example and observe how Grid places them in the next available cells.
- Change the reading-notes example so its narrow-screen layout stays in one column and its wider layout uses two columns. Check both sizes in the browser’s responsive viewport tools.
- Put the tool tile in a narrower and a wider catalog panel. Confirm the tile responds to the panel width, and compare its badge and heading rules.
- Resize the landscape page and observe the image and title. Try changing the available source widths in
srcsetand see which image the browser selects. - Change the operating system’s color-scheme and reduced-motion preferences, then reload the reading-panel example and check its colors and transition.