On Nested CSS
Have you ever stumbled upon a code that looks like this?
.card {
/*
Some lengthy css definitions
it has
to
be
lengthy
*/
&-header {
/*
Some lengthy css definitions
it has
to
be
lengthy
*/
}
&-footer {
/*
Some lengthy css definitions
it has
to
be
lengthy
*/
&-links {
/*
Some lengthy css definitions
it has
to
be
lengthy
*/
&-link {
/*
Some lengthy css definitions
it has
to
be
lengthy
*/
}
}
}
&-body {
/*
Some lengthy css definitions
it has
to
be
lengthy
*/
}
.article {
/*
Some lengthy css definitions
it has
to
be
lengthy
*/
&-content {
/*
Some lengthy css definitions
it has
to
be
lengthy
*/
}
}
}
<div class="card">
<header class="card-header"></header>
<div class="card-body">
<article class="article">
<p class="article-content"></p>
</article>
</div>
<footer class="card-footer">
<nav class="card-footer-links">
<a href="#" class="card-footer-links-link"></a>
</nav>
</footer>
</div>
Seems harmless, right? But beyond this seemingly innocent code, hides a tiny bit of complications that could be harmful to development.
The Complexities
The example above is written in Less syntax, which is similar to SCSS; both encourage us to use the &
symbol
to represent the parent selector. Even though it might not be semantically correct, we’ve been using
the &
to write less code. Why bother writes card-header
when you can just &-header
right?
However, using parent selector as a solution to write less code leads to several tiny problems that don’t break your code, but add additional overload to you, as a developer.
Broken Search
Now, because you wrote card-header
as &-header
, there’s no way you can accurately search for card-header
anymore.
You probably have a lot of others &-header
for other class, then you’d end up with multiples of &-header
to parse
from instead of just searching for card-header
.
Some IDE might be smart enough to resolve card-header
when you do “Go to Declaration” command and resolve it for you.
It means your IDE will need to index it properly so that it knows the card-header
that you wanted to open is indexed
to card.css -> .card -> &-header
- this additional indexing; when small, will not cause issues. But an unnecessary
overhead as well, just because we dev have high-performing machine, we tend to ignore this additional indexing process.
WebStorm helps will properly go to the declaration when its used on places that WebStorm knows it’s going to be a class name. If it’s an arbitrary string, it won’t resolve, and you’d have to resort to search.
The inverse is not possible, if you want to find where does
&-header
being used, cmd+clicking on the class name nor the Find Usages will help you resolve this. You’d have to manually type the full selector to search its usage.
Broken Autocompletion
Some IDE might be able to understand the &-header
that you have can be autocompleted when you start typing card-
on
the class property of your element. Just like the search above, it requires proper indexing on the IDE to know this.
Again, WebStorm supports this, but when it’s on an arbitrary string where WebStorm doesn’t index it. Good luck with remembering the full name because search is not that helpful in this situation
Broken Reading
When you are deep into the CSS file that you are editing, and all you can see is &-links
, you will need an additional
overhead to remember what’s the current parent selector that you’re working on? Then, you need to scroll up
to know, “oh, it was the .a-very-long-class-name-already
”.
Code can automatically parse and understand what’s the &
currently refers to, but you as a human, will need to process
that first, remember it again, and then know what &
currently resolve to.
WebStorm again, supports showing a breadcrumb to know where are you currently at.
Broken Refactoring
Changing a &-header
into &-headers
might seem easy, but if your IDE doesn’t properly index it, you’d need to
ensure that you’re only changing that specific parent selector and not breaking other class that follows the same
structure.
Even WebStorm can’t refactor nested-selector at the moment.
When to Actually Use It?
Nested selector is helpful, but it’s not there to help you abbreviate the parent selector. It’s there to help you write less but in a way that doesn’t involve abbreviation.
Parent Modifiers
When you want to add hover, active, focus, etc. effects. Nested selector really helps you, and it helps you without additional overhead to understand what the parent is because it’s always going to be that one parent only.
.button {
&:hover {
}
&:focus {
}
&:disabled {
}
}
Above, you can understand that the nested selector affects the parent and not to create a new child selector. You can also use it to write BEM-like modifiers like below.
.button {
&__primary {
}
&__secondary {
}
}
The aim of the nested selector is to modify the parent, and not introduce a new children.
Scoping
You can use nested selector to have a better refined scope of what you want to change. For example, you have a reusable
class called .card
, and you have .main-card-grid
where you want to show the card as a grid with additional shadows.
.card {
}
.main-card-grid {
.card {
box-shadow: 0 2px 4px #0001;
}
}
Without nesting, you’d have to type .main-card-grid .card
, but nesting still help us write less but without
resolving to use parent selector as a way to abbreviate things.
Summary
We can utilize nested selector on things that don’t introduce additional overhead and issues that we might encounter in the future. Modifiers and scoping are a good reason to use it. Using it to abbreviate, the parent selector will introduce several problems in the future.
A good rule of thumb when working with nested selector is that if it’s semantically a different element, and it just happens to have similar class name because it’s a children of that specific class name, you’d better of on writing a separate selector for it.
.card {
.card-header{
}
}
// or to avoid nested hell
.card-header {
}
Above, card
and card-header
is an entirely different element, with different looks, and different semantics; so it
doesn’t make sense to use nested selector other than we just want to abbreviate the card
part.
You can parse it easily, you can refactor it easily, and you can search it easily.
A better alternative is to use CSS module, where you no longer need to worry about name collision.
Thoughts
What are your thoughts about nested selector? What problems that you encountered while using it so far? Now that native nested selector is here, what’s stopping us from writing pure CSS?