sassscss-mixins

Sass mixin that applies CSS hacks


I'm trying to create mixin that applies CSS hacks for different browsers:

@mixin browsers($browsers) {
    $selectors: (
        chrome: '&:not(*:root)',
        firefox: '@-moz-document url-prefix()',
        ie: '@media screen and (-ms-high-contrast: active), (-ms-high-contrast: none)'
    );

    @each $browser in $browsers {
        #{map-get($selectors, $browser)} {
            @content;
        }
    }
}

For example:

#test {
    @include browsers(firefox ie) {
        background: red;
    }
}

Expected compilation output is:

@-moz-document url-prefix() {
    #test {
        background: red;
    }
}
@media screen and (-ms-high-contrast: active), (-ms-high-contrast: none) {
    #test {
        background: red;
    }
}

but it fails with:

Error: expected selector.

@-moz-document url-prefix(){
^

Of course I can use if/else statements inside @each like:

@if $browser == chrome {
    &:not(*:root) {
        @content;
    }
} @else if $browser == firefox {
    @-moz-document url-prefix() {
        @content;
    }
} @else if $browser == ie {
    @media screen and (-ms-high-contrast: active), (-ms-high-contrast: none) {
        @content;
    }
}

but is it possible to do that more... elegant?


Solution

  • After three and a half years, I've finally found a workaround and opened an issue in GitLab 🥳.

    https://github.com/sass/sass/issues/3636

    @mixin only-ie {
      @media screen and (-ms-high-contrast: active), (-ms-high-contrast: none) {
        @content;
      }
    }
    
    @mixin only-chrome {
      &:not(*:root) {
        @content;
      }
    }
    
    @mixin only-firefox {
      @-moz-document url-prefix() {
        @content;
      }
    }
    
    @mixin only-for-browsers($browsers) {
      @each $browser in $browsers {
        @if $browser == ie {
          @include only-ie {
            @content;
          }
        }
    
        @else if $browser == chrome {
          @include only-chrome {
            @content;
          }
        }
    
        @else if $browser == firefox {
          @include only-firefox {
            @content;
          }
        }
      }
    }
    
    // Usage:
    #test {
      @include only-for-browsers(ie firefox) {
        background: blue;
      }
      color: red;
    }
    

    Update

    It turned out to be much simpler, you just need to use @#{} instead of #{} in at-rules cases.

    @mixin only-for-browsers($browsers) {
      $selectors: (
        chrome: '&:not(*:root)',
        firefox: '@-moz-document url-prefix()',
        ie: '@media screen and (-ms-high-contrast: active), (-ms-high-contrast: none)'
      );
    
      @each $browser in $browsers {
        $selector: map-get($selectors, $browser);
        @if str-slice($selector, 1, 1) == "@" {
          @#{str-slice($selector, 2)} {
            @content;
          }
        } @else {
          #{$selector} {
            @content;
          }
        }
      }
    }
    
    #test {
      @include only-for-browsers(chrome firefox) {
        color: #e25922;
      }
      @include only-for-browsers(ie) {
        color: #1ebbee;
      }
    }