Which media query breakpoints should I use?
There is no universal set of breakpoints. Learn how to pick them, a sensible mobile-first starting set, and how fluid layouts and container queries need fewer.
There is no correct set of breakpoints. Add a breakpoint where your layout starts to look broken, not where a particular device begins. If you need a starting point, 640, 768, 1024 and 1280px work well for most sites, written mobile first with min-width:
.products {
display: grid;
gap: 1rem;
}
@media (min-width: 640px) {
.products {
grid-template-columns: repeat(2, 1fr);
}
}
@media (min-width: 1024px) {
.products {
grid-template-columns: repeat(3, 1fr);
}
}Those four values are Tailwind's defaults for sm, md, lg and xl (its 2xl is 1536px). They're a reasonable shared vocabulary for a team, but you don't have to use all of them on every component. Here the product grid only needs two.
Why not device widths
Don't target an iPhone width like 390px. There are hundreds of screen sizes, new ones every year, and on top of that tablets in split screen, foldables and desktop browser windows at any size. A layout built around specific devices breaks on the next one.
Instead, open the page, drag the window from narrow to wide, and add a breakpoint where something goes wrong: lines of text get too long, cards get too squeezed, or the navigation wraps. That's also why breakpoints can differ between components.
Mobile first with min-width
Write the base styles for small screens with no media query, then add min-width queries that add complexity as space grows. Small screens get the simplest CSS, and you mostly add columns rather than undo them.
With max-width (desktop first), you write the full layout and then undo parts of it for smaller screens, which usually means more overrides.
Range syntax
Modern browsers support a range syntax that reads like math:
@media (width >= 768px) {
/* 768px and wider */
}
@media (640px <= width < 1024px) {
/* between two breakpoints */
}It also fixes an old off-by-one problem. With max-width: 767px and min-width: 768px, a window that is 767.5px wide (it happens with zoom and high-density screens) matches neither query. width < 768px and width >= 768px leave no gap. Browser support is good in current browsers; see Can I Use if you need older ones.
px or em
You'll see breakpoints written in em, like @media (min-width: 48em). In media queries, em and rem are relative to the browser's default font size, not the font-size you set on html. With the default 16px, 48em equals 768px.
The benefit is for people who increase the default font size in their browser settings: the layout switches to the narrower version earlier, before the bigger text gets squeezed. With px, that doesn't happen. Either works; just don't expect html { font-size: 62.5% } to change your em breakpoints.
One more gotcha: CSS custom properties don't work inside media queries, so @media (min-width: var(--md)) is ignored. Keep breakpoint values in a Sass variable, your Tailwind config, or just repeat the number.
Fewer breakpoints with fluid layouts
Many breakpoints only exist to change a size in steps. Modern CSS can do that fluidly.
Font sizes that grow with the screen, between a minimum and a maximum:
h1 {
font-size: clamp(2rem, 1.5rem + 2.5vw, 3.5rem);
}A grid that adds columns when there's space, with no media query at all:
.products {
display: grid;
grid-template-columns: repeat(
auto-fit,
minmax(min(16rem, 100%), 1fr)
);
gap: 1rem;
}The min(16rem, 100%) stops the grid from overflowing on very narrow screens.
Container queries for components
A media query looks at the viewport, but a card might sit in a wide main column on one page and a narrow sidebar on another. Container queries let the component respond to the space it actually has:
.card-list {
container-type: inline-size;
}
@container (width >= 30rem) {
.card {
display: grid;
grid-template-columns: 8rem 1fr;
}
}The .card switches to a horizontal layout when .card-list is at least 30rem wide, whatever the screen size. Container queries are supported in all major browsers. I use media queries for the page layout and container queries for reusable components.
Detect capabilities, not "mobile"
Width doesn't tell you whether someone uses a mouse or a finger. A large tablet is wide and touch-only, and a laptop can have a touch screen. Ask about the input directly:
@media (hover: hover) {
.card:hover {
box-shadow: 0 4px 16px rgb(0 0 0 / 0.12);
}
}
@media (pointer: coarse) {
.toolbar button {
min-height: 44px;
}
}(hover: hover) keeps hover effects away from touch screens, where they get stuck after a tap. (pointer: coarse) matches when the main input is imprecise, like a finger, so you can make tap targets bigger.