Mobile-first CSS: what it means and how to write it
Mobile-first CSS explained: min-width vs max-width media queries, base styles for 360px phones, converting desktop-first CSS, Tailwind and an AI prompt.
Mobile-first CSS means the styles outside any media query are your phone layout, and min-width media queries add to it as the screen gets wider. Desktop-first is the reverse: desktop styles by default, then max-width queries that undo them for phones. Mobile-first usually means less CSS to undo, safer failures, and it's how Tailwind and Bootstrap work.
min-width vs max-width
These two snippets give the same layout: one column with small gaps on phones, three columns from 768px.
/* Desktop-first */
.features { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 32px; }
@media (max-width: 767px) {
.features { grid-template-columns: 1fr; gap: 16px; }
}
/* Mobile-first */
.features { display: grid; gap: 16px; }
@media (min-width: 768px) {
.features { grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 32px; }
}
min-width: 768pxmeans "768px and wider". The edge is included.max-width: 767pxmeans "767px and narrower".
The width is the viewport in CSS pixels, not the phone's physical resolution (what is a viewport?).
So why prefer one? Look at what happens when you forget something. In desktop-first, every desktop feature needs a matching "undo" in the phone query. Forget one (a fixed width, a sticky sidebar, a row that should wrap) and it leaks onto phones, where it breaks things. In mobile-first, a forgotten rule means a laptop gets the simpler phone style for that element. It looks plain, but it works.
The 767.5px gap
Mixing max-width: 767px and min-width: 768px leaves a sliver where neither matches. Viewports can be fractional, for example when someone zooms, and a 767.5px window gets neither rule. Bootstrap writes its max-width queries as 767.98px for exactly this reason (Bootstrap: breakpoints).
The modern fix is range syntax, @media (width < 768px), which every major browser has supported since 2023 (MDN: using media queries). A purely mobile-first stylesheet sidesteps the problem entirely, because the base styles always apply.
Base styles: design at 360, check at 320
The base layout has to work on the narrowest phones people actually use:
| Screen | CSS size | Content width with 16px padding |
|---|---|---|
| Galaxy S25, S26 and other base Galaxy S models | 360×780 | 328px |
| iPhone SE (2nd and 3rd gen) | 375×667 | 343px |
| iPhone 13 mini | 375×812 | 343px |
Then there's 320. An iPhone SE with Display Zoom set to Larger Text reports 320×568, and a Samsung at its largest display size reports 320×694. 320px is also the width WCAG 1.4.10 Reflow (level AA) tests at, because it's what a 1280px laptop window becomes at 400% zoom. So: design at 360, and make sure nothing scrolls sideways at 320.
A mobile-first base usually looks like this:
*, *::before, *::after { box-sizing: border-box; }
body { margin: 0; line-height: 1.6; }
img, video { max-width: 100%; height: auto; }
.container { padding-inline: 16px; }
.features { display: grid; gap: 16px; } /* one column */
.button { display: block; width: 100%; min-height: 44px; }
.nav-links { display: none; } /* behind a menu button */
One column, side padding from the start, images that can't overflow, tap targets at least 44px tall, and text sized to fit a phone. Headings that scale smoothly instead of jumping are covered in responsive font sizes with clamp(), and the padding in mobile padding that works.
Enhance at each breakpoint
Now add layers, each one only adding what the extra width makes possible:
/* 40rem = 640px: phones on their side, opened foldables, tablets */
@media (min-width: 40rem) {
.features { grid-template-columns: repeat(2, minmax(0, 1fr)); }
.button { display: inline-block; width: auto; }
}
/* 64rem = 1024px: 13-inch iPads held upright and every laptop */
@media (min-width: 64rem) {
.layout { display: grid; grid-template-columns: 240px minmax(0, 1fr); gap: 32px; }
.features { grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 24px; }
}
At the default font size, 40rem is 640px and 64rem is 1024px. Using rem in media queries has a bonus: it means the browser's default font size, not whatever you set on html, so someone who bumps their default text size gets the roomier layout a little later (Media Queries spec).
Which layers each screen gets:
| Screen | Width | Base | 640+ | 1024+ | Result |
|---|---|---|---|---|---|
| Galaxy S25 | 360 | yes | – | – | 1 column |
| iPhone 17 Pro | 402 | yes | – | – | 1 column |
| iPhone SE on its side | 667 | yes | yes | – | 2 columns |
| Galaxy Z Fold 7, unfolded | 750 | yes | yes | – | 2 columns |
| iPad Air 11″ | 820 | yes | yes | – | 2 columns |
| MacBook Air 13″ | 1470 | yes | yes | yes | sidebar + 3 columns |
Enhancement isn't only layout. Hover effects belong inside @media (hover: hover), so touch screens never depend on them. And a component that sits in slots of different widths is better off with a container query than another breakpoint (container queries vs media queries).
Why AI tools often write desktop-first
If an AI built your site, there's a good chance the CSS is desktop-first. Some likely reasons:
- The prompt describes the desktop page. "Hero with text on the left, image on the right, three pricing cards in a row" is a desktop layout, and the phone version becomes an afterthought.
- The preview is a wide window. What gets checked is what looks right on a laptop.
- Lots of existing CSS is desktop-first. Older tutorials and templates often put desktop styles first and patch phones with
max-widthqueries.
How to spot it:
- Lots of
@media (max-width: …)blocks, each resetting widths, floats and flex directions. - In Tailwind, a trail of
max-md:andmax-sm:classes:flex-row max-sm:flex-col,w-[600px] max-md:w-full. sm:used as if it meant "on phones", likesm:text-smmeant to shrink text on mobile. It does the opposite.
The fix is to say it up front. There's a prompt at the end of this article, and more in 10 responsive mistakes in AI-built websites.
Converting a desktop-first stylesheet
You don't have to rewrite everything at once. Convert one component at a time, and keep each property going in one direction per element.
- List the max-width queries and their widths.
- Make the phone value the base. Whatever is inside the smallest max-width query becomes the default.
- Move the old desktop value into a min-width query at the old breakpoint plus one:
max-width: 767pxbecomesmin-width: 768px. - Delete the resets. Rules like
width: autoorfloat: nonethat only existed to undo desktop styles can go. - Check before and after at 360, 820 and 1470px. Nothing should change.
A worked example, a header and a sidebar layout:
/* Before: desktop-first */
.layout { display: flex; gap: 32px; }
.sidebar { width: 280px; flex-shrink: 0; }
.nav-links { display: flex; gap: 24px; }
.menu-button { display: none; }
@media (max-width: 1023px) {
.layout { flex-direction: column; gap: 16px; }
.sidebar { width: auto; }
}
@media (max-width: 767px) {
.nav-links { display: none; }
.menu-button { display: block; }
}
/* After: mobile-first */
.layout { display: flex; flex-direction: column; gap: 16px; }
.nav-links { display: none; }
.menu-button { display: block; }
@media (min-width: 768px) {
.nav-links { display: flex; gap: 24px; }
.menu-button { display: none; }
}
@media (min-width: 1024px) {
.layout { flex-direction: row; gap: 32px; }
.sidebar { width: 280px; flex-shrink: 0; }
}
Checking it:
| Screen | Width | Layout | Sidebar | Nav |
|---|---|---|---|---|
| Galaxy S25 | 360 | column, 16px gap | full width | menu button |
| iPad Air 11″ | 820 | column, 16px gap | full width | links |
| MacBook Air 13″ | 1470 | row, 32px gap | 280px | links |
Same result as before, with one rule less: .sidebar { width: auto; } is gone because the phone never gets the fixed width in the first place.
Tailwind is mobile-first
Tailwind works this way out of the box. Unprefixed classes apply at every size, and md:flex means "from 768px up" (Tailwind: responsive design). The docs warn about the most common mix-up: sm: means "at the small breakpoint and above", not "on small screens".
| Framework | Breakpoint | Min width | Real screens at that width or wider (portrait) |
|---|---|---|---|
| Tailwind CSS | sm | 640px | from Samsung Galaxy Z Fold 6 — unfolded (708px) up |
| Tailwind CSS | md | 768px | from iPad 9.7″ / mini (classic) (768px) up |
| Tailwind CSS | lg | 1024px | from iPad Air 13″ (M2–M4) (1024px) up |
| Tailwind CSS | xl | 1280px | from 13–14″ WUXGA 1920×1200 @ 150% (1280px) up |
| Tailwind CSS | 2xl | 1536px | from 15.6″ / 17″ Full HD @ 125% (1536px) up |
| Bootstrap 5 | sm | 576px | from Samsung Galaxy Z Fold 6 — unfolded (708px) up |
| Bootstrap 5 | md | 768px | from iPad 9.7″ / mini (classic) (768px) up |
| Bootstrap 5 | lg | 992px | from iPad Air 13″ (M2–M4) (1024px) up |
| Bootstrap 5 | xl | 1200px | from 13–14″ WUXGA 1920×1200 @ 150% (1280px) up |
| Bootstrap 5 | xxl | 1400px | from MacBook Neo 13″ (1408px) up |
Converting desktop-first Tailwind follows the same steps:
<!-- Desktop-first -->
<div class="grid grid-cols-3 gap-8 max-md:grid-cols-1 max-md:gap-4">…</div>
<h1 class="text-6xl max-sm:text-4xl">…</h1>
<!-- Mobile-first: same result -->
<div class="grid grid-cols-1 gap-4 md:grid-cols-3 md:gap-8">…</div>
<h1 class="text-4xl sm:text-6xl">…</h1>
And the classic bug: sm:hidden doesn't hide something on phones, it hides it from 640px up. To show something only on phones, use md:hidden; to show it only from 768px, hidden md:block. No phone held upright reaches sm at all; the full map is in Tailwind breakpoints on real devices.
max-* variants still have a place, for a small tweak that should only touch narrow screens (max-[380px]:text-sm). Just don't build the whole layout out of them.
Does your site fit?
Paste your address (or localhost:3000). doesitfit shows it on real screen sizes at once and tells you, bluntly, where it spills.
Free, no sign-up. Or ask Claude, ChatGPT or Codex to check it.
A conversion is only safe if nothing moved. In doesitfit you can load a 360px Galaxy, a 375px iPhone SE, an iPad and a laptop together before and after the change, and any screen where the page now scrolls sideways gets a SPILLING sticker with a Show me where button.
A prompt for your AI tool
Convert [this stylesheet / this component] to mobile-first. The styles outside any media query must be the phone layout and must work at 360px wide without sideways scrolling, and still fit at 320px. Move every desktop value into min-width media queries at the existing breakpoints (max-width: 767px becomes min-width: 768px), delete the max-width queries, and drop any rule that only existed to undo a desktop style. In Tailwind: unprefixed classes are the phone styles, sm:/md:/lg: add to them, and replace max-* variants the same way. The page must look identical at 360, 820 and 1470px wide. List every rule you moved.
For new pages, put this at the start of your first prompt instead: "Build mobile-first: base styles for a 360px-wide phone, min-width media queries (or sm:/md:/lg: in Tailwind) only to enhance wider screens." More ready-made prompts are in make your AI website mobile friendly.
The short version
- Mobile-first: phone styles by default,
min-widthqueries add to them. Desktop-first: the reverse, withmax-widthqueries. - Forgotten rules fail safely in mobile-first: a laptop gets a plain element instead of a phone getting a broken one.
- Design the base at 360px (base Galaxy S phones) and 375px (iPhone SE), and check nothing breaks at 320px.
- Add a layer per breakpoint, only where the content needs it. Two or three is plenty (responsive breakpoints in 2026).
- Tailwind is already mobile-first: unprefixed means phones, and
sm:is not "small screens". - AI tools often write desktop-first. Ask for mobile-first by name, with a width.
Questions people ask
What does mobile-first mean in CSS?
Your base styles, the ones outside any media query, are the phone layout, and min-width media queries add changes for wider screens. Desktop-first is the reverse: desktop styles by default and max-width queries that undo them for phones.
Is Tailwind CSS mobile-first?
Yes. Classes without a prefix apply at every screen size, and prefixed classes like md:flex apply from that breakpoint up. Write the phone style without a prefix and add sm:, md: and lg: for wider screens.
Should I use min-width or max-width media queries?
Mostly min-width. A max-width query is fine for a small fix that should only touch narrow screens, but using both directions for the same property on the same element makes overlaps and gaps easy to create.
What width should I design the mobile layout for?
360px, the width of the base Samsung Galaxy S phones, then check that nothing breaks at 320px. That's where people with larger display or text settings end up, and it's the width WCAG's Reflow criterion tests at.
How do I get an AI tool to write mobile-first CSS?
Say it explicitly: base styles for a 360px phone with no media query, only min-width queries (or unprefixed Tailwind classes plus sm:, md: and lg:) for wider screens, and a check at 360px before any desktop width.