A Mobile-Only Grid

Check out this layout I wanted to pull off on the current design of this site:

Screenshot of a desktop layout where the sidebar has a stack of boxes.

Overlapped with a screenshot of a mobile layout where the first two in that stack of boxes are side-by-side, but the other boxes are full width below that.

See how just the first two boxes in the “sidebar” of the homepage are set side-by-side on narrow (mobile) layouts.

One way I could have done that is to target the sidebar, and turn it into a grid at that size so I could do the layout I wanted to with the first two boxes, then make sure all the rest of the boxes fill the width.

aside {
  ...
  
  @media (max-width: 750px) {
    display: grid;
    gap: 1rem;

    /* first two boxes will 
       naturally fall into these columns */
    grid-template-columns: 1fr 2fr;

    /* target the rest of the boxes
       and make sure they go full width */
    .box:nth-child(n + 3) {
      grid-column: 1 / -1;
    }
  }
}Code language: CSS (css)

But. I didn’t do it like that.

Something makes my brain go: Why involve all the rest of the boxes in this little scheme of yours? They are innocent bystanders.

So to isolate the functionality, I wrap the first two boxes in another div I can target.

<aside>

  <div class="mobile-grid">
    <div class="box">1</div>
    <div class="box">2</div>
  </div>

  <div class="box">3</div>
  <div class="box">4</div>
  <div class="box">5</div>

</aside>Code language: HTML, XML (xml)

Even with that, I could have the mobile-grid be a grid with a single column, then change to the multi-column setup at the narrow breakpoint (probably should be a container query honestly).

But. I didn’t do that either. I did this:

.mobile-grid {
  display: contents;
  @media (max-width: 750px) {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 1rem;
  }
}Code language: CSS (css)

That display: contents; bit says: pretend like this element doesn’t even exist and move all the children up one level in the DOM.

I’m still using a breakpoint, but I can focus the styling to be like this only matters when this element “kicks in”. I like that approach, as it isolates functionality to the relevant elements.

HUGE WARNING.

Using display: contents; has a bad accessibility history. I’ve mentioned this before, but accessibility expert Eric Bailey has straight up said: display: contents considered harmful and means it:

I now view the declaration as predictably unpredictable. The common response of “just test its support with assistive technology” does not apply here, either—there is a non-trivial chance that desirable behavior for the declaration in a current browser version isn’t guaranteed to persist for future versions of that browser.

To reiterate the severity at play here: display: contents’ behavior is akin to the links you use on your website or web app one day losing the ability to work because you applied CSS to them. This loss of ability comes without warning, and there’s no console error or visual indication that things are amiss.

Part of why I wanted to play with this is because I saw that Safari 17 has shipped (on desktop next week), and I count twelve fixes for display: contents in the note. Now Safari is just one browser, and the fixes in bleeding edge version don’t make this a solved problem. I made the decision to use it because:

  1. I consider this low-stakes. I’m not messing with lists, tables, or any important interactive elements. There are a few <a> links inside, and if they were rendered as “just text” that would be broken, but I don’t think that’s the case right now after looking at the accessibility tree (Chrome, Firefox).
  2. I don’t want to give up on a useful part of the web platform because it had a shitty start. I’ll use it, I’ll be careful about it so I’m not ruining anyone’s day, and I’ll incentivize browser makers to fix it and keep it fixed.

As a general best practice for now, I’d agree with “don’t use it” for now. I showed an approach to making this work that’s perfectly fine first.

Also, here’s that approach converted to container queries instead:

aside {
  ...
  container: aside / inline-size;

  .mobile-grid {
    @container aside (max-width: 300px) {
      display: grid;
      gap: 1rem;
      grid-template-columns: 1fr 2fr;
    }
  }
}Code language: CSS (css)

Feel free to smurf around with this reduced test case.

Thoughts? Email me or comment below. Also CodePen PRO is quite a deal. 🙏

5 responses to “A Mobile-Only Grid”

  1. Emma says:

    Hi, Chris!

    I’m pretty sure a simple flexbox wrapping container could have fit the bill without any query. Something like this (but with better selectors):

    .parent-container { display: flex; flex-wrap: wrap; }
    .parent-container>* { flex-grow: 1 }
    .child-one { flex-basis: 100px; }
    .child-two { flex-basis: 200px; }

    When the parent-container is fitted on the left side and is less than 300px wide, the children will pile up vertically. When it gets on top and is more than 300px wide, the children will be side-by-side.

    Or maybe am I missing some other issue?

    • Chris Coyier says:

      Could work!

      Although the more I think about it, that, and even the container query approach, isn’t quite what I’m after. It’s not so much about the space that the “sidebar” occupies, and in fact it’s roughly the same narrow space when it’s a sidebar or a mobile single column. It’s more about switching stuff up when it does become that mobile single column. So maybe a @media query when that break happens is the right way.

      • Emma says:

        I see!

        Ultimately, I’d say the better solution depends on the styling technique. if the main layout is modified through media queries, at a certain width, then the solution should consistent with this approach since it depends on it.

        So, if one uses media queries to change the main layout, use the same one to change the inner “grid”. If instead the approach to the main layout is to wrap flexboxes based on available space (which I think could work too), then the inner “grid” should use flexbox wrapping too.

        Does that make sense? Hehe.

      • Chris Coyier says:

        Yeah I like that thinking. Use the same approaches and definitely the same breakpoints. Would be nice to get @custom-media eh? https://www.w3.org/TR/mediaqueries-5/#custom-mq

        I’ve also heard it might be ENV vars that solve this sharing problem possibly, we’ll see https://drafts.csswg.org/css-env-1/

      • Emma says:

        Absolutely! This and @custom-selector would be fantastic.
        https://drafts.csswg.org/css-extensions/#custom-selectors

        And let’s hope ENV vars can be used as you just said..!

        A great day to you!

Leave a Reply

Your email address will not be published. Required fields are marked *