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>
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;
}
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;
}
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.
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:
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>
This also happens to solve our margin requirements:
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.