During my time as a front-end developer, I've been adopting patterns that help me author CSS in a way that's easier to maintain, expand and not having to constantly override default styles.
I can't stress this enough: DON'T put default margins on things. Trust me, it's going to bite you in the ass. If something needs margins, it should be handled by the parent component or via utilities. Margins assume a specific context you just can't predict.01:21 AM - 21 Jul 2020
Margins are unique in CSS because they assume a specific context. For example, if I give every <p>
element in my site a top and bottom margin of 1em
, I'm going to need to reset those margins inside my components almost every time. Otherwise, I'll have to write my components in a way that assumes all <p>
elements are going to have margins.
Components should only care about themselves, not their context. That job is for the parent component. So if I have a sidebar with a set of children widgets, the sidebars job is to add proper spacing between widgets, not the widgets to have default margins for when their context is a sidebar.
Being a good parent
The best CSS selector is all about being a good parent and not delegating the responsibility of the parent to the children. For that, we use this selector:
> * + * {
// ...
}
This selector targets the adjacent siblings of all direct children of an element. Here's a CodePen example so you can understand which elements are being targeted as well as their specificity:
There are two main reasons why I love this selector:
- It doesn't add to the elements specificity. A simple
.class
selector is equal in specificity weight to.parent > * + *
. This is absolutely HUGE if you're using a low specificity CSS authoring pattern such as BEM. - It allows us to handle the spacing of our children without having to make the children worry about that stuff. Now THAT's being a good parent!
In the wild
There are two places I use this selector in my projects. The first is a generic .type
component that handles the styles for a block of HTML elements such as a blog post. Usually this type of content is maintained inside a markdown file or a WYSIWYG editor, so you don't have control over the HTML output. But with these styles, you'd pretty much be good to go:
.type > * + * {
margin-top: 2em;
}
I also have a spacing
utility that I include in most of my projects. It's output is built using this Sass code:
$spacing-map: (
"xs": 0.25em,
"sm": 0.5em,
"md": 1em,
"lg": 1.5em,
"xl": 2em
) !default;
.spacing > * + * {
margin-top: map-get($spacing-map, "md") !important;
}
@each $key, $value in $spacing-map {
.spacing-#{$key} > * + * {
margin-top: $value !important;
}
}
Which gives me a tool for adjusting the spacing between a components child elements without having to create a new modifier for every component that needs it.
Thanks for reading!
Top comments (3)
For “adjacent siblings of all direct children of the element”, it does not include the first child?
In the first example, the first child element is green but I thought you mean > * + * will make all of the children background of salmon. Am I mistaken?
Yes,
> * + *
skips the first child. If you wanted to select ALL children, you'd just need> *
. The reason I think> * + *
is so great tho is the example of adding margins that create essentially a gutter between elements without the unwanted space above or below your root component.The added bonus of doing it this way without the a
:not()
or:first-child
is you don't add to the specificity so it's easier to override with a single class if needed.Let me know if that makes sense. I'm not the best at writing about this stuff 😄
Oh okay, I did some googling. So
* is all elements
> is direct children
x + y means y is the adjacent sibling of x, at the same level
So, > * + * I assume it means:
take the sibling of every children, which just so happens that the first element will not be selected.
And this has the effect of adding constant margins between the elements, without needing to fix the top or the bottom element. I think we can use CSS grid to achieve the same effect? Also, I wonder if there is a set of simple selectors that ignores the last child and hence we can apply margin bottom to achieve the same effect...
Cool, something new 🦄