{"id":9317,"date":"2026-04-15T13:51:41","date_gmt":"2026-04-15T18:51:41","guid":{"rendered":"https:\/\/frontendmasters.com\/blog\/?p=9317"},"modified":"2026-04-15T13:51:43","modified_gmt":"2026-04-15T18:51:43","slug":"building-a-ui-without-breakpoints","status":"publish","type":"post","link":"https:\/\/frontendmasters.com\/blog\/building-a-ui-without-breakpoints\/","title":{"rendered":"Building a UI Without Breakpoints"},"content":{"rendered":"\n<p>Breakpoints have dominated responsive design since we started building for the web. The model is familiar, convenient, teachable, and still widely used in production systems today.<\/p>\n\n\n\n<p>While breakpoints were an excellent answer to a real problem when multiple screen sizes emerged, modern interfaces are no longer page-first. They are component-first, nested, and reused across wildly different contexts. In that world, global viewport width is frequently the wrong input for local layout decisions.<\/p>\n\n\n\n<p>This article proposes a different approach, one that better fits the modern web: build fluid, intrinsic components that adapt by default, and treat conditional rules as local, intentional exceptions.<\/p>\n\n\n\n<p class=\"learn-more\">We should still use media queries, but mostly for real device capabilities and user preferences, not as the primary layout engine. We will return to that part later.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"what-breakpoints-solved-and-why-they-won\"><strong>What Breakpoints Solved, and Why They Won<\/strong><\/h2>\n\n\n\n<p>Breakpoints were a major improvement over fixed desktop layouts. They gave teams a practical way to support phones, tablets, and desktops with explicit CSS branches.<\/p>\n\n\n\n<p>The model was simple:<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li>Pick important viewport widths.<\/li>\n\n\n\n<li>Define how layout and sizing change at those points.<\/li>\n\n\n\n<li>Keep adding rules as product complexity grows.<\/li>\n<\/ol>\n\n\n\n<p>That simplicity is exactly why breakpoints became standard.<\/p>\n\n\n\n<p>Here is a very typical pattern you can still find in many codebases:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-1\" data-shcb-language-name=\"CSS\" data-shcb-language-slug=\"css\"><span><code class=\"hljs language-css\"><span class=\"hljs-selector-class\">.page<\/span> {\n  <span class=\"hljs-attribute\">width<\/span>: <span class=\"hljs-number\">90%<\/span>;\n  <span class=\"hljs-attribute\">font-size<\/span>: <span class=\"hljs-number\">16px<\/span>;\n}\n\n<span class=\"hljs-selector-class\">.card-grid<\/span> {\n  <span class=\"hljs-attribute\">display<\/span>: grid;\n  <span class=\"hljs-attribute\">gap<\/span>: <span class=\"hljs-number\">1em<\/span>;\n  <span class=\"hljs-attribute\">grid-template-columns<\/span>: <span class=\"hljs-number\">1<\/span>fr;\n}\n\n<span class=\"hljs-keyword\">@media<\/span> (<span class=\"hljs-attribute\">min-width:<\/span> <span class=\"hljs-number\">768px<\/span>) {\n  <span class=\"hljs-selector-class\">.card-grid<\/span> {\n    <span class=\"hljs-attribute\">grid-template-columns<\/span>: <span class=\"hljs-built_in\">repeat<\/span>(<span class=\"hljs-number\">2<\/span>, <span class=\"hljs-number\">1<\/span>fr);\n  }\n}\n\n<span class=\"hljs-keyword\">@media<\/span> (<span class=\"hljs-attribute\">min-width:<\/span> <span class=\"hljs-number\">1024px<\/span>) {\n  <span class=\"hljs-selector-class\">.page<\/span> {\n    <span class=\"hljs-attribute\">width<\/span>: <span class=\"hljs-number\">920px<\/span>;\n    <span class=\"hljs-attribute\">font-size<\/span>: <span class=\"hljs-number\">18px<\/span>;\n  }\n\n  <span class=\"hljs-selector-class\">.card-grid<\/span> {\n    <span class=\"hljs-attribute\">grid-template-columns<\/span>: <span class=\"hljs-built_in\">repeat<\/span>(<span class=\"hljs-number\">3<\/span>, <span class=\"hljs-number\">1<\/span>fr);\n  }\n}\n\n<span class=\"hljs-keyword\">@media<\/span> (<span class=\"hljs-attribute\">min-width:<\/span> <span class=\"hljs-number\">1280px<\/span>) {\n  <span class=\"hljs-selector-class\">.card-grid<\/span> {\n    <span class=\"hljs-attribute\">grid-template-columns<\/span>: <span class=\"hljs-built_in\">repeat<\/span>(<span class=\"hljs-number\">4<\/span>, <span class=\"hljs-number\">1<\/span>fr);\n  }\n}\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-1\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">CSS<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">css<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>There is nothing inherently wrong with this style. The challenge appears at scale, where dozens of components with repeated viewport branches quickly increase CSS size, complicate overrides, and increase coupling between unrelated parts.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"why-breakpoint-first-layout-is-aging-out\">Why Breakpoint-First Layout Is Aging Out<\/h2>\n\n\n\n<p>With all due respect to breakpoints, the environment we build for today is very different. The growing variety of screen types, sizes, and pixel densities raises a practical question: which breakpoints should we even target, and which legacy thresholds remain meaningful? As device diversity continues to expand, breakpoint selection becomes less a stable system and more a moving guess.<\/p>\n\n\n\n<p>At the same time, we now know that layout logic is not always best tied to screen width. It usually works better when it is tied to component context. The same component can appear in a full-width feed, a narrow sidebar, a modal, and a dashboard tile, often in the same app. When components live in different containers, viewport-based rules can produce mismatched behavior, force unnecessary exceptions, and make component reuse less predictable.<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img data-recalc-dims=\"1\" loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"683\" src=\"https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2026\/04\/s_124E0F4150208DDF98EE944AA8CF94692A7C4F345845A0771B8E6D5804D20A4E_1775563466437_image.png?resize=1024%2C683&#038;ssl=1\" alt=\"The same interface shown across multiple devices with inconsistent layouts.\" class=\"wp-image-9321\" srcset=\"https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2026\/04\/s_124E0F4150208DDF98EE944AA8CF94692A7C4F345845A0771B8E6D5804D20A4E_1775563466437_image.png?resize=1024%2C683&amp;ssl=1 1024w, https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2026\/04\/s_124E0F4150208DDF98EE944AA8CF94692A7C4F345845A0771B8E6D5804D20A4E_1775563466437_image.png?resize=300%2C200&amp;ssl=1 300w, https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2026\/04\/s_124E0F4150208DDF98EE944AA8CF94692A7C4F345845A0771B8E6D5804D20A4E_1775563466437_image.png?resize=768%2C512&amp;ssl=1 768w, https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2026\/04\/s_124E0F4150208DDF98EE944AA8CF94692A7C4F345845A0771B8E6D5804D20A4E_1775563466437_image.png?w=1536&amp;ssl=1 1536w\" sizes=\"auto, (max-width: 1000px) 100vw, 1000px\" \/><\/figure>\n\n\n\n<p>And when we look at day-to-day implementation, we see that most responsive changes are not true structural pivots. There are gradual shifts in space negotiation, density, typography, and rhythm. A breakpoint-first mindset forces many of those shifts into discrete jumps, often leading to repeated logic, long override chains, and fragile interactions when a tweak to one breakpoint unexpectedly affects another component.<\/p>\n\n\n\n<p>The good news is that modern CSS gives us better primitives than we had before. If we make those primitives our baseline, we can build interfaces that stay fully responsive with less branching, cleaner code, and far more predictable behavior over time.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"method-1-intrinsic-layouts-first\"><strong>Method 1: Intrinsic Layouts First<\/strong><\/h2>\n\n\n\n<p>The first move is to push adaptation into layout primitives themselves. Instead of saying, &#8220;at width X, force N columns,&#8221; define constraints and let the browser derive the layout continuously.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">The Ubiquitous Card Grid<\/h3>\n\n\n\n<p>A good example is a common card grid, similar to the code snippet above. Instead of hardcoding column counts at breakpoints, we can use&nbsp;<code>auto-fit<\/code>&nbsp;and&nbsp;<code>minmax()<\/code>&nbsp;to create a grid that naturally fills available space while respecting a minimum card width.<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-2\" data-shcb-language-name=\"CSS\" data-shcb-language-slug=\"css\"><span><code class=\"hljs language-css\"><span class=\"hljs-selector-class\">.grid<\/span> {\n  <span class=\"hljs-comment\">\/* Basic card grid styles *\/<\/span>\n  <span class=\"hljs-attribute\">display<\/span>: grid;\n  <span class=\"hljs-attribute\">gap<\/span>: <span class=\"hljs-number\">1em<\/span>;\n}\n\n<span class=\"hljs-selector-class\">.with-breakpoints<\/span> {\n  <span class=\"hljs-comment\">\/* Traditional breakpoint-based grid *\/<\/span>\n  <span class=\"hljs-attribute\">grid-template-columns<\/span>: <span class=\"hljs-number\">1<\/span>fr;\n\n  @media (<span class=\"hljs-attribute\">min-width<\/span>: <span class=\"hljs-number\">720px<\/span>) {\n    grid-template-columns: <span class=\"hljs-built_in\">repeat<\/span>(<span class=\"hljs-number\">2<\/span>, <span class=\"hljs-number\">1<\/span>fr);\n  }\n\n  <span class=\"hljs-keyword\">@media<\/span> (<span class=\"hljs-attribute\">min-width:<\/span> <span class=\"hljs-number\">1024px<\/span>) {\n    <span class=\"hljs-selector-tag\">grid-template-columns<\/span>: <span class=\"hljs-selector-tag\">repeat<\/span>(3, 1<span class=\"hljs-selector-tag\">fr<\/span>);\n  }\n\n  <span class=\"hljs-keyword\">@media<\/span> (<span class=\"hljs-attribute\">min-width:<\/span> <span class=\"hljs-number\">1360px<\/span>) {\n    <span class=\"hljs-selector-tag\">grid-template-columns<\/span>: <span class=\"hljs-selector-tag\">repeat<\/span>(4, 1<span class=\"hljs-selector-tag\">fr<\/span>);\n  }\n}\n\n<span class=\"hljs-selector-class\">.with-auto-fit<\/span> {\n  <span class=\"hljs-comment\">\/* Intrinsic grid without breakpoints *\/<\/span>\n  <span class=\"hljs-attribute\">grid-template-columns<\/span>: <span class=\"hljs-built_in\">repeat<\/span>(auto-fit, minmax(<span class=\"hljs-number\">320px<\/span>, <span class=\"hljs-number\">1<\/span>fr));\n}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-2\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">CSS<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">css<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<div class=\"wp-block-cp-codepen-gutenberg-embed-block cp_embed_wrapper\"><iframe id=\"cp_embed_OPRwYVQ\/f4d678bd98a27f241eaefacb099414c8\" src=\"\/\/codepen.io\/anon\/embed\/OPRwYVQ\/f4d678bd98a27f241eaefacb099414c8?height=650&amp;theme-id=1&amp;slug-hash=OPRwYVQ\/f4d678bd98a27f241eaefacb099414c8&amp;default-tab=result\" height=\"650\" scrolling=\"no\" frameborder=\"0\" allowfullscreen allowpaymentrequest name=\"CodePen Embed OPRwYVQ\/f4d678bd98a27f241eaefacb099414c8\" title=\"CodePen Embed OPRwYVQ\/f4d678bd98a27f241eaefacb099414c8\" class=\"cp_embed_iframe\" style=\"width:100%;overflow:hidden\">CodePen Embed Fallback<\/iframe><\/div>\n\n\n\n<p>(<a href=\"https:\/\/codepen.io\/amit_sheen\/pen\/OPRwYVQ\/f4d678bd98a27f241eaefacb099414c8\">Open the demo in a new tab<\/a> and resize the window to see how the grid adapts.)<\/p>\n\n\n\n<p>As you can see, we easily get an identical result without any media queries. By using an intrinsic grid, we removed breakpoints, saved CSS lines, and reduced override complexity. Better yet, the result is also more semantic and intentional. It&#8217;s as if we&#8217;re saying: &#8220;Make as many columns as fit, but never let them get smaller than 320px&#8221;. The browser handles the rest.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Two-Region Flexible Layout<\/h3>\n\n\n\n<p>A similar pattern applies to two-region layouts, like a&nbsp;<code>sidebar<\/code>&nbsp;and&nbsp;<code>main<\/code>&nbsp;content. Instead of switching from single-column to multi-column at a breakpoint, as many of us do, we can define a flexible layout that simply adapts.<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-3\" data-shcb-language-name=\"CSS\" data-shcb-language-slug=\"css\"><span><code class=\"hljs language-css\"><span class=\"hljs-selector-tag\">body<\/span> {\n  <span class=\"hljs-attribute\">display<\/span>: flex;\n  <span class=\"hljs-attribute\">flex-wrap<\/span>: wrap;\n}\n\n<span class=\"hljs-selector-tag\">main<\/span> {\n  <span class=\"hljs-comment\">\/* main gets priority + readable minimum *\/<\/span>\n  <span class=\"hljs-attribute\">flex<\/span>: <span class=\"hljs-built_in\">calc<\/span>(infinity) <span class=\"hljs-number\">1<\/span> <span class=\"hljs-number\">360px<\/span>;\n}\n\n<span class=\"hljs-selector-tag\">aside<\/span> {\n  <span class=\"hljs-comment\">\/* set sidebar idle size *\/<\/span>\n  <span class=\"hljs-attribute\">flex<\/span>: <span class=\"hljs-number\">1<\/span> <span class=\"hljs-number\">1<\/span> <span class=\"hljs-number\">240px<\/span>;\n}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-3\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">CSS<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">css<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<div class=\"wp-block-cp-codepen-gutenberg-embed-block cp_embed_wrapper\"><iframe id=\"cp_embed_dPpjEOv\/6896f83a17a23d0f92099c3d7ad40b83\" src=\"\/\/codepen.io\/anon\/embed\/dPpjEOv\/6896f83a17a23d0f92099c3d7ad40b83?height=450&amp;theme-id=1&amp;slug-hash=dPpjEOv\/6896f83a17a23d0f92099c3d7ad40b83&amp;default-tab=result\" height=\"450\" scrolling=\"no\" frameborder=\"0\" allowfullscreen allowpaymentrequest name=\"CodePen Embed dPpjEOv\/6896f83a17a23d0f92099c3d7ad40b83\" title=\"CodePen Embed dPpjEOv\/6896f83a17a23d0f92099c3d7ad40b83\" class=\"cp_embed_iframe\" style=\"width:100%;overflow:hidden\">CodePen Embed Fallback<\/iframe><\/div>\n\n\n\n<p>This pattern encodes intent directly and replaces hard switches with a smoother layout flow.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"method-2-use-fluid-values\">Method 2: Use Fluid Values<\/h2>\n\n\n\n<p>Many &#8220;responsive&#8221; rules are just scalar tuning. Typography, spacing, radius, and component size can usually be continuous instead of stepped.<\/p>\n\n\n\n<p>You can use&nbsp;<code>min()<\/code>&nbsp;and&nbsp;<code>max()<\/code>&nbsp;for continuous values, but&nbsp;<code>clamp()<\/code>&nbsp;is especially useful here because it gives you both a safe minimum and a safe maximum, with a fluid middle range.<\/p>\n\n\n\n<p class=\"learn-more\">I will not go too deep into the math behind&nbsp;<code>clamp()<\/code>&nbsp;here since there are <a href=\"https:\/\/www.smashingmagazine.com\/2022\/01\/modern-fluid-typography-css-clamp\/\">already great deep dives on that topic<\/a>.<\/p>\n\n\n\n<p>The key point is that with a bit of math, you can create tokens that fluidly scale between a minimum and maximum size based on viewport width, without needing discrete breakpoints.<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-4\" data-shcb-language-name=\"CSS\" data-shcb-language-slug=\"css\"><span><code class=\"hljs language-css\"><span class=\"hljs-comment\">\/* Stepped values with breakpoints *\/<\/span>\n<span class=\"hljs-selector-class\">.stepped-card<\/span> {\n  <span class=\"hljs-attribute\">font-size<\/span>: <span class=\"hljs-number\">20px<\/span>;\n  <span class=\"hljs-attribute\">padding<\/span>: <span class=\"hljs-number\">2em<\/span>;\n    \n  @media (<span class=\"hljs-attribute\">max-width<\/span>: <span class=\"hljs-number\">720px<\/span>) {\n    font-size: <span class=\"hljs-number\">18px<\/span>;\n    <span class=\"hljs-attribute\">padding<\/span>: <span class=\"hljs-number\">1.5em<\/span>;\n  }\n    \n  <span class=\"hljs-keyword\">@media<\/span> (<span class=\"hljs-attribute\">max-width:<\/span> <span class=\"hljs-number\">380px<\/span>) {\n    <span class=\"hljs-selector-tag\">font-size<\/span>: 16<span class=\"hljs-selector-tag\">px<\/span>;\n    <span class=\"hljs-selector-tag\">padding<\/span>: 1<span class=\"hljs-selector-tag\">em<\/span>;\n  }\n}\n\n<span class=\"hljs-comment\">\/* Fluid values with clamp() *\/<\/span>\n<span class=\"hljs-selector-class\">.fluid-card<\/span> {\n  <span class=\"hljs-attribute\">font-size<\/span>: <span class=\"hljs-built_in\">clamp<\/span>(<span class=\"hljs-number\">16px<\/span>, calc(<span class=\"hljs-number\">11.529px<\/span> + <span class=\"hljs-number\">1.176<\/span>vi), <span class=\"hljs-number\">20px<\/span>);\n  <span class=\"hljs-attribute\">padding<\/span>: <span class=\"hljs-built_in\">clamp<\/span>(<span class=\"hljs-number\">1em<\/span>, calc(-<span class=\"hljs-number\">0.118em<\/span> + <span class=\"hljs-number\">4.706<\/span>vi), <span class=\"hljs-number\">2em<\/span>);\n}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-4\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">CSS<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">css<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<div class=\"wp-block-cp-codepen-gutenberg-embed-block cp_embed_wrapper\"><iframe id=\"cp_embed_JoRBqrb\/30f6143de5deb91189455070dce4111d\" src=\"\/\/codepen.io\/anon\/embed\/JoRBqrb\/30f6143de5deb91189455070dce4111d?height=450&amp;theme-id=1&amp;slug-hash=JoRBqrb\/30f6143de5deb91189455070dce4111d&amp;default-tab=result\" height=\"450\" scrolling=\"no\" frameborder=\"0\" allowfullscreen allowpaymentrequest name=\"CodePen Embed JoRBqrb\/30f6143de5deb91189455070dce4111d\" title=\"CodePen Embed JoRBqrb\/30f6143de5deb91189455070dce4111d\" class=\"cp_embed_iframe\" style=\"width:100%;overflow:hidden\">CodePen Embed Fallback<\/iframe><\/div>\n\n\n\n<p>To get these&nbsp;<code>clamp()<\/code>&nbsp;values, you can use one of the many <a href=\"https:\/\/clamp-calculator.netlify.app\/\">clamps calculators<\/a> out there. The result is a single rule that produces the same effect as multiple media queries, but with smoother scaling and less CSS.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"method-3-container-units-for-local-responsiveness\">Method 3: Container Units for Local Responsiveness<\/h2>\n\n\n\n<p>So far, we have made the UI much more fluid, but most values were still absolute and tied to the viewport. In component-based systems, we often don\u2019t know where a component will render or how much space it will actually get. This is exactly where&nbsp;<strong>container units<\/strong>&nbsp;become useful, and now that support is widely available, we can use them with confidence.<\/p>\n\n\n\n<p>Container units are ideal when you want values to scale based on the component&#8217;s real rendered size, not the screen width.<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-5\" data-shcb-language-name=\"CSS\" data-shcb-language-slug=\"css\"><span><code class=\"hljs language-css\"><span class=\"hljs-selector-class\">.card-container<\/span> {\n  <span class=\"hljs-attribute\">container-type<\/span>: inline-size;\n}\n\n<span class=\"hljs-selector-class\">.card<\/span> {\n  <span class=\"hljs-comment\">\/* This font size will scale based on the card's actual width, not the viewport *\/<\/span>\n  <span class=\"hljs-attribute\">font-size<\/span>: <span class=\"hljs-number\">5<\/span>cqi;\n\n  <span class=\"hljs-comment\">\/* once the font size is set, we can use it as a base unit for other properties *\/<\/span>\n  <span class=\"hljs-attribute\">gap<\/span>: <span class=\"hljs-number\">1em<\/span>;\n  <span class=\"hljs-attribute\">padding<\/span>: <span class=\"hljs-number\">1.5em<\/span>;\n    \n  <span class=\"hljs-comment\">\/* or use clamp to scale with the card size *\/<\/span>\n  <span class=\"hljs-attribute\">border-radius<\/span>: <span class=\"hljs-built_in\">clamp<\/span>(<span class=\"hljs-number\">4px<\/span>, calc(-<span class=\"hljs-number\">16.87px<\/span> + <span class=\"hljs-number\">6.522<\/span>cqi), <span class=\"hljs-number\">64px<\/span>);\n}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-5\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">CSS<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">css<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>You can also use container units to manage internal intrinsic layout behavior.<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-6\" data-shcb-language-name=\"CSS\" data-shcb-language-slug=\"css\"><span><code class=\"hljs language-css\"><span class=\"hljs-selector-class\">.card<\/span> {\n  <span class=\"hljs-attribute\">display<\/span>: flex;\n  <span class=\"hljs-attribute\">flex-wrap<\/span>: wrap;\n}\n\n<span class=\"hljs-selector-class\">.card-content<\/span> {\n  <span class=\"hljs-comment\">\/* This content will take all available space but won't shrink below 40cqi *\/<\/span>\n  <span class=\"hljs-attribute\">flex<\/span>: <span class=\"hljs-built_in\">calc<\/span>(infinity) <span class=\"hljs-number\">1<\/span> <span class=\"hljs-number\">40<\/span>cqi;\n}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-6\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">CSS<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">css<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<div class=\"wp-block-cp-codepen-gutenberg-embed-block cp_embed_wrapper\"><iframe id=\"cp_embed_emdjarp\/11f1cdb5c6c107af4ef1a659a3beb37b\" src=\"\/\/codepen.io\/anon\/embed\/emdjarp\/11f1cdb5c6c107af4ef1a659a3beb37b?height=450&amp;theme-id=1&amp;slug-hash=emdjarp\/11f1cdb5c6c107af4ef1a659a3beb37b&amp;default-tab=result\" height=\"450\" scrolling=\"no\" frameborder=\"0\" allowfullscreen allowpaymentrequest name=\"CodePen Embed emdjarp\/11f1cdb5c6c107af4ef1a659a3beb37b\" title=\"CodePen Embed emdjarp\/11f1cdb5c6c107af4ef1a659a3beb37b\" class=\"cp_embed_iframe\" style=\"width:100%;overflow:hidden\">CodePen Embed Fallback<\/iframe><\/div>\n\n\n\n<p>Three cards, same CSS, completely different results.<\/p>\n\n\n\n<p class=\"learn-more\">I am using&nbsp;<code>cqi<\/code>&nbsp;here as it represents 1% of the container&#8217;s inline size, similar to how&nbsp;<code>vi<\/code>&nbsp;works for the viewport. To go deeper into container units and container queries, you can read&nbsp;<a href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/CSS\/CSS_containment\/Container_size_and_style_queries\">this article<\/a>.<\/p>\n\n\n\n<p>This approach removes the need to know in advance where each component will render, and makes your components far more portable and reusable across layouts, contexts, and content shapes.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"method-4-container-queries-for-real-structural-changes\">Method 4: Container Queries for Real Structural Changes<\/h2>\n\n\n\n<p>Fluid values and intrinsic layout solve a lot, but not everything. Sometimes you need a true structural shift. Container queries are exactly for that moment.<\/p>\n\n\n\n<p>The important shift is that we&#8217;re no longer asking&nbsp;<em>&#8220;How wide is the screen?&#8221;<\/em>, we are asking&nbsp;<em>&#8220;How much space does this specific component have right now?&#8221;<\/em>. That makes the behavior reusable and predictable across sidebars, modals, cards, and full-page layouts.<\/p>\n\n\n\n<p>Here\u2019s an example where a group of items starts as a vertical stack, but when the container gets tight, the component changes structure. The group becomes a row, the gap and icon size shrink, and secondary text is hidden to preserve clarity.<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-7\" data-shcb-language-name=\"CSS\" data-shcb-language-slug=\"css\"><span><code class=\"hljs language-css\"><span class=\"hljs-selector-class\">.container<\/span> {\n  <span class=\"hljs-attribute\">container-type<\/span>: inline-size;\n}\n\n<span class=\"hljs-selector-class\">.group<\/span> {\n  <span class=\"hljs-attribute\">display<\/span>: flex;\n  <span class=\"hljs-attribute\">flex-direction<\/span>: column;\n  <span class=\"hljs-attribute\">gap<\/span>: <span class=\"hljs-number\">1em<\/span>;\n\n  @container (<span class=\"hljs-attribute\">max-width<\/span>: <span class=\"hljs-number\">32em<\/span>) {\n    flex-direction: row;\n    <span class=\"hljs-attribute\">gap<\/span>: <span class=\"hljs-number\">0.5em<\/span>;\n    \n    .icon { <span class=\"hljs-attribute\">font-size<\/span>: <span class=\"hljs-number\">1.5em<\/span>; }\n\n    <span class=\"hljs-selector-tag\">p<\/span> { <span class=\"hljs-attribute\">display<\/span>: none; }\n  }\n}\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-7\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">CSS<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">css<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<div class=\"wp-block-cp-codepen-gutenberg-embed-block cp_embed_wrapper\"><iframe id=\"cp_embed_NPRBVLm\/53f9d25b4da2961a7dc6f4f849f122fb\" src=\"\/\/codepen.io\/anon\/embed\/NPRBVLm\/53f9d25b4da2961a7dc6f4f849f122fb?height=450&amp;theme-id=1&amp;slug-hash=NPRBVLm\/53f9d25b4da2961a7dc6f4f849f122fb&amp;default-tab=result\" height=\"450\" scrolling=\"no\" frameborder=\"0\" allowfullscreen allowpaymentrequest name=\"CodePen Embed NPRBVLm\/53f9d25b4da2961a7dc6f4f849f122fb\" title=\"CodePen Embed NPRBVLm\/53f9d25b4da2961a7dc6f4f849f122fb\" class=\"cp_embed_iframe\" style=\"width:100%;overflow:hidden\">CodePen Embed Fallback<\/iframe><\/div>\n\n\n\n<p>Same component, different container behavior. This is where container queries shine. They let each component carry its own adaptation logic, so layout changes happen at the right moment for that component, not at an arbitrary viewport breakpoint.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"half-point-conclusion-breakpoints-become-optional-not-foundational\">Half-point Conclusion: Breakpoints Become Optional, Not Foundational<\/h2>\n\n\n\n<p>Up to this point, the pattern is clear. Intrinsic layouts handle structure, fluid values handle scale, container units handle local sizing, and container queries handle real structural shifts. Together, these tools already cover most of what we used to solve with viewport breakpoints, but with less friction and far more flexibility.<\/p>\n\n\n\n<p>That is good news. It means responsive design can become simpler to maintain, easier to reason about, and much more aligned with how modern component systems actually work. Instead of constantly patching layouts at global thresholds, we can build components that adapt gracefully by default and keep working as products evolve.<\/p>\n\n\n\n<p>Breakpoints won&#8217;t disappear, but they are no longer the main engine of responsive design. Instead of being the foundation of every layout decision, they are a focused, optional tool you use only when a component has a real reason to switch behavior.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"reframing-media-queries-capabilities-and-preferences\">Reframing Media Queries: Capabilities and Preferences<\/h2>\n\n\n\n<p>So now you might be asking yourself, if we use fewer viewport breakpoints and use container queries for structural component changes, do we still need&nbsp;<code>@media<\/code>&nbsp;at all?<\/p>\n\n\n\n<p>The answer is&nbsp;<strong>absolutely yes<\/strong>.<\/p>\n\n\n\n<p>The shift is not about removing media queries. It is about using them for what they are best at, understanding the device and the user environment, instead of measuring screen pixels for layout.<\/p>\n\n\n\n<p>For example, we can detect&nbsp;<code>hover<\/code>&nbsp;support and&nbsp;<code>pointer<\/code>&nbsp;accuracy, adapt to&nbsp;<code>display-mode<\/code>, reduce expensive effects when&nbsp;<code>update<\/code>s are slow, and show fallback content when&nbsp;<code>scripting<\/code>&nbsp;is unavailable.<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-8\" data-shcb-language-name=\"CSS\" data-shcb-language-slug=\"css\"><span><code class=\"hljs language-css\"><span class=\"hljs-keyword\">@media<\/span> (<span class=\"hljs-attribute\">hover:<\/span> hover) {\n  <span class=\"hljs-comment\">\/* when hover is supported *\/<\/span>\n  <span class=\"hljs-selector-class\">.link<\/span><span class=\"hljs-selector-pseudo\">:hover<\/span> {\n    <span class=\"hljs-attribute\">color<\/span>: blue;\n  }\n}\n\n<span class=\"hljs-selector-tag\">button<\/span> {\n  <span class=\"hljs-attribute\">padding<\/span>: <span class=\"hljs-number\">1em<\/span>;\n\n  @media (<span class=\"hljs-attribute\">pointer<\/span>: coarse) {\n    <span class=\"hljs-comment\">\/* when pointing device has limited accuracy *\/<\/span>\n    padding: <span class=\"hljs-number\">2em<\/span>;\n  }\n}\n\n<span class=\"hljs-keyword\">@media<\/span> (<span class=\"hljs-attribute\">display-mode:<\/span> picture-in-picture) {\n  <span class=\"hljs-comment\">\/* when running in picture-in-picture *\/<\/span>\n  <span class=\"hljs-selector-tag\">body<\/span> {\n    <span class=\"hljs-attribute\">border<\/span>: <span class=\"hljs-number\">2px<\/span> solid white;\n  }\n}\n\n<span class=\"hljs-selector-class\">.glass-panel<\/span> {\n  <span class=\"hljs-attribute\">backdrop-filter<\/span>: <span class=\"hljs-built_in\">blur<\/span>(<span class=\"hljs-number\">16px<\/span>);\n\n  @media (<span class=\"hljs-attribute\">update<\/span>: slow) {\n    <span class=\"hljs-comment\">\/* when updates are slow *\/<\/span>\n    backdrop-filter: none;\n    <span class=\"hljs-attribute\">background<\/span>: <span class=\"hljs-built_in\">rgb<\/span>(<span class=\"hljs-number\">255<\/span> <span class=\"hljs-number\">255<\/span> <span class=\"hljs-number\">255<\/span> \/ <span class=\"hljs-number\">0.92<\/span>);\n  }\n}\n\n<span class=\"hljs-selector-class\">.no-script-msg<\/span> {\n   <span class=\"hljs-attribute\">display<\/span>: none;\n\n  @media (<span class=\"hljs-attribute\">scripting<\/span>: none) {\n    display: block;\n  }\n}\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-8\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">CSS<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">css<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>There are many more&nbsp;<code>@media<\/code>&nbsp;features worth exploring, and the&nbsp;<a href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/CSS\/Reference\/At-rules\/@media\">MDN documentation<\/a>&nbsp;is the best place to browse what is available. And if you want to prioritize one category, start with&nbsp;<strong>user preferences<\/strong>.<\/p>\n\n\n\n<p>The word &#8220;preferences&#8221; can be misleading. As in many cases, these settings reflect real accessibility needs, not cosmetic choices. Whether someone needs lower motion, stronger contrast, reduced data usage, or a specific color scheme, our UI should respect that.<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-9\" data-shcb-language-name=\"CSS\" data-shcb-language-slug=\"css\"><span><code class=\"hljs language-css\"><span class=\"hljs-comment\">\/* Color scheme preference *\/<\/span>\n<span class=\"hljs-selector-pseudo\">:root<\/span> {\n  <span class=\"hljs-attribute\">color-scheme<\/span>: light;\n\n  @media (<span class=\"hljs-attribute\">prefers-color-scheme<\/span>: dark) {\n    color-scheme: dark;\n  }\n}\n\n<span class=\"hljs-comment\">\/* Higher contrast preference *\/<\/span>\n<span class=\"hljs-selector-class\">.card<\/span> {\n  <span class=\"hljs-attribute\">color<\/span>: <span class=\"hljs-number\">#333<\/span>;\n  <span class=\"hljs-attribute\">background<\/span>: <span class=\"hljs-number\">#ccc<\/span>;\n\n  @media (<span class=\"hljs-attribute\">prefers-contrast<\/span>: more) {\n    color: <span class=\"hljs-number\">#000<\/span>;\n    <span class=\"hljs-attribute\">background<\/span>: <span class=\"hljs-number\">#fff<\/span>;\n  }\n}\n\n<span class=\"hljs-comment\">\/* Reduced data preference *\/<\/span>\n<span class=\"hljs-keyword\">@media<\/span> (<span class=\"hljs-attribute\">prefers-reduced-data:<\/span> reduce) {\n  <span class=\"hljs-selector-class\">.hero-video<\/span> {\n    <span class=\"hljs-attribute\">display<\/span>: none;\n  }\n}\n\n<span class=\"hljs-comment\">\/* Reduced motion preference *\/<\/span>\n<span class=\"hljs-selector-class\">.element<\/span> {\n  @media (<span class=\"hljs-attribute\">prefers-reduced-motion<\/span>: no-preference) {\n    <span class=\"hljs-comment\">\/* only animate if the user has not expressed a preference for reduced motion *\/<\/span>\n    animation: spin <span class=\"hljs-number\">4s<\/span> linear infinite;\n  }\n}<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-9\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">CSS<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">css<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>So yes, media queries are still critical, just with a sharper mission. Let container logic drive layout, and let media queries handle capabilities, constraints, and user needs.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"a-practical-migration-checklist\">A Practical Migration Checklist<\/h2>\n\n\n\n<p>Before we wrap up, here is a short checklist of actions you can start applying today to improve your project&#8217;s responsiveness. Every codebase is different, and good judgment still matters, but a few focused steps can already create meaningful improvements in code quality, stability, and maintainability.<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li><strong>Audit existing media queries<\/strong>. Separate scalar changes (size, spacing, typography) from structural changes (composition\/layout).<\/li>\n\n\n\n<li><strong>Replace scalar branches first.<\/strong>&nbsp;Move to&nbsp;<code>clamp()<\/code>,&nbsp;<code>min()<\/code>, and&nbsp;<code>max()<\/code>&nbsp;tokens.<\/li>\n\n\n\n<li><strong>Shift layout to intrinsic primitives.<\/strong>&nbsp;Prefer&nbsp;<code>auto-fit<\/code>&nbsp;and&nbsp;<code>minmax()<\/code>&nbsp;over fixed column counts tied to viewport widths.<\/li>\n\n\n\n<li><strong>Scope behavior to the component.<\/strong>&nbsp;Add&nbsp;<code>container-type<\/code>&nbsp;and introduce container units where helpful.<\/li>\n\n\n\n<li><strong>Add container queries only where structure changes.<\/strong>&nbsp;Keep thresholds local to the component.<\/li>\n\n\n\n<li><strong>Keep media queries for environment intent.<\/strong>&nbsp;Favor&nbsp;<code>hover<\/code>,&nbsp;<code>pointer<\/code>,&nbsp;<code>prefers-reduced-motion<\/code>, and&nbsp;<code>update<\/code>.<\/li>\n\n\n\n<li><strong>Validate in real placements.<\/strong>&nbsp;Test the same component in sidebar, modal, and full-content contexts.<\/li>\n<\/ol>\n\n\n\n<p>You do not need to jump into a full rewrite. Move step by step, one component at a time, and the change will compound over time. What matters most is adopting these methods as the default baseline for future development.<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img data-recalc-dims=\"1\" loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"544\" src=\"https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2026\/04\/s_124E0F4150208DDF98EE944AA8CF94692A7C4F345845A0771B8E6D5804D20A4E_1775569582099_image.png?resize=1024%2C544&#038;ssl=1\" alt=\"breakpoint-heavy CSS evolving into a cleaner, component-first responsive workflow.\" class=\"wp-image-9326\" srcset=\"https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2026\/04\/s_124E0F4150208DDF98EE944AA8CF94692A7C4F345845A0771B8E6D5804D20A4E_1775569582099_image.png?resize=1024%2C544&amp;ssl=1 1024w, https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2026\/04\/s_124E0F4150208DDF98EE944AA8CF94692A7C4F345845A0771B8E6D5804D20A4E_1775569582099_image.png?resize=300%2C159&amp;ssl=1 300w, https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2026\/04\/s_124E0F4150208DDF98EE944AA8CF94692A7C4F345845A0771B8E6D5804D20A4E_1775569582099_image.png?resize=768%2C408&amp;ssl=1 768w, https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2026\/04\/s_124E0F4150208DDF98EE944AA8CF94692A7C4F345845A0771B8E6D5804D20A4E_1775569582099_image.png?w=1418&amp;ssl=1 1418w\" sizes=\"auto, (max-width: 1000px) 100vw, 1000px\" \/><\/figure>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"closing-thought\">Closing Thought<\/h2>\n\n\n\n<p>Responsive design is moving from breakpoint choreography to intent-driven systems. That is not a small optimization. It is a mindset shift in how we design, build, and maintain interfaces.<\/p>\n\n\n\n<p>When intrinsic layout, fluid values, container units, and container queries become your default toolbox, breakpoints stop being the thing you depend on first. They become a precise tool you use on purpose. The result is cleaner CSS, fewer regressions, and components that stay resilient as your product and content evolve.<\/p>\n\n\n\n<p>If you want to start today, pick one component that currently depends on multiple viewport breakpoints and rebuild it with the patterns from this article. Ship that version, learn from it, and repeat. That is how teams move from &#8220;responsive enough&#8221; to truly adaptive interfaces.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>This article covers a layout approach that better fits the modern web: fluid, intrinsic components that adapt by default, and treat conditional rules as local, intentional exceptions.<\/p>\n","protected":false},"author":27,"featured_media":9331,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"sig_custom_text":"","sig_image_type":"featured-image","sig_custom_image":0,"sig_is_disabled":false,"inline_featured_image":false,"_jetpack_memberships_contains_paid_content":false,"footnotes":""},"categories":[1],"tags":[7,472,53,272],"class_list":["post-9317","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-blog-post","tag-css","tag-intrinsic-layout","tag-layout","tag-responsive-design"],"acf":[],"jetpack_featured_media_url":"https:\/\/i0.wp.com\/frontendmasters.com\/blog\/wp-content\/uploads\/2026\/04\/intrinsic.jpg?fit=2000%2C1200&ssl=1","jetpack_sharing_enabled":true,"_links":{"self":[{"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/posts\/9317","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/users\/27"}],"replies":[{"embeddable":true,"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/comments?post=9317"}],"version-history":[{"count":9,"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/posts\/9317\/revisions"}],"predecessor-version":[{"id":9352,"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/posts\/9317\/revisions\/9352"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/media\/9331"}],"wp:attachment":[{"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/media?parent=9317"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/categories?post=9317"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/frontendmasters.com\/blog\/wp-json\/wp\/v2\/tags?post=9317"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}