Let’s say you’re making a blog post layout. Content is entered into a CMS inside a WYSIWYG editor field. You echo that content to the page. You pull it up on a mobile device and notice the paragraphs go edge-to-edge. Yikes, it’s a little uncomfortable. So you add some kind of left/right padding maybe using a div.container
.
This works great until the client asks for the images and video to go full bleed. Your universal padding solution no longer works well. You have a few options:
- Write
</div><div class="container">
open/close container markup in your WYSIWYG. God help you if you choose this route. - Split
$post->content
into separate chunks in the CMS. If you choose this route, you write front-end and backend logic to render each content chunk (containered and container-less) accordingly. This is more to maintain. You’ve also fractured your content in the database just to achieve a visual style which is not a great separation of concerns. - No container, but explicitly add padding to each individual child element except images. If you go the this route, your CSS ends up looking like this:
.post > h1,
.post > h2,
.post > h3,
/* Repeat for every block level element in the HTML */
.post > p {
margin-left: auto;
margin-right: auto;
max-width: 50rem;
padding-left: 5%;
padding-right: 5%;
}
This works but there’s always something you didn’t consider. Some images go full-width, some don’t. All iframes or just YouTube and Vimeo? You manage both the inclusions and exclusions to the rule. A few client requests down the line and your CSS is super bloated.
Let’s try using *:not()
instead
Because I’m lazy, I started thinking about a cheaper way to write all that CSS. I found a simple and elegant solution using the CSS Level 3 :not()
selector.
.post > *:not( img ):not( video ) {
margin-left: auto;
margin-right: auto;
max-width: 50rem;
padding-left: 5%;
padding-right: 5%;
}
Now I’m only managing exceptions. I can see myself using this trick on my own blog to break out of the boring but dependable “tube of content”. This could extend to lots of elements:
.post > *:not(img):not(video):not(table):not(iframe[src*="codepen"]) { /* ... */ }
Anything I want to embiggen to break out of the container, I just add it to the list. Using :not()
feels really versatile.
Colored background tiers
This technique could be extended to give a section
or div
a different colored background (as marketing sites tend to do) AND then be recycled so that children of that div
get the same rules as the faux-container.
.post > *:not( .colored-bg ),
.colored-bg > * {
margin-left: auto;
margin-right: auto;
max-width: 50rem;
padding-left: 5%;
padding-right: 5%;
}
.colored-bg {
background: lightgray;
padding-top: 5%;
padding-bottom: 5%;
}
Demo: Blog post layout
To test this theory out, I started on a generic blog post layout that at ~70 lines of CSS is pretty close to meeting all of my layout needs.
See the Pen Universal padding using CSS :not for full bleed exceptions by Dave Rupert (@davatron5000) on CodePen.
I like where this is going
I want to be clear, this has some limitations. *
-selecting can be expensive in CSS if your page is huge. While you specify CSS selectors left-to-right, CSS evaluates selectors right-to-left. Overriding a :not()
selector is kinda hard (see blockquote
in the demo). The resize calculation might also be costly to browsers.
All that said, I think I’m going to use the hell out of this. I like that I’m not opening and closing utility containers. I like that I can be as explicit as needed. I like that it’s a relatively cheap and easy way to break away from a boring tube of content, even if just ever so slightly.
I’m on a couple projects right now where wrapper divs are really causing a ruckus and impeding the flexibility of our code. Chris Coyier and I even talked about this in a recent Shop Talk Show rapidfire. This seems like a nifty workaround for those problems.
And because we’re all thinking it…