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:

  1. Write </div><div class="container"> open/close container markup in your WYSIWYG. God help you if you choose this route.
  2. 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.
  3. 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…

Borat saying: *:not()