SASS CSS: Target Parent Class from Child
CssSassCss Problem Overview
I am using SASS and found an inconvenience. This is an example of what I am trying to do:
.message-error {
background-color: red;
p& {
background-color: yellow
}
}
Expected CSS:
.message-error {
background-color: red;
}
p.message-error {
background-color: yellow ;
}
The idea: all elements with .message-error will be red, except if it is p.message-error. This is not real-life situation, just to show an example.
SASS is not able to compile this, I even tried string concatenation. Is there some plugin that will do exactly the same?
NOTE: I know I can put another CSS definition like:
p.message-error{....}
...under, but I would like to avoid that and use one place for all .message-error definitions.
Thanks.
Css Solutions
Solution 1 - Css
As of Sass 3.4, this is now supported. The syntax looks like this:
.message-error {
background-color: red;
@at-root p#{&} {
background-color: yellow
}
}
Note the @at-root
directive and the interpolation syntax on the ampersand. Failure to include the @at-root
directive will result in a selector like .message-error p.message-error
rather than p.message-error
.
Solution 2 - Css
You can assign the current selector to a variable and then use it at any depth:
.Parent {
$p: &;
&-Child {
#{$p}:focus & {
border: 1px solid red;
}
#{$p}--disabled & {
background-color: grey;
}
}
}
Solution 3 - Css
Natalie Weizenbaum (the lead designer and developer of Sass) says it will never be supported:
> Currently, &
is syntactically the same as an element selector, so it
> can't appear alongside one. I think this helps clarify where it can be
> used; for example, foo&bar
would never be a valid selector (or would
> perhaps be equivalent to foo& bar
or foo &bar
). I don't think this use
> case is strong enough to warrant changing that.
Source: #282 – Element.parent selector
To my knowledge, there is no possible workaround.
Solution 4 - Css
The best thing to do would be probably this (assuming you have a little more in your .message-error class than just background color.
.message-error {
background-color: red;
}
p.message-error {
@extend .message-error;
background-color: yellow
}
This approach doesn't offer that close grouping, but you can just keep them close to each other.
Solution 5 - Css
I had the same problem so I made a mixin for that.
@mixin tag($tag) {
$ampersand: & + '';
$selectors: simple-selectors(str-replace($ampersand, ' ', ''));
$main-selector: nth($selectors, -1);
$previous-selectors: str-replace($ampersand, $main-selector, '');
@at-root {
#{$previous-selectors}#{$tag}#{$main-selector} {
@content;
}
}
}
To make it work, you will need a string replacement function as well (from Hugo Giraudel):
@function str-replace($string, $search, $replace: '') {
$index: str-index($string, $search);
@if $index {
@return str-slice($string, 1, $index - 1) + $replace + str-replace(str-slice($string, $index + str-length($search)), $search, $replace);
}
@return $string;
}
How it works:
SCSS
.foo {
color: blue;
@include tag(p) {
color: red;
}
}
Output
.foo {
color: blue;
}
p.foo {
color: red;
}
> Use case
This method works with nested selectors but not whit compound ones.
Solution 6 - Css
@Zeljko It is no possible to do what you want via SASS.
See Nex3 comment: https://github.com/nex3/sass/issues/286#issuecomment-7496412
The key is the space before the '&':
.message-error {
background-color: red;
p & {
background-color: yellow
}
}
instead of:
.message-error {
background-color: red;
p& {
background-color: yellow
}
}
Solution 7 - Css
I think if you want to keep them grouped by parent selector, you might need to add a common parent:
body {
& .message-error {background-color: red;}
& p.message-error {background-color: yellow}
}
Of course, body
could be replaced with some other common parent, such as #Content
or another div
name that will contain all the error messages.
UPDATE (Another Idea)
If you leverage @for and lists then it seems like this should work (what I don't know for sure is if the list will allow the .
(period).
@for $i from 1 to 3 {
nth(. p. ul., #{$i})message-error {
background-color: nth(red yellow cyan, #{$i}));
}
}
Should compile to something like:
.message-error {
background-color: red;}
p.message-error {
background-color: yellow;}
ul.message-error {
background-color: cyan;}
Solution 8 - Css
I made a mixin that solves this problem.
Github: https://github.com/imkremen/sass-parent-append
Example: https://codepen.io/imkremen/pen/RMVBvq
Usage (scss):
.ancestor {
display: inline-flex;
.grandparent {
padding: 32px;
background-color: lightgreen;
.parent {
padding: 32px;
background-color: blue;
.elem {
padding: 16px;
background-color: white;
@include parent-append(":focus", 3) {
box-shadow: inset 0 0 0 8px aqua;
}
@include parent-append(":hover") {
background-color: fuchsia;
}
@include parent-append("p", 0, true) {
background-color: green;
}
}
}
}
}
Result (css):
.ancestor {
display: inline-flex;
}
.ancestor .grandparent {
padding: 32px;
background-color: lightgreen;
}
.ancestor .grandparent .parent {
padding: 32px;
background-color: blue;
}
.ancestor .grandparent .parent .elem {
padding: 16px;
background-color: white;
}
.ancestor:focus .grandparent .parent .elem {
box-shadow: inset 0 0 0 8px aqua;
}
.ancestor .grandparent .parent:hover .elem {
background-color: fuchsia;
}
.ancestor .grandparent .parent p.elem {
background-color: green;
}
Solution 9 - Css
I created package/mixin with a similar solution :) (Maybe it will help U)
https://github.com/Darex1991/BEM-parent-selector
so writing:
.calendar-container--theme-second-2 {
.calendar-reservation {
@include BEM-parent-selector('&__checkout-wrapper:not(&--modifier):before') {
content: 'abc';
}
}
}
This mixin will add selector only for the last parent:
.calendar-container--theme-second-2 .calendar-reservation__checkout-wrapper:not(.calendar-reservation--modifier):before {
content: 'abc';
}
More info on the repo.
Solution 10 - Css
I have ran into this before as well. Bootstrap 3 handles this using a parent selector hack. I've tweaked it slightly for my own purposes...
@mixin message-error() {
$class: '.message-error';
#{$class} {
background-color: red;
}
p#{$class} {
background-color: yellow;
}
}
@include message-error();
wheresrhys uses a similar approach above, but with some sass errors. The code above allows you to manage it as one block and collapse it in your editor. Nesting the variable also makes it local so you can reuse $class for all instances where you need to apply this hack. See below for a working sample...
Solution 11 - Css
This cheat might work
{
$and: .message-error;
#{$and} {
background-color: red;
}
p#{$and} {
background-color: yellow
}
}
You may even be able to use $&
as your variable name but I'm not 100% sure it won't throw an error.
And SASS has inbuilt scoping, which removes having to worry about the value of $and
leaking out to the rest of your stylesheet
> Variables are only available within the level of nested selectors > where they’re defined. If they’re defined outside of any nested > selectors, they’re available everywhere.
Solution 12 - Css
In the Current Release: Selective Steve (3.4.14) this is now possible, just need to update a little bit your code:
.message-error {
background-color: red;
p &{
background-color: yellow
}
}
this only works if you are one level nested, for instance it does not work if you have something like this:
.messages{
.message-error {
background-color: red;
p &{
background-color: yellow
}
}
}