The following is a guest post by Filip Naumovic from Salsita Software. Filip has built a Sass tool to help with an issue I know I’ve experienced many times. You’re happily nesting in Sass. You’re maybe a level or two deep, and you need to style a variation based on some parent selector. You need to either break out of the nesting and start a new nesting context, or go nuclear with @at-root
. I’ll let Filip tell the story of his new tool that changes that.
Sass has helped a lot of us tremendously with cleaning up our CSS code bases. When it arrived on the scene, some extremely useful patterns emerged and got quickly adopted.
For example, the usage of &
and nesting allowed for the cleaning up of otherwise rather gross code blocks.
This:
.my-app { display: block; }
.my-app .widget { border-radius: 5px; }
.my-app .widget.blue { color: blue; }
.isIE6 .my-app .widget { background-image: url('fake-borders.png'); }
@media (max-width: 768px) { .my-app .widget { float: left; } }
Turned into this:
.my-app {
display: block;
.widget {
border-radius: 5px;
&.blue {
color: blue;
}
.isIE6 & {
background-image: url("fake-borders.png");
}
@media (max-width: 768px) {
float: left;
}
}
}
What a positive change for our sanity!
All style variations of the .widget
element are clearly nested below itself, using indentation as both a visual cue for relevancy and a query generator.
The current selector (&
), in this case, provides a shortcut for the most common patterns. Styling the variation of an element that is invoked either by a property of the element itself or a prepending parent state.
Nested media queries are a younger addition, but they hint that evolution towards indented syntax for styles comes almost naturally. It’s easy to read and navigate, because it somehow mirrors the familiar DOM structure and keeps all styles for an element in one place, while still producing our precious, yet sometimes complicated, selectors.
Today, nesting and current selector features are present in Less, Sass, and Stylus. With a bit of wine, one could almost call it a standard.
A classic case of “You can’t do that.”
Using the above code block as an example, let’s add styles for .my-app.expanded .widget
.
Despite our mighty tools, we quickly find ourselves with limited choices:
Option 1
Using the modern @at-root
directive (or /
in Stylus), we leave the current scope entirely and repeat the full root query to keep the relevant new styles nested below .widget
, because the current selector can’t help us express this relationship.
.my-app {
display: block;
.widget {
border-radius: 5px;
&.blue {
color: blue;
}
.isIE6 & {
background-image: url("fake-borders.png");
}
// repeating the root selector here
@at-root .my-app.expanded .widget {
color: red'
}
@media (max-width: 768px) {
float: left;
}
}
}
This creates harder to read code with a lot of duplicity, especially when real world usage extends way over our small example piece. But, it keeps our glorious nesting paradigm intact.
Option 2
We create a new code block below .my-app
and use it to change all child elements relevant to the .expanded
state. This means that our .widget
is now styled in different places, and this separation grows for every added state in each element in the nest.
.my-app {
display: block;
.widget {
border-radius: 5px;
&.blue {
color: blue;
}
.isIE6 & {
background-image: url("fake-borders.png");
}
@media (max-width: 768px) {
float: left;
}
}
&.expanded .widget
color: red;
}
}
While it is in direct violation of our “nesting all relevant styles” dream, it’s the imperfection we learned to live with. Many of you would probably even defend this pattern, because it has been the way things are done for quite a while.
However, for the sake of choice, wouldn’t it be great to have an Option 3? One that would allow us to express the simple change in .my-app.expanded
that influences our .widget
without having to escape the context?
This idea has been secretly bothering me for quite a while, if only out of some form of OCD about my own stylesheets. I’ve made it my sidequest to try and find this missing tool in the style shed.
Finding Option 3
While digging around the topic, I’ve found spider webs, eternal discussions, and wildly varying propositions, many of which suggested adding some special syntax to the current selector character &
. Doing that would mean months of learning complicated core libraries and fighting the long war, which instantly felt like an unacceptable burden.
Secondly, I think &
works well because it’s a clear representation of the whole context, and for that reason it might be problematic adding more features to it. It does one thing and does it well, so creating a good partner to it seemed like a better idea at this time.
For sake of easy integration, I’ve decided to implement the idea on the level of preprocessor language, so you could just @import
and use it right away. Preprocessors are powerful frameworks nowadays, so why not?
My first choice was Stylus, because it’s just so awesome. Unfortunately, due to issue 1703, the current selector placeholder cannot be modified inside a mixin function as of right now. Like a good zealot I’ll wait until the end of time for Stylus to fix it, but I had to keep searching for something I could implement now.
You shall not parse the current selector, as I’ve learned, in Less, so that was out.
SassScript on the other hand proved to be a powerhouse. While it is missing many useful abstractions for manipulation with strings and arrays, it is very possible to craft such functions manually. Many of them are already provided by Sass Prince Kitty Giraudel.
After months of controlled string terror…
inStyle for Sass 3.4+ is born!
Cheesy name, I know. But it’s suggestive of the functionality, because you want this thing readable in the actual code. Mixin syntax is already familiar with preprocessor users, so having a suggestive name for describing changes in element parents sounded right to me as an added bumper against unfamiliarity.
Either way, all of it has to stay readable while handling complex cases, otherwise it loses purpose in favor of @at-root
selector approaches or just nesting the code elsewhere. I decided to go with two basic mechanisms that I believe address even the most despicable needs, while keeping a logically simple parsing algorithm:
Use 1) Modification
Additions to a compound element present in the current selector proved to handle ~80% of real world code, just like our first example tries to achieve.
.my-app {
display: block;
.widget {
border-radius: 5px;
&.blue {
color: blue;
}
.isIE6 & {
background-image: url("fake-borders.png");
}
@include in(".my-app.expanded") {
color: red; // .my-app.expanded .widget { };
}
@media (max-width: 768px) {
float: left;
}
}
}
Try to read that like this:
styling
.widget
in the.my-app.expanded
state.
The function searches the nest bottom to top for the first occurrence of .my-app
element (skipping current element) and appends the class .expanded
to it, returning a new selector.
What about longer queries and combo modifications?
table {
table-layout: fixed;
thead {
font-weight: normal;
tr {
height: 30px;
td {
background-color: #fafafa;
&.user {
font-weight: bold'
}
@include in('table.one tr.two:hover') {
background-image: url(rainbow.png) // table.one thead tr.two:hover td { };
}
}
}
}
}
The tr
parent is found and modified with .two:hover
. Going upwards, table
is also found and modified with .one
, other elements are skipped.
Irrelevant multi-selectors are removed from the new selector:
ul, ol {
list-style: none;
li {
display: inline-block;
a {
text-decoration: underline;
@include in("ol.links") {
color: orange; // ol.links li a { };
}
}
}
}
Impossible cases and invalid CSS queries produce a blocking Sass error on compilation:
table {
table-layout: fixed;
td {
height: 30px;
@include in("table^s&()#") {
what: the; // ERROR, invalid CSS
}
@include in ("tr.green:hover") {
border-color: green; // ERROR, no tr or tr.green to modify in &
}
}
}
While crash testing this in production (hah!), I found another very practical need that I couldn’t satisfy with modifications of the parent tree only. In fact, it solves the example above, because you have to be able to do just that with tr.green:hover
. You just have to be able to say where.
Use 2) Insertion
Let’s assume the following:
table {
table-layout: fixed;
thead {
font-weight: normal;
}
tr {
height: 30px;
}
td {
background-color: #fafafa;
}
}
Where would you ideally nest a table thead tr
selector? By the dogma, you seemingly have to add it as follows:
table {
table-layout: fixed;
thead {
font-weight: normal;
tr {
height: 50px;
}
tr {
height: 30px;
}
td {
background-color: #fafafa;
}
}
However, the styled element in question is tr
and you already have that as a generic style, so theoretically, nesting it below itself as a variant might be closer to how you think about the relationship, filling the gaps that current selector &
cannot describe.
In this case, it means there has to be a simple way to insert some selector at a certain position above the current element while also allowing combinations with compound modifications. I couldn’t imagine this without adding a special character, so I went with the visually suggestive ^
caret.
table {
table-layout: fixed;
thead {
font-weight: normal;
}
tr {
height: 30px;
@include in("^thead") {
height: 50px; // table thead tr { };
}
}
td {
background-color: #fafafa;
@include in("table.blue-skin ^tbody") {
background-color: blue; // table.blue-skin tbody td { };
}
}
}
In this case, the caret is inserting thead
one level above current or last modified element. More carets mean higher jumps in the current selector:
main {
display: flex;
> div {
flex-grow: 1;
span {
display: inline-block;
&.highlight {
outline-style: dashed;
@include in("^^.section.active") {
outline-style: solid; // main .section.active > div span.highlight { };
}
@include in("^^^.section") {
some: thing; // ERROR, inserting too high, it would equal to ".section &"
}
}
}
}
}
Note: &.highlight
is the same element as span
, so the insertion treats it as one step in the nest
I think inStyle shines in the simplest cases, which are also by far the most common. But things can get more complex if needed.
Use 3) Advanced combinations
The matching algorithm allows you to go even wilder with inserting in or modifying more compounds at once:
.my-app {
display: flex;
section {
flex: 1;
header {
width: 100%;
@include in("^^.wrapper ^.dialog)") {
height: 50px; // .my-app .wrapper section .dialog header { };
}
@include in("^.overlay ^.sidebar") {
position: fixed; // .my-app section .overlay .sidebar header { };
}
@include in("^.modifier section.next ^.parent") {
opacity: 0; // .my-app .modifier section.next .parent header { };
}
}
}
}
.dialog
is inserted one level aboveheader
and.wrapper
is inserted two levels..sidebar
is inserted aboveheader
and.overlay
directly above it.- Pushes
.parent
aboveheader
, modifiessection
with.next
and then pushes.modifier
above it.
This reminds me, perhaps you have some feedback! I’ve been thinking about enabling some simpler syntax when you want to insert more compound elements directly after each other as in the second case, perhaps something like @include in("^(.overlay .sidebar)")
or improve the parser and enable more natural @include in("^.overlay .sidebar")
. Let me know your opinion!
After using it for a while, I’ve found that most of my inconvenient code patterns are solved rather easily by just changing one element here and there or pushing a new selector at a certain position and keep things in place. Still, I need to be honest, it is potentially quite invasive to your usual code organization by nature of the idea.
I can see how using inStyle might bring on heated arguments. My colleagues seem to be either open minded or don’t care, which is both great.
If you use it, I would hope that the correct handling would be like with any other tool: when it’s fit for the job. Spamming complex nested mixins will unlikely score high on readability than flat out writing the full query, but on the other hand it can simplify most real world problems while keeping a slim footprint.
In the near future, I’d like to get the Stylus port working and perhaps create an Atom editor plugin to display the resulting query as a hint in the code.
It was fun taking a shot at solving the first-world problems of CSS and it is my hope that you consider the subject at least worthy of a discussion. The project is open source, so feel free to get onboard with either code or feedback!
Love it or hate it, here it is on GitHub, here’s a little microsite and here’s a live debugger for good measure.
See the Pen inStyle Crash Test Dummy by Salsita Software (@salsita) on CodePen.
Thanks for reading!
Off-Topic: Is it possible, to set the highlight for code lines in a way that they stay at the same line even if one changes the font size of the website? Maybe using em instead of px?
Cool hater comment.
I’d be torn between the extra dependency and the useful syntax.
Here’s the #1 thing I like about it:
Keeping things nested at the same level feels kinda cool.
Yeah, I was being a hater. Sorry. The ideas in the article are clever and well executed. I’ve just been having a rough time with overly engineered Sass nesting lately. I mean, even in the very first example in this article, the verbose CSS seemed preferable to the nested SASS. (Although I know that isn’t always the case.)
Chris, to me, your example feels even better like this:
Bone simple CSS for the win.
I would say if you overall prefer standard CSS with full queries as in your last example, this whole idea becomes somewhat meaningless for your application.
Chris, I think you’ve written the perfect counterexample. What happens when you or someone else changes the nav from an
ol
to aul
and forgets to change the selector in the string? :)Here’s a much more readable version without
@include in("^headache")
:Yes, you can still mess this up by changing one of the
li
selectors without changing the others. But – it’s a lot easier to see all of theli
selectors here than it is to see the parent selector nested deeply and inside of a string.Seriously, though, what’s this obsession with nesting? And having selectors inside of strings, yuck!
Hi Agop, I’m not sure it’s possible to address a case where someone changes the HTML and doesn’t change the CSS.
Filip, my note was addressing the case of changing the
ol
to aul
in both the HTML and the (S)CSS, but not inside of the@include in("foo")
string. That is very unintuitive for a developer not intimately familiar with the codebase. Repeating the parent selector instead of the child selector is a step backward IMO.You’re right about that requirement. I’ve thought having to repeat the parent compound would raise readability, but after Serg’s request further down in comments, I have now added a way to abstract the parent target in 1.4.0, so it doesn’t have to be repeated. The special character is also configurable and will reference any change in the parent correctly.
For the record, repeating the compound selector has some perks that you can’t get from abstracting it with
<
.You can solve multiselectors and stay quite readable as mentioned in the article:
Alright, cool, that’s much better. Now you don’t repeat anything, which is awesome.
(Still not sure I’d ever get behind that syntax, but more power to those that like it.)
This is a very cool concept. I would definitely like to apply this trick to my next project.
To those worried about HTML and CSS conflicts whilst modifying markup etc. I’d highly recommend watching this talk on Modular CSS: https://www.youtube.com/watch?v=HoQ-QEusyS0
Hey! I’ve just published a POSTCSS plugin to reference ancestor selectors in nested CSS with a very similar and configurable “^&” syntax:
Have a look at it: postcss-nested-ancestors.
Cheers!
Hi Andrea, this is great stuff! I am also looking into porting inStyle to PostCSS and you’ve given me the courage :D We seem to be using similar methods to assign the parents (counting upwards of the current element), although I’m a bit torn on the
&
usage – I’m just so used to it referencing the whole string. Either way, it’s great to see these solutions on the rise as I’m sure they have value for our codebases.What I would’ve liked, rather than another @include-operator(selector) thing, would have been an extension to Stylus’
/
using a “syntax” we all are familiar with already: The good old../
path traversal notation.That would easily allow to go up a level, two or three perhaps, so that the “nuclear” option and with it going down the whole selector path again would not be necessary any more.
(And the counter argument of “but what if I need to go up a lot more levels, then this becomes really ugly and hard to read” probably just means you’ve been letting your selectors getting too long to begin with already ;-)
Stylus already has way to target specific parents, see http://stylus-lang.com/docs/selectors.html#partial-reference. It it however somewhat underwhelming in practice and doesn’t address all the possible uses that you might need from it.
Do we really need this at all, if bem is the way to go nowadays (until css modules or shadow dom are fully usable)?
Hi, author here. I don’t believe there’s any relation to BEM, can you elaborate?
With BEM you avoid to have nesting as much as possible. The method showed in this article, instead, seems to heavily use nesting, at the point that you need a custom mixin to add flexibility to your nested selectors.
I don’t think that “Nest all the things!” is a good way to write clean CSS… BEM teaches us.
Could you specify the conflict with BEM? If you mean short extremely specific queries, you will nevertheless encounter the same cases as with any other methodology. I think InStyle is merely giving you some ways to describe these cases more conveniently if you’re using nested CSS, but doesn’t interfere with the methodology itself.
Maybe I miss something (for sure), but…
What’s the difference between
@include in(".my-app.expanded") {
and.my-app.expanded & {
?having a BEM-like CSS, I would do:
which reduces the nesting hell.
BEM code uses the current selector to store the prefix and not much else, which has both advantages and disadvantages. It’s sort of a tradeoff producing the same thing for BEM with the current inStyle – you don’t need to repeat the current element and you gain variant nesting, but you lose the
my-app
interpolation further down (which you however won’t need in BEM anyway as you want to end the query short). This would be equal to your code:´´´
.my-app
&__widget
color: red
+in(‘.my-app–expanded’)
color: blue
´´´
I can see how that could be further optimised though, perhaps there is a case for a BEM-friendly version:
Since BEM has set syntax rules, the states could be automatically prefixed. Now this would be directly less code doing the same thing and I dare to say with added readability. This isn’t what inStyle does at the moment, but it shouldn’t be a problem to add. Would you consider this an improvement?
My preference here:
Keep code searchable by not using & selector. Depending on how many other properties need to go here I might write it like this:
Or just this:
Sorry for the 3rd comment in a row, but it’s also worth noting that a dynamic state would use a separate class like this:
Hello Michael, you examples are exactly what I’m trying to solve. Your preferred syntax seems to be basically default CSS, where you are repeating a large amount of selectors without really using the advantages that nesting can offer.
My examples are poor, I apologise. I just don’t think it’s a good idea to split a classname into two parts in CSS source – this painfully impacts code searchability. On the topic of the article, this is an important problem that I encounter frequently! Great to see this experimental feature.
Hello Michael, I apologize for misunderstanding your comments at first – I see now you are questioning the way the above BEM example uses the current selector. I do agree that interpolating selector queries this way looks rather dubious. There’s probably both a nesting hell and a BEM hell in existence and both should be avoided. I think if someone is set on using BEM, there is a lot of space to abstract away the bad parts within the preprocessor framework, but it is not directly the scope of inStyle.
Thanks, the idea and plugin are nice.
I faced such problems and it would be super useful for me if
^^.wrapper
was interpreted as.my-app.wrapper
,but currently it’s
.my-app .wrapper
(I would expect such behavior from^^ .wrapper
)which doesn’t give any way to interact with parent(s) only add the mediators.
Hello Serg, thanks for the feedback! I have been considering adding shorthand syntax to target the parents without repeating their compound selector, but had decided not to implement it yet as the full selector seemed more readable in practice. At the same time, the
^
character seemed to suggest the insertion feature well, so perhaps we could find some alternative character to suggest the target compound. Numbering perhaps? I’ll open an issue on the topic on Github, so feel free to chime in! Definitely not a problem to add in one way or the other.I use the BEM syntax, like a couple of the other commenters have mentioned. One of the rules I set for myself is “Only declare a module part once.” But you’re right, when you’re working on a particular state for the module, it’s not very clear how to handle the Sass organization. I’ve often used both methods 1 & 2.
I think that this plugin, when used responsibly, will give me a way to keep my module part state-specific styles right next to the module’s original styles. I like that a lot.
I love the idea and an execution.
Is it usefull? To me it is. I’ve been looking for solution like this for months now.
Is it essential? Hell no, but it is really nice to have it in my toolbelt.
Of course it can be tricky and overused can make a lot of messy code. But the same can be told about a lot of common preprocessors features. Take a basic SASS @extend for example. I’ve seen a lot of terrible usages but it is there, it is useful and I do not think it is a problem.
As with all tools it should be used carefully and only when it helps.
Man, that lookd awesome! I was actually struggling with exactly that on a past project. I tried different approaches with mixins but never got it right. I went fot approach 2 (@at-root just looks to messy for me). I will definitely bookmark your plugin and use on my next css heavy project.
People say it can lead to messy code. Sure it can, but then people can abuse any kind of future, not less so in Sass. I worked on a project once where people just went bananas using @extend (I only use it when working with placeholder and that is very occasionally). I can tell that I couldn’t even read those files because of the level of complexity created by the number of extend rules.
This is really appreciated. Thanks for sharing!
How about this?
I think your mixin is basically equivalent of
Useful but not as much as the plugin described in the post.