Explicit Container Syntax

Table of Contents

The overall approach documented in my initial Proposal & Explainer has been approved by the CSSWG – but there is still some interest in adding explicit new syntax for establishing containment, as well as new container-relative units.

General terminology

CSS already has many different types of containers, containment, containing blocks, etc. That doesn’t necessarily mean we have to avoid the term “container” entirely – at this point we’re leaning into it with @container – but it does mean we need to define terms carefully.

These are a few of the essential parts, and the names I’m currently using for them:

The word “container” is already used quite often in CSS, but never on its own. Still, there are potential confusions with the term – especially if we want a new container property in addition to the existing contain property. So I asked twitter to help brainstorm additional terms:

Despite the issues, I’ll continue using container for now – since it’s the name most people have used for this feature since ~2010. 😂

But context stands out to me as a reasonable alternative.

Why add new syntax?

See CSSWG-drafts issue #6174 for public discussion of this issue.

There are several reasons to consider new syntax for establishing observable containers:

Since the new syntax will need to set containment values, currently defined by the contain property, we should attempt to build on top of that existing property.

Extending the contain property

If all query features required containment, we might think of them as shorthand values added to the the contain property. Much like the strict value, something like inline-query could apply values of layout, inline-size, and style:

main {
contain: inline-query;
}

Another variant of this might involve a function syntax:

main {
contain: query(inline-size);
}

Pros:

Cons:

Adding a new property

There is already existing precedent for other properties to “imply” containment. Content-visibility doesn’t change the computed value of contain, but it does change the used/applied value – triggering containment behavior. We also have features like block formatting context that are triggered by any number of different properties & combinations – while also now having an explicit display value.

From that perspective, we can think of contain as the explicit hook into containment minutia – but “containment” itself as a more generic behavior that can be triggered in various ways. Then we could add an explicit new property custom-designed for establishing query containers:

main {
container: inline-size;
}

Pros:

Cons:

At this point, a new property seems to me like the best path forward.

Proposed syntax for establishing containers

I’m sure we’ll bikeshed some of the details, but here’s an initial attempt, which is also posted to the CSSWG issue.

I’m proposing a new container property:

.selector {
container: inline-size;
}

This property would:

Different [container types][ctype] (dimensions, states, styles) are explored below.

This also presents an opportunity for adding named containers using a custom-identifier (also explored below).

If we do support both name & types, the container property would act as a shorthand:

.selector {
/* container: [<custom-ident>? <types>+] | none; */
container: my-widget inline-size style;
}

/* @container <container-query-list> { <stylesheet> } */
@container my-widget (inline-size >= 30em) { /* … */ }

If we do have both a “name” and a list of “types”, we would likely also want longhand properties for each. Those could be called container-name and container-type:

.selector {
/* container-name: <custom-ident> | none; */
container-name: page;

/* container-type: <types>+ | none; */
container-type: inline-size style;
}

Container name

While I suggested removing selectors from the @container syntax, having multiple container-types makes it more likely that there will be reasons to query different containers in each case.

The new syntax here also opens up the possibility of creating more flexible & repeatable named containers – not limited to selector-matching:

main, section {
container-type: inline-size;
cotainer-name: layout;
}

.my-component {
container-type: style;
container-name: component;
}

@container layout (inline-size >= 30em ) { /* … */ }
@container component (font-size >= 2rem) { /* … */ }

/* name is optional */
@container (inline-size >= 30em ) { /* … */ }

These queries would resolve as follows:

You could also query the name of the container, without any qualifiers. I’m not sure if that would have any real use-cases:

@container my-component { /* … */ }

Multiple different containers could share the same identifier – and descendants would query the nearest ancestor with that identifier.

Container types

This proposal identifies three broad groups of container-type that an author might want to observe and query…

Observable dimensions

In order to query the dimensions of a container, we would provide at least two values, and potentially more. These would all apply the necessary layout, size, and style containment:

If block-only containment is not possible, that will also rule out width/height as values.

main {
container: size;
}

@container (inline-size >= 30em) { /* … */ }
@container (min-aspect-ratio: 8/5) { /* … */ }

It would not make much sense to apply multiple dimension values at once.

Observable styles

We may be able to expose/query the computed values of other properties (especially custom properties) on the container. We likely only need a single value to expose that sort of query:

main {
container: style;
}

@container (--colors == dark) { /* … */ }
@container (font-size >= 2rem) { /* … */ }

It’s not clear to me what containment (if any) would be required for this to work.

Observable state

There has also been discussion about querying the “state” of the container:

If all “state” queries require similar containment, we could use a single keyword for these as well:

main {
container: state;
}

But it seems likely that different states might require establishing different types of containment – in which case each state might need to be listed individually. This likely needs more exploration.

From the other end, I expect we might want to designate state-queries with a functional syntax:

@container state(stuck) { /* … */ }

In which case we could consider using a similar functional syntax for exposing individual states:

main {
container: state(stuck);
}

Container-relative units

TBD