question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Extending within the @media directive

See original GitHub issue

Extending within the @media directive

As this topic seems to pop-up quite often I decided to see if I could make it work without having to bother nex3 and all you other champs more than I’ve already done.

Here is what I came up with: Please comment 😃

Best, Jakob E

Note! mediaExtend causes deprecation warning mediaExtend Back-to-Hack-Edition added below

Mixins and config

// ====================================================================
// Breakpoints
// Define a global list of breakpoints
// (key, min-width, max-width),...
// Note! zero will omit the query
// ==================================================================== 
$breakpoints: 
     (all,0,0)
    ,(desktop,769,0)
    ,(tablet,481,768)
    ,(mobile,0,480);


// ====================================================================
// Media
// Prints out a media query from the breakpoints list 
// ==================================================================== 
@mixin media($key){ // @content
    $bp:null;
    @for $i from 1 through length($breakpoints){
        $item:nth($breakpoints,$i);
        @if(nth($item,1)==$key){ $bp:$item; }
    }
    $min:nth($bp,2) * 1px; 
    $max:nth($bp,3) * 1px;
    $mq:'screen and (min-width:'+$min+') and (max-width:'+$max+')'; 
    @if($min and $max==0px){ $mq:'screen and (min-width:'+$min+')'; }
    @if($min==0px and $max){ $mq:'screen and (max-width:'+$max+')'; }
    @if($min==0px and $max==0px){ $mq:'all'; }
    @media #{unquote($mq)}{ @content; }
}


// ==================================================================== 
// MediaExtend
// Places each extend within the each media directive of the breakpoint list
// ==================================================================== 
@mixin mediaExtend(){ // @content
    @for $i from 1 through length($breakpoints){
        $key:nth(nth($breakpoints,$i),1);
        @include media($key){ @content; };
    }
}


How to use


// ==================================================================== 
// Create the media extends.
// ... to test the output just add a regular extend
// @include mediaExtend(){ .test{ content:'test'}}
// 
@include mediaExtend(){
    %red { color:red; }
    %blue { color:blue; }
    %green { color:green; }
    %yellow { color:yellow; }
}


// ==================================================================== 
// Using media as wrapper 
//
@include media(all){
  .foo { @extend %red; }
  .bar { @extend %blue; }
}
@include media(mobile){
  .foo { @extend %blue; }
  .bar { @extend %yellow; }
}
@include media(tablet){
  .foo { @extend %green; }
  .bar { @extend %green; }
}

// CSS Output
// @media all {
//   .foo { color: red; }
//   .bar { color: blue; }
// }
// @media screen and (min-width: 481px) and (max-width: 768px) {
//   .foo, .bar { color: green; }
// }
// @media screen and (max-width: 480px) {
//   .foo { color: blue; }
//   .bar { color: yellow; }
// }


// ==================================================================== 
// Wrapping media
// 
.foo { @include media(tablet){ @extend %green } }
.bar { @include media(tablet){ @extend %green } }

// CSS Output
@media screen and (min-width: 481px) and (max-width: 768px) {
  .foo, .bar {  color: green; }
}


// --------------------------
// Note! when passing non-media-extend @content to the media mixin
//  - CSS Output order is set by the include order  not the order 
// of the breakpoints list... Be careful if your list contains
// overlapping breakpoints
// 
.foo { @include media(tablet){ content:'tablet'; } }
.foo { @include media(mobile){ content:'mobile'; } }
.foo { @include media(all)   { content:'all';    } }

// CSS Output
// @media screen and (min-width: 481px) and (max-width: 768px) {
//   .foo { content: 'tablet'; }
// }
// @media screen and (max-width: 480px) {
//   .foo { content: 'mobile'; }
// }
// @media all {
//   .foo { content: 'all';  }
// }


mediaExtend Back-to-Hack-Edition

While the mediaExtend solution above was nice and clean and made perfect sense it will cause a deprecation warning (crashed CodeKit) - sucks big time!!!

It looks as if we are still allowed to extend within the media directive as long as there are no naming conflicts with other extends. This solution uses a media prefix to create a sort of namespace. In no way as flexible, intuitive or as simple as the first version.

Note! the breakpoints list and media mixin remains the same


// ==================================================================== 
// mediaExtend  
// ==================================================================== 
// Prevent multiple instantiations
$media-extend-first-run:true !default;
@mixin mediaExtend($args...){ 

    // Arguments passed => extend
    @if(length($args)>0){
        // Loop thru arguments to see if any matches a key in the breakpoints list 
        // - if found add to keys list
        $keys:();
        @for $i from 1 through length($args){
            @for $j from 1 through length($breakpoints){
                @if(nth(nth($breakpoints,$j),1)==nth($args,$i)){ 
                    $keys:append($keys,nth($args,$i)); 
                }
            }
        }
        // If no breakpoints were passed we'll extend all items in the breakpoints list. 
        // Note! if extending within context scope must match
        // @media all {
        //     @include mediaExtend(all, foo){ .... }
        // }
        $keys:if($keys==(),$breakpoints,$keys);
        @for $i from 1 through length($args){
            @for $j from 1 through length($keys){
                $key:nth(nth($keys,$j),1);
                @extend %#{$key+'_'+ nth($args,$i)} 
            }
        }  
    }

    // No arguments => create extends 
    @else if $media-extend-first-run {
        $media-extend-first-run:false;
        // Loop thru breakpoints
        @for $i from 1 through length($breakpoints){
            $key:nth(nth($breakpoints,$i),1);
            // 1 Place extends within a media scope
            // 2 Prefix extends to avoid conflicts
            @include media($key){
                // ==================================
                // Add your all your extends here 
                // %#{$key+_+ EXTEND_NAME }{ ... }
                // ==================================       

                %#{$key+_+ foo }{ content:'foo'; }
                %#{$key+_+ bar }{ content:'bar'; }
                %#{$key+_+ doh }{ content:'doh'; }

            }
        }
    } 
}
@include mediaExtend(); // Create extends


// ==================================================================== 
// Usage 
// Instad of using @extend %foo we use the mediaExtend to handle the prefixing.
// ==================================================================== 


// -----------------------------------------------------------
// Example 1
// Calling without a scope (extend it all)
.myClass{ @include mediaExtend(foo); }

// CSS Output 
@media all { .myClass { content: 'foo'; } }
@media screen and (min-width: 769px) { .myClass { content: 'foo'; } }
@media screen and (min-width: 481px) and (max-width: 768px) { .myClass { content: 'foo'; } }
@media screen and (max-width: 480px) { .myClass { content: 'foo'; } }


// -----------------------------------------------------------
// Example 2 
// Passing one scope 
.myClass{ @include mediaExtend(mobile, foo); }

// CSS Output
@media screen and (max-width: 480px) { .myClass { content: 'foo'; } }


// -----------------------------------------------------------
// Example 3 
// Passing multiple scopes
.myClass{ @include mediaExtend(mobile, tablet, foo); }

// CSS Output
@media screen and (min-width: 481px) and (max-width: 768px) { .myClass { content: 'foo'; } }
@media screen and (max-width: 480px) { .myClass { content: 'foo'; } }


// -----------------------------------------------------------
// Example 4
// Passing multiple scopes and extends 
.myClass{ @include mediaExtend(mobile, tablet, foo, bar); }

// CSS Output
@media screen and (min-width: 481px) and (max-width: 768px) { 
    .myClass { content: 'foo'; }
    .myClass { content: 'bar'; } 
}
@media screen and (max-width: 480px) { 
    .myClass { content: 'foo'; }
    .myClass { content: 'bar'; } 
}


// -----------------------------------------------------------
// Example 5 
// Extending within context
@media all {
    .myClass{ @include mediaExtend(all, foo); }
}

// CSS Output 
@media all { .myClass { content: 'foo'; } }


// -----------------------------------------------------------
// Example 6
// Extending within context - using media mixin
@include media(all) {
    .myClass{ @include mediaExtend(all, foo); }
}

// CSS Output 
@media all { .myClass { content: 'foo'; } }


// -----------------------------------------------------------
// Example 7 - Error
// Extending within wrong context
@media all {
    .myClass{ @include mediaExtend(mobile, foo); }
}

// No CSS Output


Constants

Just a small might-be-of-some-use note - use constants


$MEDIA_ALL:all;
$MEDIA_DESKTOP:desktop;
$MEDIA_TABLET:tablet;
$MEDIA_MOBILE:mobile;

$breakpoints: 
     ($MEDIA_ALL,0,0)
    ,($MEDIA_DESKTOP,769,0)
    ,($MEDIA_TABLET,481,768)
    ,($MEDIA_MOBILE,0,480);

@include media($MEDIA_ALL){ .... }
@include mediaExtend($MEDIA_ALL){ ... }

Issue Analytics

  • State:closed
  • Created 10 years ago
  • Comments:10 (1 by maintainers)

github_iconTop GitHub Comments

1reaction
lunelsoncommented, Jun 2, 2016

@entozoon actually it does work; I wrote that gist before Sass introduced the !global flag. This is required in order to modify a global variable from inside of a mixin — for example the mq ()mixin in that gist. Here’s the updated code

http://www.sassmeister.com/gist/cd999df6a2712b1959d2b68d4505602b

1reaction
adamjgrantcommented, May 29, 2014

I’m encountering this same issue with @extend and @media.

The solutions so far seem overthought. A less messy solution would be to create a new operator, @copy that just copies whatever you point it to. You can still use extend with the same class, however.

%style1
  display: block

%style2
  display: inline-block

x
  @extend %style1

y
  @extend %style1

p
  @extend %style1
  @media screen and (min-width: 320px)
    @copy %style2

This knows to simply copy in the styles. Nothing fancy.

x, y, p { display: block }
@media screen and (min-width: 320px) {
  p { display: inline-block }
}
Read more comments on GitHub >

github_iconTop Results From Across the Web

Extending selectors from within media queries with Sass
The simple answer is: you can't because Sass can't (or won't) compose the selector for it. You can't be inside of a media...
Read more >
Extending within the @media directive - CodePen
Extending within the @media directive. Click (SCSS) in the CSS pane to see the CSS Output /** * Using media as wrapper */...
Read more >
SCSS - Extending Classes Within Media Queries
What we would have to do is to put the code from the class we want to extend inside of a @mixin instead...
Read more >
Cross-Media Query @extend Directives in Sass - SitePoint
Sass doesn't allow you to @extend a placeholder inside a media query from another scope. Here is a complex, but easy to use...
Read more >
Allow @extend across media queries · Issue #1050 · sass/sass
Edit: The current plan here is to allow @extend across media queries by duplicating the queries the current @extend is in and unifying...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found