|
| 1 | +--- |
| 2 | +title: 'Going beyond pixels and (r)ems in CSS - Container query length units' |
| 3 | +date: '2024-02-28' |
| 4 | +tags: ['frontend', 'css'] |
| 5 | +images: ['/articles/going-beyond-pixels-and-rems-in-css/visual-pixel-containers.jpg'] |
| 6 | +summary: 'In the third part of this series, we’ll look at length units based on the container. Yes, you heard that right, we can finally get some measurements based on a containing element and that just spells awesome in my book. Currently available in all evergreen browsers, these units open up a lot of opportunities to create some smart systems and once again, I will write this up packed with a bunch of demos and cool use cases.' |
| 7 | +authors: ['brecht-de-ruyte'] |
| 8 | +theme: 'beige' |
| 9 | +serie: 'going-beyond-pixels-and-rems-in-css' |
| 10 | +--- |
| 11 | + |
| 12 | +As part of the containment spec, container queries are something to be reckoned with and in my personal opinion, they still aren’t used enough, but that’s a whole other discussion. What we’ll be covering today are the units that came with this awesome spec and once again, this article will be based on the [list at MDN](https://developer.mozilla.org/en-US/docs/Web/CSS/length). First, we’ll get through the basics with a listing of the units, followed up with some more advanced usage. |
| 13 | + |
| 14 | +## Prerequisites for using container query length units |
| 15 | + |
| 16 | +Before we get started we will need to define a `container-type` for the container we’ll target. For our first example, let’s add a bit of HTML: |
| 17 | + |
| 18 | +```html |
| 19 | +<div class="container"> |
| 20 | + <div class="item">A cool container item</div> |
| 21 | +</div> |
| 22 | +``` |
| 23 | + |
| 24 | +Next up, let’s write a bit of CSS to define a `container-type` on our container and give it a `max-width`: |
| 25 | + |
| 26 | +```css |
| 27 | +.container { |
| 28 | + container-type: inline-size; |
| 29 | + max-width: 800px; |
| 30 | +} |
| 31 | +``` |
| 32 | + |
| 33 | +This is the part that matters, but let’s add some presentational styling as well, to visualize what is going on: |
| 34 | + |
| 35 | +```css |
| 36 | +.container { |
| 37 | + container-type: inline-size; |
| 38 | + max-width: 800px; |
| 39 | + width: 100%; |
| 40 | + text-align: center; |
| 41 | + border: 2px dotted #695958; |
| 42 | +} |
| 43 | + |
| 44 | +.item { |
| 45 | + background: #271f30; |
| 46 | + color: white; |
| 47 | +} |
| 48 | +``` |
| 49 | + |
| 50 | +Ok, we’ve set our `container-type` and added some basic styling, It’s time to dig in and get started with those units: |
| 51 | + |
| 52 | +## cqw |
| 53 | + |
| 54 | +The `cwq` unit stands for **1% of the containing element's width**. It allows you to define element sizes and spacing relative to the width of their container in contrast to the `vw` unit, which defines the spacing relative to the viewport. But just as viewport and font units, it can be used in any property that accepts `<length>` values, such as `font-size`, `width`, `padding`, `margin`, etc. The latter counts for every unit we will tackle in this article. |
| 55 | + |
| 56 | +Now, a demo says more than a thousand words, so let’s put it to the test. Let’s add a margin to our previous code for the `.item` inside of the container: |
| 57 | + |
| 58 | +```css |
| 59 | +.item { |
| 60 | + /*previous code */ |
| 61 | + margin: 5cqw; |
| 62 | +} |
| 63 | +``` |
| 64 | + |
| 65 | +Now the item will have a margin based on its container. This is what you should have now, resize the container to see this in full effect: |
| 66 | + |
| 67 | +<iframe |
| 68 | + height="300" |
| 69 | + scrolling="no" |
| 70 | + class="simple-embed" |
| 71 | + title="Basic container unit example" |
| 72 | + src="https://codepen.io/utilitybend/embed/preview/jOJjbez?default-tab=result&theme-id=dark" |
| 73 | + frameborder="no" |
| 74 | + loading="lazy" |
| 75 | + allowtransparency="true" |
| 76 | + allowfullscreen="true" |
| 77 | +> |
| 78 | + See the Pen <a href="https://codepen.io/utilitybend/pen/jOJjbez">Basic container unit example</a> |
| 79 | + by utilitybend (<a href="https://codepen.io/utilitybend">@utilitybend</a>) on{' '} |
| 80 | + <a href="https://codepen.io">CodePen</a>. |
| 81 | +</iframe> |
| 82 | + |
| 83 | +Now let’s make the effect stick out a bit more by making some other properties rely on the `cqw` unit. |
| 84 | + |
| 85 | +```css |
| 86 | +.item { |
| 87 | + margin: 6cqw; |
| 88 | + padding: 1cqw; |
| 89 | + font-size: 5cqw; |
| 90 | +} |
| 91 | +``` |
| 92 | + |
| 93 | +Now we can see the effect on roids. If you add a few extra containers with different max-widths, it becomes a great little demo. Just resize the following and see the potential: |
| 94 | + |
| 95 | +<iframe |
| 96 | + height="300" |
| 97 | + scrolling="no" |
| 98 | + class="simple-embed" |
| 99 | + title="Margins, padding and font-size with container units" |
| 100 | + src="https://codepen.io/utilitybend/embed/preview/dyrBYMB?default-tab=result&theme-id=dark" |
| 101 | + frameborder="no" |
| 102 | + loading="lazy" |
| 103 | + allowtransparency="true" |
| 104 | + allowfullscreen="true" |
| 105 | +> |
| 106 | + See the Pen{' '} |
| 107 | + <a href="https://codepen.io/utilitybend/pen/dyrBYMB"> |
| 108 | + Margins, padding and font-size with container units |
| 109 | + </a> |
| 110 | + by utilitybend (<a href="https://codepen.io/utilitybend">@utilitybend</a>) on{' '} |
| 111 | + <a href="https://codepen.io">CodePen</a>. |
| 112 | +</iframe> |
| 113 | + |
| 114 | +**Note: What happens if we don’t define a `container-type` on any of the parents?** |
| 115 | + |
| 116 | +In that case, the container query unit will fall back to the viewport unit equivalent, in this case: `vw`. |
| 117 | + |
| 118 | +## cqh |
| 119 | + |
| 120 | +The `cqh` unit stands for (yes, you guessed it) **1% of the containing element's height**. It allows you to define element sizes and spacing relative to the height of their container |
| 121 | +We pretty much know what’s going on here by now, so let’s get to it. One thing we could do is create a quick test out of that previous demo and change our `.item` CSS to use the `cqh` unit: |
| 122 | + |
| 123 | +```css |
| 124 | +.item { |
| 125 | + margin: 10cqh; |
| 126 | + padding: 10cqh; |
| 127 | + height: 80cqh; |
| 128 | + font-size: 12cqh; |
| 129 | +} |
| 130 | +``` |
| 131 | + |
| 132 | +Now this didn’t do that much, we’ll need to change our `container-type` as well to use the block axis as well, so for now, change it like this: |
| 133 | + |
| 134 | +```css |
| 135 | +.container { |
| 136 | + container-type: size; |
| 137 | +} |
| 138 | +``` |
| 139 | + |
| 140 | +And instead of our width, we’ll change the containers to have different heights: |
| 141 | + |
| 142 | +```css |
| 143 | +.container { |
| 144 | + height: 100px; |
| 145 | +} |
| 146 | + |
| 147 | +.container-2 { |
| 148 | + height: 200px; |
| 149 | +} |
| 150 | + |
| 151 | +.container-3 { |
| 152 | + height: 100px; |
| 153 | +} |
| 154 | +``` |
| 155 | + |
| 156 | +Now you can notice that the height of the container is taken into account. |
| 157 | + |
| 158 | +<iframe |
| 159 | + height="300" |
| 160 | + class="simple-embed" |
| 161 | + scrolling="no" |
| 162 | + title="Untitled" |
| 163 | + src="https://codepen.io/utilitybend/embed/preview/wvOLMjY?default-tab=result&theme-id=dark" |
| 164 | + frameborder="no" |
| 165 | + loading="lazy" |
| 166 | + allowtransparency="true" |
| 167 | + allowfullscreen="true" |
| 168 | +> |
| 169 | + See the Pen <a href="https://codepen.io/utilitybend/pen/wvOLMjY">Untitled</a> by utilitybend ( |
| 170 | + <a href="https://codepen.io/utilitybend">@utilitybend</a>) on{' '} |
| 171 | + <a href="https://codepen.io">CodePen</a>. |
| 172 | +</iframe> |
| 173 | + |
| 174 | +We could do some practical examples, but I’d rather show them after the next part because the next two units are probably the most important ones. |
| 175 | + |
| 176 | +## Going logical: use cqi and cqb |
| 177 | + |
| 178 | +I believe that it’s always a good reflex to use logical variants by default. I use these a lot, but mostly for RTL writing modes, which doesn’t apply to this difference, but you never know what the future brings, so it’s good to make this a habit. |
| 179 | + |
| 180 | +- The `cqi` unit stands for **1% of the containing element’s inline axis**, for western languages this is horizontal as our reading direction is from left to right. |
| 181 | +- The `cqb` unit stands for **1% of the containing element’s block axis**, for western languages this is vertical, but for example Mongolian, this is horizontal as their reading direction is from top to bottom. |
| 182 | + |
| 183 | +Let’s create a practical example of the `cqi` unit by having a banner move from the main content to the sidebar and adjust sizes based on that. |
| 184 | + |
| 185 | +The idea is the following, we create a 2-column grid where the children both have a container-type. |
| 186 | + |
| 187 | +```html |
| 188 | +<div class="grid"> |
| 189 | + <main></main> |
| 190 | + <aside></aside> |
| 191 | +</div> |
| 192 | +``` |
| 193 | + |
| 194 | +```css |
| 195 | +.grid { |
| 196 | + display: grid; |
| 197 | + grid-template-columns: 2fr 1fr; |
| 198 | + > * { |
| 199 | + container-type: inline-size; |
| 200 | + } |
| 201 | +} |
| 202 | +``` |
| 203 | + |
| 204 | +Next up, let’s create a little banner and put this in our main content |
| 205 | + |
| 206 | +```html |
| 207 | +<div class="grid"> |
| 208 | + <main> |
| 209 | + <div class="banner"> |
| 210 | + <h2>This is an awesome banner</h2> |
| 211 | + <p> |
| 212 | + Depending on the container, this will adjust its font-size and paddings to fit perfectly |
| 213 | + </p> |
| 214 | + </div> |
| 215 | + </main> |
| 216 | + <aside></aside> |
| 217 | +</div> |
| 218 | +``` |
| 219 | + |
| 220 | +Let’s style our banner by using some container cqi units, as we want to work with the inline axis: |
| 221 | + |
| 222 | +```css |
| 223 | +.banner { |
| 224 | + padding: 3cqi; |
| 225 | + background: #55673e; |
| 226 | + font-size: 3cqi; |
| 227 | + border-radius: 0.5rem; |
| 228 | +} |
| 229 | + |
| 230 | +.banner h2 { |
| 231 | + font-size: 4cqi; |
| 232 | + margin: 0 0 1cqi; |
| 233 | +} |
| 234 | +``` |
| 235 | + |
| 236 | +With this basic setup, let's move (or duplicate) that banner to the sidebar. By using these smart units and some basic styling, we get the following: |
| 237 | + |
| 238 | + |
| 239 | + |
| 240 | +This is the CodePen of that example with some extras: |
| 241 | + |
| 242 | +<iframe |
| 243 | + height="300" |
| 244 | + scrolling="no" |
| 245 | + class="simple-embed" |
| 246 | + title="Multiple banners with container units" |
| 247 | + src="https://codepen.io/utilitybend/embed/preview/vYPqKoy?default-tab=result&theme-id=dark" |
| 248 | + frameborder="no" |
| 249 | + loading="lazy" |
| 250 | + allowtransparency="true" |
| 251 | + allowfullscreen="true" |
| 252 | +> |
| 253 | + See the Pen{' '} |
| 254 | + <a href="https://codepen.io/utilitybend/pen/vYPqKoy">Multiple banners with container units</a> by |
| 255 | + utilitybend (<a href="https://codepen.io/utilitybend">@utilitybend</a>) on{' '} |
| 256 | + <a href="https://codepen.io">CodePen</a>. |
| 257 | +</iframe> |
| 258 | + |
| 259 | +Now, while all of this looks fine and dandy when it comes to paddings, our `font-size` does get a bit too low in the sidebar. We can adjust this by setting a minimum and maximum `font-size`. Yes, we’ll be adding some **fluid typography with container query units.** Let’s take our previous `font-size` values for the banner and update them by using the `clamp()` function: |
| 260 | + |
| 261 | +```css |
| 262 | +.banner { |
| 263 | + font-size: clamp(1rem, 2cqi + 0.5rem, 1.5rem); |
| 264 | +} |
| 265 | + |
| 266 | +.banner h2 { |
| 267 | + font-size: clamp(1.3rem, 3cqi + 0.5rem, 2.4rem); |
| 268 | +} |
| 269 | +``` |
| 270 | + |
| 271 | +For the banner, in general, we will take a `1rem` minimum font size and a maximum of `1.5rem`. This will make our general look and feel a lot better. Here is the updated CodePen for that: |
| 272 | + |
| 273 | +<iframe |
| 274 | + height="300" |
| 275 | + class="simple-embed" |
| 276 | + scrolling="no" |
| 277 | + title="Banner with container units and clamp() function for font-sizes" |
| 278 | + src="https://codepen.io/utilitybend/embed/preview/xxBoRbr?default-tab=result&theme-id=dark" |
| 279 | + frameborder="no" |
| 280 | + loading="lazy" |
| 281 | + allowtransparency="true" |
| 282 | + allowfullscreen="true" |
| 283 | +> |
| 284 | + See the Pen{' '} |
| 285 | + <a href="https://codepen.io/utilitybend/pen/xxBoRbr"> |
| 286 | + Banner with container units and clamp() function for font-sizes |
| 287 | + </a> |
| 288 | + by utilitybend (<a href="https://codepen.io/utilitybend">@utilitybend</a>) on{' '} |
| 289 | + <a href="https://codepen.io">CodePen</a>. |
| 290 | +</iframe> |
| 291 | + |
| 292 | +We could, of course, apply this to margins and paddings as well. Resize the window to see how this behaves. |
| 293 | + |
| 294 | +If you’re feeling brave, at the bottom of that demo, you can find a `writing-mode` property in a comment. Notice that, you still get that great layout in a “top to bottom and right to left” writing mode when enabling this. [It’s all about being logical](https://www.youtube.com/watch?v=pP8iUyb9Gn8). the stuff that CSS does is a miracle, beautiful and magical. |
| 295 | + |
| 296 | + |
| 297 | + |
| 298 | +### cqb and declared height |
| 299 | + |
| 300 | +When using container query units for the block size, there is a little gotcha. In that case, the container needs to have a declared height otherwise, it gets an overflow. This is unfortunately one of the limitations of the containment spec. But that doesn’t mean we can’t have some fun with it. |
| 301 | + |
| 302 | +In the following example, we’ll create a little banner and set the font size based on the block height: |
| 303 | + |
| 304 | +The following will be our HTML: |
| 305 | + |
| 306 | +```html |
| 307 | +<section class="intro"> |
| 308 | + <h1>Be<br />Query</h1> |
| 309 | + <img src="..." alt="" /> |
| 310 | +</section> |
| 311 | +``` |
| 312 | + |
| 313 | +We want to have a nice little intro that is about `80%` of the viewport height, but we might want to change that later on for different pages. To make it versatile, we could do something like this: |
| 314 | + |
| 315 | +```css |
| 316 | +.intro { |
| 317 | + display: flex; |
| 318 | + justify-content: space-between; |
| 319 | + container-type: size; |
| 320 | + background: rebeccapurple; |
| 321 | + block-size: 80dvb; |
| 322 | +} |
| 323 | + |
| 324 | +h1 { |
| 325 | + font-family: 'Oswald', sans-serif; |
| 326 | + font-size: clamp(1rem, 20cqb + 0.5rem, 10rem); |
| 327 | + padding: 2cqi; |
| 328 | + line-height: 1.2; |
| 329 | +} |
| 330 | +``` |
| 331 | + |
| 332 | +Notice how we used the `cqb` in the `clamp()` function. Here is a little extended demo of this in action: |
| 333 | + |
| 334 | +<iframe |
| 335 | + height="300" |
| 336 | + className="simple-embed" |
| 337 | + scrolling="no" |
| 338 | + title="cqb container unit example" |
| 339 | + src="https://codepen.io/utilitybend/embed/preview/JjzQbOp?default-tab=result&theme-id=dark" |
| 340 | + frameborder="no" |
| 341 | + loading="lazy" |
| 342 | + allowtransparency="true" |
| 343 | + allowfullscreen="true" |
| 344 | +> |
| 345 | + See the Pen <a href="https://codepen.io/utilitybend/pen/JjzQbOp">cqb container unit example</a> by |
| 346 | + utilitybend (<a href="https://codepen.io/utilitybend">@utilitybend</a>) on{' '} |
| 347 | + <a href="https://codepen.io">CodePen</a>. |
| 348 | +</iframe> |
| 349 | + |
| 350 | +## cqmin and cqmax |
| 351 | + |
| 352 | +In CSS, `cqmin` and `cqmax` are container query length units that offer dynamic sizing based on both the `width` and `height` of their containing element. These units let us define styles that adapt to different aspect ratios and container shapes. |
| 353 | + |
| 354 | +- `cqmin`: Represents the **smaller** value of either cqi (1% of inline size) or cqb (1% of block size). |
| 355 | +- `cqmax`: Represents the **larger** value of either cqi or cqb. |
| 356 | + |
| 357 | +This can be particularly handy if our container changes shape depending on the viewport. Let’s redo the banner example, but instead of using `cqi` for the padding, let’s use `cqmax`, taking the biggest value between block and inline size into account: |
| 358 | + |
| 359 | +```css |
| 360 | +.banner { |
| 361 | + padding: 3cqmax; |
| 362 | + background: #55673e; |
| 363 | + font-size: clamp(1rem, 2cqi + 0.5rem, 1.5rem); |
| 364 | + border-radius: 0.5rem; |
| 365 | +} |
| 366 | +``` |
| 367 | + |
| 368 | +<iframe |
| 369 | + height="300" |
| 370 | + class="simple-embed" |
| 371 | + scrolling="no" |
| 372 | + title="Using cqmax for paddings - resize for effect" |
| 373 | + src="https://codepen.io/utilitybend/embed/preview/YzgoNgq?default-tab=result&theme-id=dark" |
| 374 | + frameborder="no" |
| 375 | + loading="lazy" |
| 376 | + allowtransparency="true" |
| 377 | + allowfullscreen="true" |
| 378 | +> |
| 379 | + See the Pen <a href="https://codepen.io/utilitybend/pen/YzgoNgq">Untitled</a> by utilitybend ( |
| 380 | + <a href="https://codepen.io/utilitybend">@utilitybend</a>) on{' '} |
| 381 | + <a href="https://codepen.io">CodePen</a>. |
| 382 | +</iframe> |
| 383 | + |
| 384 | +Now resize the window in as many ways as possible, changing aspect-ratios completely to notice the full effect. (Open the pen in a new window to make that easier). |
| 385 | + |
| 386 | +## So that’s it for part 3. |
| 387 | + |
| 388 | +One of the things that everybody should get a bit more familiar with during 2024 is probably container queries and the units that come with it. If you write CSS on a day-to-day basis, it’s something that you just need to have in your toolset for the future. We have this awesome new tool to create these kinds of micro-design systems that make our components work no matter the size of their containing element. Truth be told, I am still working on mastering all these new units, but the more I play around with them, the more I believe that having a strong foundation in them can make the difference between a CSS newbie and a CSS Ninja. For the last part, we will kinda end with a downer on absolute units, but just as in video games, I’m a bit of a completionist, so we just can’t ignore them. Happy unit-querying! |
0 commit comments