Comparing Switch & Container

Table of Contents
Archived:

✅ This feature has shipped in browsers. Our notes are likely out-of-date.

I began by pursuing both of these approaches, since they have interesting trade-offs.

Ability to Implement

The @container syntax is blocked by a dependence on single axis containment, but there is a plan to resolve that, and a prototype underway at Google.

The switch() syntax has working prototype at Igalia.

Summary

The switch proposal avoids containment by providing a limited set of features that will resolve after the browser-engine has completed the layout pass. That allows more flexibility for querying “available space” in an abstract way – but only on certain properties.

Currently that’s handled inline, one property at a time – but more work could be done to explore a block syntax that works within the limitations:

The @container proposal requires authors to define explicit “containers” that are no longer able to size based on intrinsic values – but provide a stable context for resolving queries. That would allow us to:

There are tradeoffs to either approach, and they could work well in unison.

The Query Target

Container queries require an element that will be adjusted, and a context that will be queried. The query target is always an element that is extrinsically sized.

The switch() proposal allows you to adjust properties of an element based on its own dimensions. The target of a switch query is the element itself.

Block vs Property Conditions

You can think of them a bit like inline & block conditional statements in other languages. Sass, for example, has both:

@if <condition> {
.example { /* block of rules when condition is met */ }
}

.example {
grid-template: if(
<condition>,
1fr auto /* value when condition met */,
auto /* default when condition is not met */
);
}

A switch function can only change one property at a time, while a container @-rule would allow more comprehensive changes. Most languages provide both, because either one can become bulky & painful depending on the use-case.

Use Cases

As far as I can tell, there are a limited number of use-cases for switch(), and all the potential use-cases can be solved by @container – though sometimes an additional element is required.

Here are a few uses that only @container can solve (or solves much more easily):

There are two potential advantages of switch() in theory:

It’s hard to comment on the first case. So far I haven’t had much trouble using the early prototype of 1D containment – but we still don’t know exactly where things will land.

The second case is more clear, and has been the basis of all switch() demos so far. Consider the following HTML & CSS:

<section class="card-grid">
<div class="card">...</div>
<div class="card">...</div>
<div class="card">...</div>
<div class="card">...</div>
</section>
.card-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(20em, 1fr));
}

.card {
display: grid;
/* we want to change this value based on the track size */
grid-template: "image" auto "content" 1fr "footer" auto / 100%;
}

The size of .card-grid does not accurately reflect the available space for a given card, but there is no other external “container” that .card can use to adjust the grid-template.

Switch allows us to solve that on the .card, without any extra elements:

.card {
display: grid;
grid-template: switch(
(available-inline-size > 40em) "image content" 1fr "footer footer" auto / auto 1fr;
"image" auto "content" 1fr "footer" auto / 100%;
);
}

Using @container, we would need to add an extra wrapping element – so that the card component has an external track-sized container to query:

<section class="card-grid">
<div class="card-container"><div class="card">...</div></div>
<div class="card-container"><div class="card">...</div></div>
<div class="card-container"><div class="card">...</div></div>
<div class="card-container"><div class="card">...</div></div>
</section>
/* the outer element can get containment… */
.card-container {
contain: layout inline-size;
}

/* which gives .card something to query against */
@container (inline-size > 30em) {
.card {
grid-template: "image content" 1fr "image footer" auto / 1fr 3fr;
}
}

The extra markup might not be ideal? But on the other hand, I’m not sure this single use-case warrants a second approach?

Other inline-conditions

There are various other discussions about adding inline-conditional functions to CSSif(), nth(), cond(), etc. Some of those proposals also allow comparisons against 100% as part of the condition statement.

In most cases 100% would be identical or similar to available-inline-size. It might be interesting to see if one of these more generic proposals can be used to cover the switch() use-case.

Learning & Teaching

While I see potential advantages in using switch() for a limited set of use-cases, those advantages come down to it’s behavior being more implicit – which also makes it a bit harder to understand.

With @container:

The primary point of confusion is likely understanding which element is being queried in a given case (maybe by marking “containers” in the DOM inspector)

With @switch:

I expect confusion around what is being queried, what those queries can be applied to, and (in both cases) why?