Responsive design: mobile-first CSS
What the mobile-first CSS approach is and how to use media queries to keep a single interface looking right across different screen sizes.
For a while I worked with a “design for desktop first, then adapt down” mindset. That meant building a layout that looked good on wide screens, then trying to break it apart with media queries for smaller ones. The result was usually a pile of overriding rules and style conflicts that were a nightmare to trace.
The mobile-first approach flips this: you write the base styles for small screens first, then layer on additional rules for larger ones. That single change makes CSS noticeably easier to write and maintain.
Writing media queries in the right direction
The desktop-first approach uses max-width — “if the screen is this small, do this”:
/* Desktop-first — not recommended */
.nav {
display: flex;
}
@media (max-width: 768px) {
.nav {
display: block;
}
}
The mobile-first approach uses min-width — “if the screen is at least this wide, do this”:
/* Mobile-first */
.nav {
display: block;
}
@media (min-width: 768px) {
.nav {
display: flex;
}
}
The base style is written for mobile. Larger screens get additional rules on top. This is also cleaner from a CSS specificity standpoint: small-screen styles live outside any media query, and larger screens receive an extra layer.
Breakpoints
While they vary by design, the values popularised by Bootstrap 3 and 4 have become industry reference points:
/* Small devices — 576px and up */
@media (min-width: 576px) { ... }
/* Medium devices — 768px and up */
@media (min-width: 768px) { ... }
/* Large devices — 992px and up */
@media (min-width: 992px) { ... }
/* Extra large — 1200px and up */
@media (min-width: 1200px) { ... }
Rather than hard-coding these values throughout your stylesheets, storing them in Sass variables makes maintenance much easier:
$breakpoint-sm: 576px;
$breakpoint-md: 768px;
$breakpoint-lg: 992px;
@media (min-width: $breakpoint-md) {
.container {
max-width: 720px;
}
}
When a value changes, there is only one place to update it.
Working with a flexible grid
To build a grid system the mobile-first way, I start columns at full width by default:
.col {
width: 100%; /* Each column spans the full row on mobile */
padding: 0 15px;
box-sizing: border-box;
}
@media (min-width: 768px) {
.col-md-6 {
width: 50%; /* Two columns side by side on tablet */
}
.col-md-4 {
width: 33.333%;
}
}
With Flexbox or CSS Grid this requires far less code, but the core idea is the same: single column on small screens, switching to a horizontal layout on larger ones.
Keeping images flexible
Fixed-width images overflow their containers on small screens. A simple two-line rule fixes that:
img {
max-width: 100%;
height: auto;
}
This prevents an image from growing wider than its container and preserves the aspect ratio. I include it in the global stylesheet of every mobile-first project.
The viewport meta tag
No matter how correctly the CSS is written, without the viewport meta tag in the HTML <head>, mobile browsers will shrink the page down as if it were a desktop-width document:
<meta name="viewport" content="width=device-width, initial-scale=1">
Forgetting this line invalidates all your media query work. Early on, I spent hours unable to figure out why nothing was working — that was the culprit.
Testing on real devices
The device simulator in browser developer tools is not enough. It correctly reproduces screen dimensions, but it does not simulate touch events, scroll inertia, or the actual pixel density of real hardware. On one project, a navigation menu that looked perfect in the simulator was too small to tap on a real phone — I had overlooked touch target sizing.
A practical rule for touch targets: interactive elements should be at least 44×44 pixels. It is easy to miss this on small screens; the spacing between list items in particular tends to fall short.
The real benefit of mobile-first
Mobile-first also means writing less CSS — because the base styles are built for the least feature-rich context (mobile), and larger screens progressively add to them. Instead of stacking override after override, you end up with a structure that grows incrementally.
When I worked desktop-first, I noticed the pattern: after writing the large-screen styles, I had to “undo” them for smaller screens — display: none, width: auto, margin: 0 reset rules kept multiplying. Mobile-first eliminates those resets; when you build in the right direction from the start, you never have to take a step back.
As a developer without a dedicated designer, this approach gives me one more benefit: it forces me to think about the simplest version first, then scale up. Deciding what needs to appear on a small screen clarifies what the page’s truly important content is. You can add visual polish on larger screens; the mobile constraint forces you to prioritise.
Comments
Sign in with your GitHub account to join the discussion. Comments are stored in GitHub Discussions.