Skip to main content

Tagged “containerqueries”

An interactive guide to CSS Container Queries, by Ahmad Shadeed

This is a wonderful guide that’s choc-full of practical examples.

CSS container queries help us to write a truly fluid components that change based on their container size. In the next few years, we’ll see less media queries and more container queries.

Lots of inspiration here for when I start using Container Queries in earnest.

First play with CSS Container Queries

In responsive design we generally want a single component to get different styles in different contexts. Up until recently the prevailing method of context-awareness was to use a CSS media query to query the viewport size. This wasn’t ideal. For example you might want an component to be styled differently when in a narrow context such as a sidebar (regardless of the device size), and viewport-based queries don’t help with that.

But everything has changed. We can now use CSS to query the size of any given container and this feature is supported in all major browsers.

There’s a bit of new syntax to learn, so I recently had my first play with container queries on codepen.

My pen is pretty trivial, but the goal was specifically to do the most minimal test that lets me test-drive the key syntax. It turns out that it’s quite straightforward.

Define an element as a container:

.sidebar {
  container: ctr-sidebar / inline-size;
}

Change the styles of another element (.foo) when it’s inside that container and the container’s inline-size (the logical property name for width) matches a given query:

@container ctr-sidebar (max-width: 300px) {
  .foo {
    // context-specific styles go here 
  }
}

Note that you could also omit the ctr-sidebar context in the above query, if you wanted the change to apply in all defined containers.

Container Queries in Web Components | Max Böck

Max’s demo is really clever and features lots of interesting web component related techniques.

I came up with this demo of a book store. Each of the books is draggable and can be moved to one of three sections, with varying available space. Depending on where it is placed, different styles will be applied to the book.

Some of the techniques I found interesting included:

  • starting with basic HTML for each book and its image, title, and author elements rather than an empty custom element, thereby providing a resilient baseline
  • wrapping each book in a custom book-element tag (which the browser would simply treat like a div in the worst case scenario)
  • applying the slot attribute to each of the nested elements, for example slot="title"
  • including a template with id="book-element" at the top of the HTML. This centralises the optimal book markup, which makes for quicker, easier, and less disruptive maintenance. (A template is parsed but not rendered by the browser. It is available solely to be referenced and used by JavaScript)
  • including slots within the template, such as <slot name="title">
  • putting a style block within the template. These styles target the book component only, and include container query driven responsiveness
  • targetting the <book-element> wrapper element in CSS via the :host selector, and applying contain to set it as a container query context
  • targetting a slot in the component CSS using (for example) ::slotted(img)

Thoughts

Firstly, in the basic HTML/CSS, I might ensure images are display: block and use div rather than span for a better baseline appearance should JavaScript fail.

Secondly, even though this tutorial is really nice, I still find myself asking: why use a Web Component to render a book rather than a server-side solution when the latter removes the JS dependency? Part of the reason is no doubt developer convenience—people want to build component libraries in JavaScript if that’s their language of choice. Also, it requires less backend set-up and leads to a more portable stack. And back-end tools for component-based architectures are generally less mature and feature-rich then those for the front-end.

One Web Component specific benefit is that Shadow DOM provides an encapsulation mechanism to style, script, and HTML markup. This encapsulation provides private scope that both prevents the content of the component from being affected by the external document, and keeps its CSS and JS from leaking out… which might be nice for avoiding the namespacing you’d otherwise have to do.

I have a feeling that Web Components might make sense for some components but be neither appropriate nor required for others. Therefore just because you use Web Components doesn’t mean that you suddenly need to feel the need to write or refactor every component that way. It’s worth bearing in mind that client-side JavaScript based functionality comes with a performance cost—the user needs to wait for it to download. So I feel there might be a need to exercise some restraint. I want to think about this a little more.

Other references

Observer APIs in a nutshell

I’ve played with the various HTML5 Observer APIs (IntersectionObserver, ResizeObserver and MutationObserver) a little over the last few years—for example using ResizeObserver in a container query solution for responsive grids. But in all honesty their roles, abilities and differences haven’t yet fully stuck in my brain. So I’ve put together a brief explainer for future reference.

Intersection Observer

Lets you watch for when an element of your choice intersects with a root element of your choice—typically the viewport—and then take action in response.

So you might watch for a div that’s way down the page entering the viewport as a result of the user scrolling, then act upon that by applying a class which animates that div’s opacity from 0 to 1 to make it fade in.

Here’s how it works:

  • Instantiate a new IntersectionObserver object, passing in firstly a callback function and secondly an options array which specifies your root element (usually the viewport, or a specific subsection of it).
  • Call observe on your instance, passing in the element you want to watch. If you have multiple elements to watch, you could call observe repeatedly in a loop through the relevant NodeList.
  • in the callback function add the stuff you want to happen in response to “intersecting” and “no longer intersecting” events.

Mutation Observer

Lets you watch for changes to the attributes or content of DOM elements then take action in response.

You might use this if you have code that you want to run if and when an element changes because of another script.

Here’s how it works:

  • Your typical starting point is that you already have one or more event listeners which modify the DOM in response to an event.
  • Instantiate a new MutationObserver object, passing in a callback function.
  • The callback function will be called every time the DOM is changed.
  • Call observe on your instance, passing in as first argument the element to watch and as second argument a config object specifying what type of changes you’re interested in, for example you might only care about changes to specific attributes.
  • your callback function provides an array of MutationRecord objects—one for each change that has just taken place—which you can loop through and act upon.

Resize Observer

Lets you watch for an element meeting a given size threshold then take action in response.

For example you might add a class of wide to a given container only when it is wider than 60em so that new styles are applied. This is a way of providing container query capability while we wait for that to land in CSS.

Or you might load additional, heavier-weight media in response to a certain width threshold because you feel you can assume a device type that indicates the user is on wifi. Adding functionality rather than applying styles is something we could not achieve with CSS alone.

Given that Container Query support is coming in CSS and that we can usually get by without it in the meantime, I don’t think it’s something I need so desperately that I’ll keep reaching for JavaScript to achieve it. However that’s not the only use for ResizeObserver.

It’s also worth remembering that it’s not all about width: ResizeObserver can also be used to detect and respond to changes to an element’s height. An example might be watching for changes to a chat window’s height—something that’s liable to happen as new messages appear—and then ensuring the focus stays at the bottom, on the latest message.

References

Steve Griffiths has some great video tutorials on these topics.

Minimalist Container Queries

Scott Jehl’s experimental take on a container/element query aimed at letting us set responsive styles for our elements based on their immediate context rather than that of the viewport.

I made a quick and minimal take on approximating Container/Element Queries using a web component and basic CSS selectors.

The idea is that for any given instance of the <c-q> custom element / web component you would define a scoped custom property which sets the pixel min-widths you’re interested in, like so:

c-q {
  --breakpoints: "400 600 800";
  background: black;
}

Zero to many of those numeric min-width values will appear in the element’s data-min-width HTML attribute based on which (if any) of them the element’s width is equal to or greater than.

You can style the element based on their presence using the ~= attribute selector, like this:

c-q[data-min-width~="400"] { 
  background: green; 
}
c-q[data-min-width~="600"] {
  background: blue;
}

See also Scott’s tweet announcing this which contains some interesting contributions including Heydon’s watched-box.

All of the various JavaScript approaches/experiments are summarised in CSS-Tricks’s article Minimal Takes on Faking Container Queries.

(via @scottjehl)

See all tags.

External Link Bookmark Note Entry Search