We recently redesigned this site and a part of that was a sidebar-based article view. That aspect was fine because the Every Layout Sidebar is designed to deliver that layout with no fuss. I threw a spanner into the works though because we have a component named <PreviewFrame>
and I wanted it to span the full width, including the sidebar because it needs room to be effective at showing the responsiveness of a demo.
Sure, we could do a fancy CSS grid layout, but we’d lose the intrinsic, flexible nature of the sidebar layout if we did that. We needed something that would preserve the flexible layout but also give us that expansive breakout capability.
Step up container unitspermalink
We’ve had viewport units for what seems forever at this point. They’re representative of the size of the viewport:
- Code language
- css
.my-element { /* 25% of the viewport’s inline width */ width: 25vi; }
Our post layout’s width doesn’t span the whole width of the viewport though, so these units are unfortunately, useless to us.
We can, however, take a percentage of a container’s width using the cqi
or cqw
units. These — similarly to viewport units — represent a percentage of a container. All you need to do to make these work how you expect is slap this on a parent element.
- Code language
- plaintext
.my-parent { container-type: inline-size; }
Building our layoutpermalink
With the tools in place, let’s get started. Here’s the HTML shell of the layout:
- Code language
- html
<article class="post sidebar" data-direction="rtl"> <div class="post__content"> <!-- Content goes here --> </div> <div class="post__sidebar"> <div class="post__meta"> <!-- Sidebar content goes here--> </div> </div> </article>
Yeh sure, more <div>
elements than what I imagine some are comfortable with, but I’ll take the flexible layout capability that those extra elements give us any day.
The parent <article>
has two classes: post
and sidebar
. The post
is a CUBE CSS Block and the sidebar
is a CUBE CSS Composition which is modified with a CUBE CSS Exception: data-direction="rtl"
. Let’s start with the sidebar.
- Code language
- css
.sidebar { display: flex; flex-wrap: wrap; gap: var(--gutter, var(--space-m)); } .sidebar:not([data-direction='rtl']) > :first-child { flex-basis: var(--sidebar-target-width, 20rem); flex-grow: 1; } .sidebar:not([data-direction='rtl']) > :last-child { flex-basis: 0; flex-grow: 999; min-width: var(--sidebar-content-min-width, 50%); } .sidebar[data-direction='rtl'] > :last-child { flex-basis: var(--sidebar-target-width, 20rem); flex-grow: 1; } .sidebar[data-direction='rtl'] > :first-child { flex-basis: 0; flex-grow: 999; min-width: var(--sidebar-content-min-width, 50%); }
It’s a modified version of the Every Layout Sidebar which supports the sidebar element being both on the left or the right. Instead of re-writing what we published in the Sidebar’s explainer, I’ll link to it for those interested to keep this article as short as possible.
With that in place, it’s time to do the magic stuff. Let’s start with the post
:
- Code language
- css
.post { --sidebar-content-min-width: 60%; --sidebar-target-width: clamp(16rem, 30vi, 30rem); --gutter: var(--space-xl); container-type: inline-size; }
We configure the sidebar
, telling it what sort of size we want its sidebar element to be, what the gutter (space) is and how much of the post
parent’s width the content element needs to be for the sidebar element to display inline with the content. This is a nice safeguard against having a super narrow content column on weird-sized viewports.
The most important part of this CSS though is container-type: inline-size
. This is because post
will be the container that our breakout element’s reference point. In other words, we want the breakout element to be the width of our post container, even though it will be a child of the content column.
Let’s add the breakout CSS:
- Code language
- css
.post__breakout { width: 100cqi; position: relative; z-index: 1; background: var(--color-dark); color: var(--color-light-glare); padding: var(--space-m); }
There’s various self explanatory decorative properties here, but the one we want to zoom in on is width: 100cqi
. Because our post
parent element has container-type: inline-size
, the cqi
unit is based on its width. We want the breakout to fill the width, so 100cqi
is equivalent to 100% of post
’s width.
Job done:
Let’s make it all look betterpermalink
While I’ve got you, let’s do some magic. It would be nice if the sidebar element had a border on the top when it’s stacked on small viewports and a left border when it is on larger viewports. We don’t want to use a media query for that because they’re too rigid and we’ll miss the breaking point of the sidebar. Let me show you a handy way of handling that.
- Code language
- css
.post__sidebar { container-type: inline-size; }
The first thing to do is use the same approach as earlier but make our sidebar element a container too. This is why we had to use an extra <div>
element because the sidebar’s child — post__meta
— can respond to the state changes of the sidebar layout.
We start by applying a border to the left of the sidebar element first:
- Code language
- css
.post__meta { border-inline-start: 1px solid; height: 100%; padding: var(--space-m) var(--gutter); }
This is only relevant if the sidebar is inline with the content though, so let’s get a container query going.
- Code language
- css
@container (width > 50cqi) { .post__meta { border-inline-start: 0; border-block-start: 1px solid; padding-inline: 0; } }
The logic here is if the post__sidebar
’s width is greater than 50% of its parent — the post
element, which is also a container — it’s stacked and now at the bottom of the post
, so we remove the left border and instead apply a border to the top. This approach means there’ll be no weird states where the sidebar layout has stacked but the post__meta
element still has a left border on it.
Wrapping uppermalink
Container queries and units are super useful for this sort of thing. They’re much more useful for stuff other than — y’know — card elements. Before container capability arrived with CSS, I would hack this sort of thing with a full bleed treatment instead, but that is no longer needed.
If the browser doesn’t support container units, the border will still be on the left when the sidebar is stacked, but the breakout element will just be the width of the content column. That feels like a good minimum viable experience which we target by building with a progressive enhancement mindset.