Find out how to use container queries now  |  Weblog  |  net.dev


Philip Walton

Lately, Chris Coyier wrote a blog post posing the query:

Now that container queries are supported in all browser engines, why aren’t extra builders utilizing them?

Chris’s submit lists a lot of potential causes (for instance, lack of information, previous habits die exhausting), however there’s one explicit purpose that stands out.

Some builders say they wish to use container queries now however assume they cannot as a result of they nonetheless should help older browsers.

As you could have guessed from the title, we expect it is doable for many builders to make use of container queries now—in manufacturing—even when it’s a must to help older browsers. This submit walks you thru the strategy we suggest to do this.

A realistic strategy

If you wish to use container queries in your code now, however you need the expertise to look the identical in all browsers, you’ll be able to implement a JavaScript-based fallback for browsers that do not help container queries.

The query then turns into: how complete ought to the fallback be?

As with all fallback, the problem is to strike stability between usefulness and efficiency. For CSS options, it is usually unimaginable to help the complete API (see why not use a polyfill). Nevertheless, you will get fairly far by figuring out the core set of performance that the majority builders wish to use, after which optimize the fallback for simply these options.

However what’s the “core set of performance” that the majority builders need for container queries? To reply that query, think about how most builders construct responsive websites at the moment with media queries.

Just about all trendy design techniques and part libraries have standardized on mobile-first rules, carried out utilizing a set of predefined breakpoints (corresponding to SM, MD, LG, XL). Elements are optimized to show nicely on small screens by default, after which types are conditionally layered on to help a hard and fast set of bigger display screen widths. (See the Bootstrap and Tailwind documentation for examples of this.)

This strategy is simply as related to container-based design techniques as it’s to viewport-based design techniques as a result of, typically, what’s related to designers is just not how massive the display screen or viewport is, it is how a lot area is on the market to the part within the context that it has been positioned. In different phrases, slightly than breakpoints being relative to your complete viewport (and relevant to your complete web page), breakpoints would apply to particular content material areas, corresponding to sidebars, modal dialogs, or submit our bodies.

If you happen to’re capable of work inside the constraints of a mobile-first, breakpoint-based strategy (which most builders at the moment do), then implementing a container-based fallback for that strategy is considerably simpler than implementing full help for each single container question function.

The following part explains precisely how this all works, together with a step-by-step information exhibiting you how you can implement it on an present website.

The way it works

Step 1: replace your part types to make use of @container guidelines as an alternative of @media guidelines

On this first step, establish any parts in your website that you just assume would profit from container-based sizing slightly than viewport-based sizing.

It is a good suggestion to start out with only one or two parts to see how this technique works, however if you wish to convert 100% of your parts to container-based styling, that is positive too! The beauty of this technique is you’ll be able to undertake it incrementally if wanted.

As soon as you’ve got recognized the parts you wish to replace, you will want to alter every @media rule in these parts’ CSS to an @container rule.

Here is an instance of how which may look on a .photo-gallery part that, by default, is a single column, after which makes use of @media guidelines to replace its structure to turn out to be two and three columns within the MD and XL breakpoints (respectively):

.photo-gallery {
  show: grid;
  grid-template-columns: 1fr;
}

/* Types for the `MD` breakpoint */
@media (min-width: 800px) {
  .photo-gallery {
    grid-template-columns: 1fr 1fr;
  }
}

/* Types for the `XL` breakpoint */
@media (min-width: 1200px) {
  .photo-gallery {
    grid-template-columns: 1fr 1fr 1fr;
  }
}

To replace the .photo-gallery part to make use of @container guidelines, first exchange the string @media with the string @container within the CSS. The grammar for these two guidelines is analogous sufficient that, in lots of circumstances, this can be all it’s essential change.

Relying on the design of your website, you might also must replace the scale situation, particularly in case your website’s @media guidelines are guaranteeing assumptions about how a lot area might be obtainable to particular parts at varied viewport sizes.

For instance, if the types for the .photo-gallery CSS on the MD and XL breakpoints in earlier instance assume {that a} 200 pixel-wide sidebar might be displayed at these breakpoints, then the scale situations for the @container guidelines ought to be round 200 pixels much less—assuming that the “container” aspect for the .photo-gallery part will not embrace the sidebar.

Altogether, to transform the .photo-gallery CSS from @media guidelines to @container guidelines, the complete set of modifications are as follows:

/* Earlier than, utilizing the unique breakpoint sizes: */
@media (min-width: 800px) { /* ... */ }
@media (min-width: 1200px) { /* ... */ }

/* After, with the breakpoint sizes decreased by 200px: */
@container (min-width: 600px) { /* ... */ }
@container (min-width: 1000px) { /* ... */ }

Word that you do not have to alter any of the types inside the declaration block, as these replicate how the part seems to be slightly than when particular types ought to apply.

As soon as you’ve got up to date your parts types from @media guidelines to @container guidelines, the following step is to configure your container components.

Step 2: add container components to your HTML

The earlier step outlined part types which can be based mostly on the scale of a container aspect. The following step is to outline which components in your web page ought to be these container components whose measurement the @container guidelines might be relative to.

You may declare any aspect to be a container aspect in CSS by setting its container-type property to both measurement or inline-size. In case your container guidelines are width-based, then inline-size is usually what you wish to use.

Take into account a website with the next primary HTML construction:


  
  

...

To make the .sidebar and .content material components on this website containers, add this rule to your CSS:

.content material, .sidebar {
  container-type: inline-measurement;
}

For browsers that help container queries, this CSS is all it’s essential make the part types outlined within the earlier step relative to both the principle content material space or the sidebar, relying on which aspect they occur to be inside.

Nevertheless, for browsers that do not help container queries, there’s some extra work to do.

You have to add some code that detects when the scale of the container components modifications after which updates the DOM based mostly on these modifications in a method that your CSS can hook into.

Happily, the code required to do this is minimal, and it may be fully abstracted away right into a shared part that you should use on any website and in any content material space.

The next code defines a reusable aspect that routinely listens for measurement modifications and provides breakpoint lessons that your CSS can type based mostly on:

// A mapping of default breakpoint class names and min-width sizes.
// Redefine these (or add extra) as wanted based mostly in your website's design.
const defaultBreakpoints = {SM: 400, MD: 600 LG: 800, XL: 1000};

// A resize observer that screens measurement modifications to all 
// components and calls their `updateBreakpoints()` methodology with the up to date measurement.
const ro = new ResizeObserver((entries) => {
  entries.forEach((e) => e.goal.updateBreakpoints(e.contentRect));
});

class ResponsiveContainer extends HTMLElement {
  connectedCallback() 
  disconnectedCallback() {
    ro.unobserve(this);
  }
  updateBreakpoints(contentRect) {
    for (const bp of Object.keys(this.breakpoints)) {
      const minWidth = this.breakpoints[bp];
      const className = this.title ? `${this.title}-${bp}` : bp;
      this.classList.toggle(className, contentRect.width >= minWidth);
    }
  }
}

self.customElements.outline('responsive-container', ResponsiveContainer);

This code works by making a ResizeObserver that routinely listens for measurement modifications to any components within the DOM. If the scale change matches one of many outlined breakpoint sizes, then a category with that breakpoint title is added to the aspect (and eliminated if the situation now not matches).

For instance, if the width of the aspect is between 600 and 800 pixels (based mostly on the default breakpoint values set within the code), then the SM and MD lessons might be added, like this:

...

These lessons help you outline fallback types for browsers that do not help container queries (see step 3: add fallback styles to your CSS).

To replace the earlier HTML code to make use of this container aspect, change the sidebar and foremost content material

components to be components as an alternative:


  
  ...

In most conditions, you'll be able to simply use the aspect with none customization, however if you happen to do must customise it, the next choices can be found:

  • Customized breakpoint sizes: This code makes use of a set of default breakpoint class names and min-width sizes, however you alter these defaults to be no matter you want. You may also override these values on a per-element foundation utilizing the breakpoints attribute.
  • Named containers: This code additionally helps named containers by passing a title attribute. This may be essential if it's essential nest container components. See the limitations section for extra particulars.

Right here is an instance that units each of those configuration choices:



Lastly, when bundling this code, be sure you use function detection and dynamic import() to solely load it if the browser does not help container queries.

if (!CSS.helps('container-sort: inline-measurement')) {
  import('./path/to/responsive-container.js');
}

Step 3: add fallback types to your CSS

The final step on this technique is so as to add fallback types for browsers that do not acknowledge the types outlined within the @container guidelines. Do that by duplicating these guidelines utilizing the breakpoint lessons that get set on the components.

Persevering with with the .photo-gallery instance from earlier than, the fallback types for the 2 @container guidelines may seem like this:

/* Container question types for the `MD` breakpoint. */
@container (min-width: 600px) {
  .photo-gallery {
    grid-template-columns: 1fr 1fr;
  }
}

/* Fallback types for the `MD` breakpoint. */
@helps not (container-type: inline-size) {
  :the place(responsive-container.MD) .photo-gallery {
    grid-template-columns: 1fr 1fr;
  }
}

/* Container question types for the `XL` breakpoint. */
@container (min-width: 1000px) {
  .photo-gallery {
    grid-template-columns: 1fr 1fr 1fr;
  }
}

/* Fallback types for the `XL` breakpoint. */
@helps not (container-type: inline-size) {
  :the place(responsive-container.XL) .photo-gallery {
    grid-template-columns: 1fr 1fr 1fr;
  }
}

On this code, for every @container rule there may be an equal rule conditionally matching the aspect if the corresponding breakpoint class is current.

The portion of the selector matching the aspect is wrapped in a :where() practical pseudo-class selector, to maintain the specificity of the fallback selector equal to the specificity of the unique selector inside the @container rule.

Every fallback rule can be wrapped in an @helps declaration. Whereas this isn't strictly vital for the fallback to work, it implies that the browser fully ignores these guidelines if it helps container queries, which may enhance type matching efficiency on the whole. It additionally doubtlessly permits construct instruments or CDNs to strip these declarations in the event that they know the browser helps container queries and does not want these fallback types.

The primary draw back of this fallback technique is it requires you to repeat type declaration twice, which is each tedious and error susceptible. Nevertheless, if you happen to're utilizing a CSS preprocessor, you'll be able to summary that right into a mixin that generates each the @container rule and the fallback code for you. Here is an instance utilizing Sass:

@use 'sass:map';

$breakpoints: (
  'SM': 400px,
  'MD': 600px,
  'LG': 800px,
  'XL': 1000px,
);

@mixin breakpoint($breakpoint) {
  @container (min-width: #{map.get($breakpoints, $breakpoint)}) {
    @content material();
  }
  @helps not (container-type: inline-size) {
    :the place(responsive-container.#{$breakpoint}) & {
      @content material();
    }
  }
}

Then, after you have this mixin, you might replace the unique .photo-gallery part types to one thing like this, which eliminates the duplication completely:

.photo-gallery {
  show: grid;
  grid-template-columns: 1fr;

  @embrace breakpoint('MD') {
    grid-template-columns: 1fr 1fr;
  }

  @embrace breakpoint('XL') {
    grid-template-columns: 1fr 1fr 1fr;
  }
}

And that is all there may be to it!

Recap

So, to recap, here is how you can replace your code to make use of container queries now with a cross browser fallback.

  1. Identification parts that you just wish to type relative to their container, and replace the @media guidelines of their CSS to make use of @container guidelines. Additionally (if you happen to're not already), standardize on a set of breakpoint names to match the scale situations in your container guidelines.
  2. Add the JavaScript that powers the customized aspect, after which add the aspect to any content material areas in your web page that you really want your parts to be relative to.
  3. To help older browsers, add fallback types to your CSS that match towards the breakpoint lessons that get routinely added to the components in your HTML. Ideally use a CSS preprocessor mixin to keep away from having to jot down the identical types twice.

The beauty of this technique is there's a one-time setup price, however after that it does not take any extra effort so as to add new parts and outline container-relative types for them.

Seeing it in motion

Most likely one of the simplest ways to grasp how all these steps match collectively is to see a demo of it in action.



A video of a consumer interacting with the container queries demo site. The consumer is resizing the content material areas to indicate how the part types replace based mostly on the scale of their containing content material space.

This demo is an up to date model of a site created in 2019 (earlier than container queries existed) to assist illustrate why container queries are important to constructing really responsive part libraries.

Since this website already had types outlined for a bunch of "responsive parts", it was an ideal candidate to check out the technique launched right here on a non-trivial website. Seems, it was truly fairly easy to replace and required nearly no modifications to the unique website types.

You may take a look at the full demo source code on GitHub, and make sure you look particularly on the demo component CSS, to see how the fallback types are outlined. If you wish to take a look at out simply the fallback habits, there is a fallback-only demo that features simply that variant—even in browsers that help container queries.

Limitations and potential enhancements

As talked about initially of this submit, the technique outlined right here works nicely for almost all of use circumstances that builders truly care about when reaching for container queries.

That stated, there are some extra superior use circumstances that this technique deliberately doesn't try and help, addressed subsequent:

Container question items

The specification for container queries defines a lot of new units, which can be all relative to the container's measurement. Whereas doubtlessly helpful in some circumstances, nearly all of responsive designs can doubtless be achieved by means of present means, corresponding to percentages or utilizing grid or flex layouts.

That stated, if you happen to do want to make use of container question items, you might simply add help for them utilizing custom properties. Particularly, by defining a customized property for every unit used on the container aspect, like this:

responsive-container {
  --cqw: 1cqw;
  --cqh: 1cqh;
}

After which each time it's essential entry the container question items, use these properties, slightly than utilizing the unit itself:

.photo-gallery {
  font-size: calc(10 * var(--cqw));
}

Then, to help older browsers, set the values for these customized properties on the container aspect inside the ResizeObserver callback.

class ResponsiveContainer extends HTMLElement {
  // ...
  updateBreakpoints(contentRect) {
    this.type.setProperty('--cqw', `${contentRect.width / 100}px`);
    this.type.setProperty('--cqh', `${contentRect.top / 100}px`);

    // ...
  }
}

This successfully helps you to "move" these values from JavaScript to CSS, after which you have got the complete energy of CSS (for instance, calc(), min(), max(), clamp()) to control them as wanted.

Logical properties and writing mode help

You'll have observed using inline-size slightly than width within the @container declarations in a few of these CSS examples. You'll have additionally observed the brand new cqi and cqb items (for inline and block sizes, respectively). These new options replicate CSS's shift to logical properties and values slightly than bodily or directional ones.

Sadly, APIs like Resize Observer nonetheless report values in width and top, so in case your designs want the flexibleness of logical properties, then it's essential determine that out for your self.

Whereas it is doable to get the writing mode utilizing one thing like getComputedStyle() passing within the container aspect, doing so has a cost, and there is not likely a great way to detect if the writing mode modifications.

Because of this, one of the best strategy is for the aspect itself to simply accept a writing mode property that the positioning proprietor can set (and replace) as wanted. To implement this, you'd observe the identical strategy proven within the earlier part, and swap width and top as wanted.

Nested containers

The container-name property helps you to give a container a reputation, which you'll then reference in an @container rule. Named containers are helpful if in case you have containers nested inside containers and also you want sure guidelines to solely match sure containers (not simply the closest ancestor container).

The fallback technique outlined right here makes use of the descendant combinator to type components matching sure breakpoint lessons. This may break if in case you have nested containers, since any variety of breakpoint lessons from a number of container aspect ancestors may match a given part on the identical time.

For instance, right here there are two components wrapping the .photo-gallery part, however because the outer container is bigger than the internal container, they've totally different breakpoint lessons added.


  ...
  
    ...
    

...

On this instance, the MD and LG class on the outer container would have an effect on the type guidelines matching the .photo-gallery part, which does not match the habits of container queries (since they solely match towards the closest ancestor container).

To cope with this, both:

  1. Be sure you at all times title any containers that you just're nesting, after which guarantee your breakpoint lessons are prefixed with that container title to keep away from clashes.
  2. Use the child combinator as an alternative of the descendant combinator in your fallback selectors (which is a little more limiting).

The nested containers part of the demo website has an instance of this working utilizing named containers, together with the Sass mixin it uses within the code to generate the fallback types for each named and unnamed @container guidelines.

What about browsers that do not help :the place(), Customized Parts, or Resize Observer?

Whereas these APIs could appear comparatively new, they've all been supported in all browsers for greater than three years, they usually're all a part of Baseline extensively obtainable.

So until you have got information exhibiting that a good portion of your website's guests are on browsers that do not help one among these options, then there isn't any purpose to not freely use them with out a fallback.

Even then, for this particular use case, the worst that might occur is the fallback will not work for a really small proportion of your customers, which suggests they're going to see the default view slightly than a view optimized for the container measurement.

The performance of the positioning ought to nonetheless work, and that is what actually issues.

Why not simply use a container question polyfill?

CSS options are notoriously difficult to polyfill, and customarily require re-implementing the brower's total CSS parser and cascade logic in JavaScript. Because of this, CSS polyfill authors should make many tradeoffs that nearly at all times include quite a few function limitations in addition to vital efficiency overhead.

For these causes, we typically do not suggest utilizing CSS polyfills in manufacturing, together with the container-query-polyfill from Google Chrome Labs, which is now not maintained (and was primarily supposed for demo functions).

The fallback technique mentioned right here has fewer limitations, requires far much less code, and can carry out considerably higher than any container question polyfill ever may.

Do you even must implement a fallback for older browsers?

If you're involved about any of the constraints talked about right here, it is in all probability value asking your self whether or not you truly must implement a fallback within the first place. In any case, the simplest strategy to keep away from these limitations is to only use the function with none fallbacks. Actually, in lots of circumstances, that could be a superbly affordable alternative.

In line with caniuse.com, container queries are supported by 90% of world web customers, and for many individuals studying this submit, the quantity is probably going fairly a bit larger for his or her consumer base. So it is essential to understand that most of your customers will see the container-query model of your UI. And for the ten% of customers that will not, it is not like they are going to have a damaged expertise. When following this technique, within the worst case these customers will see the default or "cell" structure for some parts, which isn't the tip of the world.

When making tradeoffs, it is a good observe to optimize for almost all of your customers—slightly than defaulting to a lowest-common-denominator strategy that provides all customers a constant, however sub-par, expertise.

So earlier than you assume which you can't use container queries resulting from lack of browser help, truly take the time to think about what the expertise could be like if you happen to did select to undertake them. The tradeoff could also be nicely value it, even with none fallbacks.

Trying ahead

Hopefully this submit has satisfied you that it's doable to make use of container queries in manufacturing now, and that you do not have to attend for years till all non-supporting browsers fully disappear.

Whereas the technique outlined right here does require a bit of additional work, it ought to be easy and simple sufficient that most individuals can undertake it on their websites. That stated, there may be definitely room to make it even simpler to undertake. One thought could be to consolidate a variety of the disparate components right into a single part—optimized for a particular framework or stack—that handles all the glue give you the results you want. If you happen to construct one thing like this, tell us and we might help market it!

Lastly, past container queries, there are such a lot of superb CSS and UI options that are actually interoperable throughout all main browser engines. As a group, let's determine how we are able to truly use these options now, so our customers can profit.


Replace (July 25, 2024): initially the steering in "Step 1" prompt that media queries and container queries may use the identical measurement situations. That is usually true however not at all times (as some causes rightly pointed out). The up to date steering now clarifies this and affords instance circumstances the place the scale situations may want to alter.

Replace (July 2, 2024): initially all the CSS code examples used Sass (for consistency with the ultimate advice). Primarily based on feedback from readers, the primary few CSS have been up to date to plain CSS, and Sass is simply used within the code samples that require using mixins.


Besides as in any other case famous, the content material of this web page is licensed below the Creative Commons Attribution 4.0 License, and code samples are licensed below the Apache 2.0 License. For particulars, see the Google Developers Site Policies. Java is a registered trademark of Oracle and/or its associates.

Final up to date 2024-07-25 UTC.











Source link

Leave a Comment

Your email address will not be published. Required fields are marked *

error

Enjoy this blog? Please spread the word :)

YouTube
YouTube
Pinterest
fb-share-icon
LinkedIn
Share
Instagram
Index
Scroll to Top