West Wind Hero Image

Rick Strahl's Weblog

Wind, waves, code and everything in between...
.NET • C# • Markdown • WPF • All things Web
Contact   •   Articles   •   Products   •   Support   •  
Sponsored by:
Markdown Monster - The Markdown Editor for Windows
On this page:

Container Banner

I've been using @media() queries in CSS forever, but on a number of occasions these type of Viewport-only limits on width are problematic, especially in UIs that are resizable or otherwise dynamically change.

My specific scenario is a resizable panel display where a fixed @media() width would cause problems due to the container, rather than the window, is resizing.

Most of us have likely used the media() at-rule for responsive design, but it's limited to the Viewport width. But, there's another, more recent type of conditional CSS at-rule that can be used to restrict sizing: The @container() at-rule, which I have been unaware of until recently. @container is a relatively new addition to CSS that was only introduced to browsers a few years back in 2022/23, so perhaps I'm not the only one who got caught unawares.

Here's are how the two at-rules differ from each other:

  • @media()
    Works off of min/max values of the ViewPort of the host frame.
    more info

  • @container()
    Allows specifying of a Parent Container Name to apply the min/max values to.
    more info

Problems with Media Queries

@media() queries are great, when you're dealing with page wide content. If your page consists of a single host container that doesn't change, using @media() queries works perfectly fine.

But... if you have multiple content panes in your page, or resizable or movable ones at that, @media queries can be too broad of a brush to apply to get the result you need to control nested container content.

For reference @media queries look like this:

.topic-outline {
    display: none;
}

@media(min-width: 1100px){ 
    .topic-outline {
        position: fixed;
        display: block;
        overflow-y: auto;

        top: 7em;
        right: 1em;
        max-height: calc(100vh - 10em);
        width: 255px;

        font-size: 0.725em !important;
        line-height: 1.4em;

        margin-top: 0px;
        margin-left: 10px;
        padding: 20px 3px 0 20px;
    }
}    

The @media(min-width: 1100px) { } block contains a subset of CSS that is applied when the condition is true - in this case if the Viewport width exceeds a minimum of 1100px.

The above CSS is from a specific example from one of my documentation Web sites generated with Documentation Monster, which originally used media() queries in its topic templates used for rendering topics. DM renders the final output into a two-pane view that contains a table of contents on the left and a resizable content pane on the right with a splitter in the middle. The content pane then conditionally displays a left hand sidebar that shows a document outline if the pane is large enough.

The following screen capture demonstrates some of the problems with media() only queries. Keep an eye on the bookmark sidebar on the right:

mm docs container sizing media Figure 1 - Using @media() queries allows sizing per window, which can cause problems if you have resizable content that needs to conditionally render or modify content. Here you can see some inconsistent behavior for the bookmarks sidebar and content pane when then content pane is resized.

If you look closely you can see that the initial full window resizes work properly with the sidebar showing and hiding as expected.

However, resizing with the splitter from a size where the bookmark sidebar shows, to a size where it should hide, you can see the sidebar causing the content to get crunched, rather than hiding the sidebar and allowing the content to flow wider which is the expected and designed behavior. It works with Window resizing but fails with container resizing. Similar behavior shows with the hamburger menu that is showing and hiding the TOC.

This example uses the @media() at-rule, and it's misbehaving because the sizing condition is based on window sizing, not container sizing. The result is that in some instances you don't get the right behavior of the sidebar showing or hiding as it should.

Fixing this is not possible with @media() queries alone, because it only works as expected when the entire window size changes.

Note that in this scenario the @media() query also misrepresents the intent of the size restriction. Specifically in Documentation Monster there's a preview that uses the same HTML but always hides the TOC on the left. So right of the start the @media() query sizing in that scenario is off by the 350px or so of the TOC panel. IOW the intent of the sizing here is clearly on the container, not the window, but a @media() query alone doesn't really address that. There are ways around that with extra CSS class definitions but that gets messy quick as you have to duplicate many CSS attributes.

@container Queries

The @container at-rule fixes this by explicitly allowing you to specify a container to restrict by for the query condition. So rather than being tied to the window, a @container() query is scoped to a specific container.

To get back to the Documentation example shown above here's is the example behaving correctly with the @container() query:

mm docs container sizing
Figure 2 - Using @container() queries allows more control by selecting a specific container to apply a media query to. Here you can see the bookmarks sidebar correctly diplaying depending on the content container only.

Notice that now, using the slider or hamburger menu correctly shows and hides the bookmark sidebar as the the sizing is now dependent on the size of the content pane, rather than the size of the window.

Yay.

@container Syntax: A two-step Process

To use the @container at-rule in the same way as the @media query above, is a two part process:

  • Setting a container-name in the parent CSS Class
    The parent container which is targeted by @container() needs to be explicitly defined in the CSS with something like container-name: main-content to be used as a parent in a @container() query.

  • Applying the @container() at-rule
    Once a container has been defined with container-name you can then use it with:

    @container main-content (min-width: 1100) { /* css styles */  }
    

    The behavior otherwise is identical to a @media() query block including the same min-width/max-width rules.

A Space after the Name is Required!

When I originally found out about @container(), I made the mistake of writing it @container main-content(min-width: 1100px). Notice no space between the name and the () query condition and that did not work. And yes I spent 20 minutes cursing that this feature doesn't work, only to have an agent slap me in the face with the space requirement 😄. So:

The space after the name is absolutely required!

@container main-content (min-width: 1100px)
                       ^

No Container Name works too, but...

Actually, the container-name and specification in the @container are not absolutely required. If you don't specify one, the immediate parent element(s) of the style(s) in the CSS block are used for the container it's applied to.

But, due to the fact that this can be pretty arbitrary I personally prefer to be explicit and provide a specific container via container-name.

So in this CSS:

@container (width > 920px) {
    .topic-outline {
        display: block;
    }

    .content-pane {
        margin-right: 235px;
    }

    .footer {
        font-size: 0.9rem;
    }
}

Each style's parent container is considered rather than a single container. As you might imagine this can get confusing quickly.

If you don't use a container-name I'd make sure to stick to embedding a single style or at least any styles that have the same parent. Alternately if you want to control several elements, create a new CSS class and apply the container-name to it, then assign the class to all affected elements to be explicit. This avoids confusing and hard to debug scenarios later on.

Applying the @container at-rule

Let's break down the full syntax:

Start by setting a container-name in the container element that the resizing should be based on. I'm using an #id here, but you can point at any CSS selector:

#MainContent {
    container-name: main-content;
    container-type: inline-size;
}

In my case I have an explicit Id for the content container so I used that. If you don't have an explicit CSS style defined that matches your container exactly, you need to create one and add container-name.

Once you have container-name defined in your CSS, you can then reference the it in the @container() directive:

@container main-content (min-width: 920px){
   .topic-outline {
       position: fixed;
       display: block;
       overflow-y: auto;

       top: 7em;
       right: 1em;
       max-height: calc(100vh - 10em);
       width: 255px;

       font-size: 0.725em !important;
       line-height: 1.4em;

       margin-top: 0px;
       margin-left: 10px;
       padding: 20px 3px 0 20px;
   }
@container main-content (min-width: 1100px) {
        .topic-outline {
            width: 400px;
            font-size: 0.725em !important;
        }
        .content-pane.topic-outline-visible {
            margin-right: 380px;
        }
    }
}   

And that's it!

Summary

CSS @container queries provide a much-needed solution for container-level responsive design where viewport-based @media queries fall short.

With a few very minor changes to the CSS, I've resolved a very long running nagging CSS issue, that I put up with for a long time. At its original inception this issue had no quick resolution because @container() didn't exist at the time other than resorting to script code.

But now, with this relatively new @container() feature I get the intended behavior with minimal effort and a very simple change path from the original behavior. Nice!

@container() was new to me since it is a relatively new at-rule, so it might be new to you too, so I hope this is useful to some of you...

Pickleball Bear TShirt at Goldenbear T-Shirts

Resources

this post was created and published with the Markdown Monster Editor
Posted in: HTML  CSS