Martin Winkler published a package that polyfills Element.matchContainer
to get notified in script when a Container Query matches/unmatches. Under the hood, it uses a StyleObserver
.
~
Window.matchMedia()
With Window.matchMedia()
you can observe whether a Media Query matches the given document.
const mql = window.matchMedia("(max-width: 600px)");
console.log(mql.matches); // true when the document is ≤ 600px
Furthermore you can use it to observe changes, by listening to the change
event on the returned MediaQueryList
object.
const mql = window.matchMedia("(max-width: 600px)");
mql.addEventListener("change", (e) => {
console.log(e.matches);
});
~
Element.matchContainer()
One of the open requests at the CSS Working Group is to have something similar to Window.matchMedia()
but then for Container Queries. One of the suggested solutions is to have something like Element.matchContainer()
.
Today, Martin Winkler published a package that polyfills exactly that: match-container
:
Element.matchContainer()
is to@container
whatWindow.matchMedia()
is to the@media
query CSS feature.
It allows you to do things like this:
import "https://esm.sh/match-container";
const cql = $someElement.matchContainer("(width < 400px)");
cql.addEventListener("change", (e) => {
console.log(e.matches);
});
Or this:
import "https://esm.sh/match-container";
const cql = $someElement.matchContainer("style(--my-property: some-value)");
cql.addEventListener("change", (e) => {
console.log(e.matches);
});
Sweet!
~
StyleObserver
under the hood
Peeking under the hood, I see Martin uses my technique from @bramus/style-observer
: set up a transition on some custom properties and listen for their transitionrun
event. These custom properties automatically get registered and applied on the registered Container Query.
When
Element.matchContainer()
is called the polyfill inserts a corresponding@container
query into the CSSOM. The observed element is tethered to an attribute selector inside this query block by a correspondingdata-*
attribute, ensuring the target element is the only element matched by this selector. When the@container
query matches a prepared CSS custom property is set on the observed element. A style observer (heavily inspired by Bramus’ StyleObserver) finally triggers the callback in the script code.
For example, if you observe "(width < 400px)"
, this piece of CSS automatically gets injected into the page.
@property --container-query-observer-bae45330cd3d4e0e96b60d26b57009b5-1740410397140-0 {
syntax: "<custom-ident>";
inherits: false;
initial-value: --false;
}
@container (width < 400px) {
[data-container-query-observer-bae45330cd3d4e0e96b60d26b57009b5-1740410397140-0] {
--container-query-observer-bae45330cd3d4e0e96b60d26b57009b5-1740410397140-0: --true;
}
}
The attribute you see there is also auto-generated and gets attached to the observed element.
~
Demos
Here are two demos that use match-container
. The first one uses a size query and the second one a style query. Whenever the container query matches/unmatches, the log gets prepended with a new entry.
See the Pen
Element.matchContainer() demo by Bramus (@bramus)
on CodePen.
See the Pen
Element.matchContainer() demo – Style Query by Bramus (@bramus)
on CodePen.
‼️ Don’t forget that container queries query a parent Element, so you need to attach .matchContainer
on a child of the container (!).
Also don’t forget to apply container-type: inline-size
when doing .matchContainer(<size-query>)
.
~
In Closing
It’s very nice to see that Martin got to leverage the technique from @bramus/style-observer
to solve this longstanding request. It confirms to me once again that we need a native StyleObserver
built into the Web Platform.
You can get match-container
from NPM and check out its source code on GitHub.
~
# Spread the word
Feel free to reshare one of the following posts on social media to help spread the word:
~
🔥 Like what you see? Want to stay in the loop? Here's how:
Thank you for covering my polyfill in your blog article and giving it a wider reach.
Hope you don’t mind when I bring up a few point:
– Your first example of a _style query_ doesn’t need the outer parens `$someElement.matchContainer(“(style(–my-property: some-value))”)` (but I don’t know if it would still work, though)
– The headline in the first demo reads “Style Query” but should probably be “Size Query”
– The note about needing to register a custom property when it is used in a _style query_ might be based on a misunderstanding. The polyfill certainly doesn’t need it. So as long as your target browser would correctly match `@container style(–my-property: some-value)` when used purely in CSS you don’t need to register the property. Not sure if some browsers still have buggy support for _style queries_ in CSS and your note actually does refer to that fact.
Thanks for the feedback, Martin!
– The extra parens are not required indeed, but also do no harm. It’s an artifact of me copy-pasting from the size query example.
– Uhoh! I’ve updated the title.
– Yes, you’re right! The workaround I mentioned is only needed when observing the custom props themselves directly, not through a container query condition. I’ve removed the erroneous parts from the post.
Thanks again for making `match-container` – I’m really digging it!