'sass merge selectors on ampersand

assuming I have an existing SASS rule like:

[dir] .foo {
   background-image: ...;
   // ... some more
}

And I want to add specific behaviors for ltr / rtl like:

[dir] .foo {
   background-image: ...;
   [dir='ltr'] & {
     padding-right: ...;
   }
   [dir='rtl'] & {
     padding-left: ...;
   }
}

this would generate undesired css like:

[dir='rtl'] [dir] .foo {
    padding-left: ...;
}

This will not match what I want.

Assuming I cannot change the parent selectors (due to specificity), is there any way I can write such nested selectors in a way that compiles to just [dir='rtl'] .foo {...} for the nested elements?

Some resources about the ampersand: https://css-tricks.com/the-sass-ampersand/#aa-qualifying-based-on-context



Solution 1:[1]

There is no way that I know to merge selectors as requested.

As you're not allowed to change the parent selector, the only solution I see would be to use the @at-root rule.

@at-root

The @at-root rule is usually written @at-root { ... } and causes everything within it to be emitted at the root of the document instead of using the normal nesting. It's most often used when doing advanced nesting with the SassScript parent selector and selector functions.

Definition on sass-lang.


Here is an example:

[dir] .foo {
    $root: '.foo';

    background-image: linear-gradient(black, white);

    @at-root {
        [dir=ltr] #{$root} {
            padding-right: 1em;
        }

        [dir=rtl] #{$root} {
            padding-left: 1em;
        }
    }
}

This will compile to:

[dir] .foo {
  background-image: linear-gradient(black, white);
}
[dir=ltr] .foo {
  padding-right: 1em;
}

[dir=rtl] .foo {
  padding-right: 1em;
}

You could create a mixin to help you with that:

@mixin dir($dir: ltr, $selector: &) {
    @at-root {
        [dir=#{$dir}] #{$selector} {
            @content;  
        }
    }
}

[dir] .foo {
    $root: '.foo';
    background-image: linear-gradient(black, white);

    @include dir(ltr, $root) {
        padding-right: 1em;
    }
   
    @include dir(rtl, $root) {
        padding-right: 1em;
    }
}

Food for thougt

If you don't have to support internet explorer, you might want to check padding-inline-end and padding-inline-start properties.

They will free you from the need to have different rules for different directions.

padding-inline-end

The padding-inline-end CSS property defines the logical inline end padding of an element, which maps to a physical padding depending on the element's writing mode, directionality, and text orientation.

MDN Docs - padding-inline-end

padding-inline-start

The padding-inline-start CSS property defines the logical inline start padding of an element, which maps to a physical padding depending on the element's writing mode, directionality, and text orientation.

MDN Docs - padding-inline-start

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1