Sass Selectors: To Nest Or Not To Nest?
I threw a question out on Twitter regarding CSS/Sass authoring style. It was a difference between Option 1:
.c-btn { ... }
.c-btn__icon { ... }
and Option 2:
.c-btn {
&__icon {
...
}
}
The results were interesting so I wanted to write them up. Here we go!
Option 1: Not Nested
.c-btn { ... }
.c-btn__icon { ... }
As you can see, this looks like regular ol’ CSS. Because it is! Let’s unpack the pros and cons of this approach.
Pros
- Findable/Searchable – the biggest advantage this approach has is being able to quickly find a selector in a codebase. If you encounter
class="c-btn__icon"
in your markup and need to view or edit the styles for.c-btn__icon
, you search forc-btn__icon
in your project and arrive at the styles for the selector. - Legible – Legibility of course is a relative thing, but proponents find this approach more legible than a bunch of nested selectors. As Marc points out, this approach becomes especially helpful for longer partials that require scrolling.
- Not reliant on Sass – This approach uses plain CSS conventions to structure CSS instead of leaning on a preprocessor to. Some proponents of this approach like the idea of leaning less on preprocessors and instead reaching for built in features of CSS instead.
- Keeps things flatter – With Sass it can be easy to go crazy with nesting, and this approach keeps things flat, which saves nesting for special scenarios like adding hover/focus states, parent selectors, and media queries.
Cons
- Not DRY (Don’t Repeat Yourself) – The big downside of this approach is that you have to repeat your selector for as many children or modifiers your component may have. This opens the door to more typos and having to touch multiple things if you want to rename a component.
- More verbose – This approach is not as succinct as nesting child and modifier selectors, meaning developers have to write more characters to accomplish their styling.
Option 2: Nested
Option 2 takes advantage of Sass’s parent selector feature, which uses an ampersand to prepend the parent selector to wherever the &
appears.
.c-btn {
&__icon {
...
}
}
Pros
- Keeps things DRY – The big advantage of this approach is that authors don’t have to repeat themselves. They can define the component name in exactly one place and it will prepend to all children and modifiers that are nested inside it. The Sass
&
feature is pretty neat and powerful, and this approach takes full advantage of it. - Succinct – Related to being DRY, developers don’t have to write as much, meaning they can focus on the
&__icon
part of the selector and not have to worry about what comes before it. It saves writing characters so there could be some developer speed improvements as well.
Cons
- Findability/searchability issues – When looking at a chunk of HTML, authors can search for a class name in their project and immediately be taken to the corresponding styles for that selector. Proponents of this nesting approach mentioned how using source maps in addition to using logical partials removes this barrier, but many others said they found it harder to zero in on specific selectors they encounter in HTML.
- Can lose context – This certainly isn’t an issue for shorter components like the example I shared above, some components (like cards, headers, carousels, and so on) can get pretty gnarly. With a nested approach, developers can lose context within the partial, making it harder to grok where you’re at.
- Requires multi-level nesting – In order to achieve pseudo-classes, write media queries, and do other nested selectors, you’re now looking at being 3 levels deep. This isn’t necessarily a bad thing, but it can open the door to nesting abuse.
This post by Chip Cullen discusses his experience with Option 2 and lays out similar cons.
My Personal Approach
When I threw this out there on Twitter, I was impressed by how many people weighed in and how split the results were. It’s clear there are pros to each approach, and developers can state a case for choosing one or the other.
One theme in the responses resonated with my own experience. People said something to the tune of “I used to do Option 2, but switched to Option 1.” That’s exactly how things panned out for me. I used to lean a lot harder into Sass features (lots of ampersands, nesting, variables, mixins, extends, and so on), but as the years go by I find myself pulling back and instead relying more or less on CSS’s (increasingly powerful) feature set. Partials and imports are still great, some mixins are still very helpful (for things like clumping font-size
, line-height
, and letter-spacing
together) and I haven’t fully switched to CSS variables yet, but I find myself walking back from going all-in on Sass features. As Una said, Option 2 is novel, but maybe it’s not as practical.
Time also plays a big role in this. I think it’s fascinating a lot of people talked about the perils of Option 2 after significant time has passed and they needed to revisit a codebase only to discover they couldn’t find what they’re looking for. There’s something to that, and I think that’s why I continue to think Option 1 is a bit more sturdy than Option 2.
Regarding verbosity, I find it interesting that people talked about reducing characters, but they’re still advocating BEM-style CSS, which of course is super verbose . As I’ve written elsewhere, on projects I’ve architected we’ve made decisions that legibility trumps succinctness when writing CSS, and I feel that same principle applies here. Yes it’s more characters to type, and yes you have to update multiple selectors when refactoring (my favorite is repeatedly mashing Cmd + D
until I get them all), but to me I feel those extra characters lead to a more legible and stable codebase.
I can definitely see the merits of both options, and if I joined a project that made use of Option 2 I likely wouldn’t throw a fit and insist on refactoring to Option 1. But like any stylistic or syntactic decisions your team makes, just make sure you document your frontend guidelines (and hey! I made a thing for that) and ensure all of your team members are on the same page.