DEV Community

Cover image for My Favorite CSS Selector
Sebastian Nitu
Sebastian Nitu

Posted on

My Favorite CSS Selector

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.

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)

Collapse
 
tlylt profile image
Liu Yongliang

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?

Collapse
 
sebnitu profile image
Sebastian Nitu

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 😄

Collapse
 
tlylt profile image
Liu Yongliang

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 🦄