Spacing The Bottom of Modules

Published by Chris Coyier

Ah the humble module! A good many designs these days make use of modules in the content-y and app-y sites alike. A chunk of information, an advertisement, a grouped set of functionality... could be anything. The fact that they likely have visual similarity yet can contain anything leads to an interesting CSS challenge: how do you pad the inside consistently?

Here's a simple example where the module is set apart from the background through color differences:

html {
  background: #333;
}
.module {
  width: 20rem;
  padding: 1.5rem;
  margin: 1.5rem auto;
  background: white;
}

Easy cheesy. Nothing to it. Evenly padded.

But as soon as that module contains other elements, the padding can get "off".

<div class="module">
  <h3>Module</h3>
  <p>Pellentesque habitant morbi tristique senectus...</p>
</div>

The paragraph will very likely have some global typography rules applied to it which very likely have some bottom margin. Even if you go with opt-in typography, you'd probably opt-in here since there is type present.

Paragraph in a module.

See the heavy bottom padding? Perhaps that's OK to you, but I'd suspect it's not. That heaviness comes from the consistent set of padding you've set on the module itself plus the bottom margin on the paragraph.

Red = bottom paragraph margin

So what do we do here? A wet band-aid would be to remove margins on those paragraphs.

.module p {
  margin: 0;
}

But I'm sure we agree that's pretty gross. What if a module contains two paragraphs? Now they butt up against each other awkwardly.

Perhaps just the last one?

.module p:last-child {
  margin: 0;
}

That might work. But this fix is still feeling a little hacky. What if the problem element isn't a paragraph at all someday? It could be an <ul> or <ol>. Or <dl>. Or any other HTML element... Are we going to list every single HTML element out?

.module p:last-child,
.module ul:last-child,
.module ol:last-child,
.module dl:last-child {
  /* losing battle */
  margin: 0;
}

That's a losing battle for sure.

But we could match any element...

.module *:last-child {
  margin: 0;
}

You could even omit the "*" there since it's implied and has no specificity, but I think it makes it more clear. I think that's closer to a good fix, but it's a little heavy. Lists always have child elements that this would effect. Anything with decendants would be a problem. To fix that, we'll just ensure only the top level element under the module gets this treatment with the child selector.

.module > *:last-child {
  margin: 0;
}

That's decent. I still wouldn't call it perfect since descendants might actually be the cause of the final extra bottom padding. Like if the list items had paragraphs in them. You might want to chain them up a few times to match up to a reasonable level of DOM depth.

.module > *:last-child,
.module > *:last-child > *:last-child,
.module > *:last-child > *:last-child > *:last-child {
  margin: 0;
}

That might just do it.

Here's a visual demo:

See the Pen JFynD by Chris Coyier (@chriscoyier) on CodePen


A totally different approach might be to leave the bottom padding off the module and let the child elements provide it, but that feels a bit dangerous to me as there is no guarantee the child element would come with its own (e.g. some generic <div>).

Have you run into this? Do you have your own solution you like?