Responsive Design Without Media Queries
Media queries and modern CSS layouts like flexbox and grid have long been the foundation of responsive design. But they’re not the only tools available. A range of native HTML and CSS features can handle responsiveness naturally—and often more efficiently—than a pile of breakpoint rules. These features work whether or not media queries are involved, and in many cases, media queries become a complement rather than the primary mechanism.
Art-Directing Images With <picture>
The old habit of slapping width: 100% on an image and moving on still makes images fluid, but it comes with real costs. Images can squish to the point of losing their focal point, and small devices end up downloading full-size files that hurt performance. The solution is to serve the right resolution for the right screen: large, high-resolution images to large screens, and smaller variations to smaller ones.
The <picture> element lets you define exactly which image resource renders under which conditions. Instead of shipping one large image to every viewport and scaling it down, you provide a set of candidates:
<picture>
<source media="(max-width:1000px)">
<source media="(max-width:600px)">
<source media="(max-width:400px)">
<img src="picture.png" alt="picture"">
</picture>
In that example, picture.png is the original full-size image, with picture-lg.png and picture-sm.png as progressively smaller versions. Media queries are still present, but it’s the <picture> element driving behavior rather than breakpoints in the stylesheet. The mapping goes like this:
- Viewports 1000px and above get
picture.png. - Viewports between 601px and 999px get
picture-lg.png. - Viewports 600px and below get
picture-sm.png.
You can also tag each source with an image density (e.g., 1x, 2x, 3x) after the URL, provided the images are proportional to each other. The browser then picks the version based on both viewport size and pixel density:
<picture>
<source media="(max-width:1000px)">
<source media="(max-width:600px)">
<source media="(max-width:400px)">
<img src="picture.png" alt="picture"">
</picture>
Two nested tags matter inside <picture>: <source> and <img>. The browser walks through the <source> elements looking for the first whose media query matches the current viewport, then loads the image named in its srcset attribute. The <img> element is required as the last child, serving as fallback if no <source> matches:

For simpler cases, you can get responsive behavior with srcset on an <img> element alone, using density descriptors:
<img
src="flower-fallback.jpg"
>
Another approach is moving resolution-based media queries into CSS. Rather than checking viewport width alone, you can query the device’s screen resolution in dots per inch. Instead of this:
@media only screen and (max-width: 600px) {
/* Style stuff */
}
you write this:
@media only screen and (min-resolution: 192dpi) {
/* Style stuff */
}
That lets you serve high-quality images to high-DPI screens and smaller versions to lower-resolution displays. One caveat: mobile devices often have small screens but high resolutions, so resolution alone can result in sending heavy images to tiny displays. The source’s example shows how to pair what <picture> offers with resolution queries:
body {
background-image : picture-md.png; /* the default image */
}
@media only screen and (min-resolution: 192dpi) {
body {
background-image : picture-lg.png; /* higher resolution */
}
}
Beyond choosing the file, <picture> enables art direction—crop decisions that preserve the focal point. CSS’s object-fit and object-position properties let you crop images while maintaining aspect ratio:
@media only screen and (min-resolution: 192dpi) {
body {
background-image : picture-lg.png;
object-fit: cover;
object-position: 100% 150%; /* moves focus toward the middle-right */
}
}
Fluid Values With min(), max(), and clamp()
CSS functions now allow sizes to be fluid without a single breakpoint. min() sets the absolute smallest size an element can shrink to, which is useful for keeping fluid type legible:
html {
font-size: min(1rem, 22px); /* Stays between 16px and 22px */
}
min() takes two values—relative, percentage, or fixed. In the source’s example, the browser never lets a .box element go below 45% width or 600px, whichever is smaller at the current viewport width:
.box {
width : min(45%, 600px)
}
If 45% computes to less than 600px, the element is 45% wide; once 45% grows beyond 600px, 600px becomes the constraint. Conversely, max() defines the largest size an element can reach:
.box {
width : max(60%, 600px)
}
If 60% computes to more than 600px, the browser uses 60%; otherwise it defaults to 600px.
For clamped ranges, clamp() combines both functions into one declaration, accepting three parameters: the minimum value, the preferred value, and the maximum value. This example sets a minimum of 1rem, a preferred of 2vw, and a maximum of 4rem:
.box {
font-size : clamp(1rem, 40px, 4rem)
}
The browser increases font size along with the preferred value up to 4rem, then stops—all without media queries.
Responsive Units and Their Behavior
Large headings can look excellent on desktop but oversized on mobile. Choosing the right units for sizing is a big part of the fix. In CSS, px is an absolute unit, fixed and independent of other elements on the page. Relative units—%, em, rem, vw, and vh—scale across screen sizes:
vw: relative to the viewport widthvh: relative to the viewport heightrem: relative to the root (<html>) font size, normally 16px by defaultem: relative to the parent element’s font size%: relative to the parent element
Because most browsers default to a 16px root, rem multiplies that base by the number you declare:
.8rem = 12.8px (.8 * 16)
1rem = 16px (1 * 16)
2rem = 32px (2 * 16)
If the user changes the browser’s default font size—or you change the root size within CSS—the values scale accordingly, and the entire page scales rather than breaking layout. For instance, changing the root to 10px recalculates every rem-based size:
html {
font-size : 10px;
}
1rem = 10px (1 * 10)
2rem = 20px (2 * 10)
.5rem = 5px (.5 * 10)
The same logic applies to % values:
100% = 16px;
200% = 32px;
50% = 8px;
The difference between rem and em comes down to the reference element. rem always calculates against the root <html> font size, while em resolves against the parent element’s font size. When those two reference sizes differ—a 16px root but an 18px parent—em and rem produce different computed values. That distinction gives finer control across responsive contexts.
For viewport units, 100vh equals 100% of the viewport height (device behavior varies) and 100vw equals 100% of the viewport width.
Beyond Breakpoints
These HTML and CSS features don’t replace the responsive techniques you already use. They add more granular control over how elements behave across font sizes, screen resolutions, widths, and focal points. When a design needs finer detail on specific devices, it’s worth checking what native HTML and CSS can handle first. The toolset has expanded considerably.



