DEV Community

Cover image for The case for * { margin: 20px }
Gajus Kuizinas
Gajus Kuizinas

Posted on

The case for * { margin: 20px }

Suppose this layout:

<ul class='posts'>
  <li class='post'>
    <h1>
      Title of the article
    </h1>
    <div class='sub-heading'>
      <ul class='tags'>
        <li>foo</li>
        <li>bar</li>
        <li>baz</li>
      <ul>
    </div>
    <div class='body'>
      Body
    </div>
  </li>
</ul>

Enter fullscreen mode Exit fullscreen mode

If I were to ask you to add consistent spacing between all elements, your first approach would likely be:

.post,
.post h1,
.post .sub-heading,
.post .body {
  margin: 20px;
}

Enter fullscreen mode Exit fullscreen mode

This is not ideal because now every element needs to be aware of its parent's spacing requirements.

There is a better way.

Using universal descendent selector to add consistent spacing

Perhaps one of the first things I get called out when onboarding new frontend developers is about my use of a variation of the following code snippet:

& > * {
  margin: 20px;
}

Enter fullscreen mode Exit fullscreen mode

A variation (usually & > * { margin: var(--gap-size); }) of this snippet is present in every UI I've built in the last half a decade.

I place this style in any kind of a "container" element, e.g. "panel". Similar to Figma's auto-layout, it enforces consistent spacing between layout elements.

Applaudience forms

The beauty of the above approach is that none of the individual elements need to be aware of their margin requirements. In the above example, individual inputs, buttons and rows inherit margin from their parent.

Adjusting space between individual elements

Suppose we want the sub-heading to be closer to the heading:

Alt Text

Because of how CSS specificity works, each element can easily override margin settings, e.g. in this example .posts and .post children inherit default margin, but .sub-heading overrides this setting.

Alternatively, as mentioned in one of the comments, a unidirectional margin can be used to achieve the same result without using negative margins. However, the downside of this approach is that only the element that follows can control margin relative to the preceding element (or the element that leads, if we inverse margin direction).

Either way, if you run into such a requirement, then I would encourage you to consider if your markup is semantically logical. The goal of the * { margin: 20px } style is to enforce consistent spacing between members of a container. If a particular element requires different spacing, then it is likely that this element should be a member of a different container. In our case, I would suggest to group heading and sub-heading elements into a header group.

<li class='post'>
  <div class='header'>
    <h1>
      Title of the article
    </h1>
    <div class='sub-heading'>
      <ul class='tags'>
        <li>foo</li>
        <li>bar</li>
        <li>baz</li>
      <ul>
    </div>
  </div>
  <div class='body'>
    Body
  </div>
</li>

Enter fullscreen mode Exit fullscreen mode

This also happens to solve our margin requirements:

Alt Text

As the last word of caution, I am not advocating specifically for * { margin: 20px }. I am suggesting to enforce consistent spacing between elements using a universal descendent selector, regardless of what that spacing would be. In the last example, it was * { margin: 0 0 10px 0; } because that's the margin we want to enforce among all children of that container. And as illustrated in the last example, the by-product of using this rule is that it highlights mistakes in the layout organisation.

Top comments (11)

Collapse
 
aleksandrhovhannisyan profile image
Aleksandr Hovhannisyan

You bring this up as an example of what one would be inclined to do as a first pass:

.post,
.post h1,
.post .sub-heading,
.post .body {
  margin: 20px;
}

.post .sub-heading {
  margin: -10px 20px 20px 20px;
}

This is a bad design to begin with. And I'd argue that your solution isn't much better:

& > * {
  margin: 20px;
}

You'll inevitably run into the problem of collapsing margins with both top and bottom set to 20px.

Better solution: Use unidirectional margins, consistently applying a margin to either the top or bottom of all elements (but not both). I prefer bottom margins. This means my spacing flows consistently from the top down.

Collapse
 
gajus profile image
Gajus Kuizinas

Use negative margin as is illustrated in one of the examples.

Collapse
 
aleksandrhovhannisyan profile image
Aleksandr Hovhannisyan

Why introduce unnecessary complexity, though? You could just as well do 0 20px 20px 0 and have unidirectional flow, or margin-bottom: 20px, or only margin-top: 20px. You're creating problems for yourself that could be avoided altogether.

Thread Thread
 
gajus profile image
Gajus Kuizinas

Because it is not a real world scenario. The whole point of this pattern is to enforce a consistent spacing between members of a container. If one element requires different spacing than others, then your hierarchy of elements is off. I will update article to make this more clear.

Collapse
 
bholmesdev profile image
Ben Holmes • Edited

Well, you might need a wrapper "div" or "section" for styling purposes... I'd hate to override a margin property I dreamt up every single time.

The inclusion of horizontal margins bothers me as well. What about form groups and labels? These would probably need overrides too...

Idk. Might be worth a try on personal projects, but it would make me pretty frustrated working on a team (especially if I'm new to the codebase) 🤷‍♀️

Collapse
 
polarbirke profile image
Søren Birkemeyer

I'm a big fan of a variant of what you describe here called "the lobotomized owl" which was introduced in 2014 on A List Apart: alistapart.com/article/axiomatic-c.... The author has since iterated on his original idea and evolved it into the "stack" as described on every-layout.dev/layouts/stack/. I highly recommend Every Layout, it's a treasure trove of clever ideas like your approach!

Collapse
 
gajus profile image
Gajus Kuizinas

Wow, this is beautifully written. Instantly bookmarked for future conversations on the subject.

Collapse
 
steveblue profile image
Stephen Belovarich • Edited

While there may be some uniformity among parts of a design system, I don’t believe this approach is sustainable. What if the designer wants a more hierarchical margin throughout the design? Do you then override everything that requires the new margin? This approach adds unnecessary complexity. I would rather see margins explicitly zeroed out and set on specific elements that require a different margin than 0. That pattern makes for a more stable implementation of a design system.

Collapse
 
gajus profile image
Gajus Kuizinas

What if the designer wants a more hierarchical margin throughout the design? Do you then override everything that requires the new margin.

I can respond to that if you share a real-world example. A requirement that adds inconsistent spacing between elements should be seriously questioned. However, as mentioned at the end of the article, it is likely that what you are missing is another container to logically group elements.

Collapse
 
yyyyaaa profile image
Ha Gia Phat

If you don't want to pollute the global CSS namespace then you can use a pattern called "the Stack". It does the same thing but in the right way. Example implementation in React:

codesandbox.io/s/serverless-sky-2f...

Collapse
 
invot profile image
invot

I think this makes for a great thought experiment. I'd do this myself, but limit it to just the div tag.