23

I would like to create a Sass mixin for the box-shadow property but am running into some trouble. Some of the existing code looks like this.

#someDiv {
  -moz-box-shadow:0 0 5px rgba(0,0,0,.25);
}

#someOtherDiv {
  -moz-box-shadow:0 0 5px rgba(0,0,0,.25) inset;
}

#theLastDiv {
  -moz-box-shadow: 0 0 5px rgba(0,0,0,.25), 0 1px 0 rgba(255,255,255,.2) inset;
}

Trying to roll all of this into 1 mixin is becoming problematic. The documentation for using logic within mixins is pretty sparse.

I would like to create some mixin along the lines of:

@mixin boxShadow($xOffSet, $yOffSet, $blur, $red, $green, $blue, $opacity, $inset : false) {
  @if $inset == true {
    -moz-box-shadow: #{$xOffSet}px #{$yOffSet}px #{$blur}px rgba($red,$green,$blue) inset;
  } @else {
    -moz-box-shadow: #{$xOffSet}px #{$yOffSet}px #{$blur}px rgba($red,$green,$blue);
  }
}

This is throwing errors because I guess Sass can't evaluate the $inset variable.

The previous example only demonstrates the problem I am having when it comes to to box-shadow being inset or not. The other problem I am having is when there are multiple box-shadows being declared on a single element. Refer to #theLastDiv described above if for reference.

@mixin boxShadow($declarations : 2, $xOffSet1, $yOffSet1, $blur1, $red1, $green1, $blue1, $opacity1 $xOffSet2, $yOffSet2, $blur2, $red2, $green2, $blue2, $opacity2) {
  @if $declarations == 1 {
    -moz-box-shadow: #{$xOffSet}px #{$yOffSet}px #{$blur}px rgba($red,$green,$blue);
  } @else if $declarations == 2 {
    -moz-box-shadow: #{$xOffSet1}px #{$yOffSet1}px #{$blur1}px rgba($red1,$green1,$blue1), #{$xOffSet2}px #{$yOffSet2}px #{$blur2}px rgba($red2,$green2,$blue2);
  }

Sometimes an element has one box-shadow and other times it has to separate box-shadows. Am I mistaken that Sass lacks the ability to decipher this logic and that to accomplish this would require separate Mixins (one for elements with one box shadow, another for mixins with two box shadows).

And to complicate the matter, how does the opacity problem described above factor into this? Would love to get some feedback on this. Let me know if I'm mistaken or the way I'm thinking about the problem is flawed in general. Thanks!

Kevin Burke
  • 61,194
  • 76
  • 188
  • 305
Steve Klein
  • 231
  • 1
  • 2
  • 3

6 Answers6

25

You can use a variable argument like this:

// Box shadows
@mixin box-shadow($shadow...) {
  -webkit-box-shadow: $shadow;
     -moz-box-shadow: $shadow;       
          box-shadow: $shadow;
}

That allows you to have commas in the argument passed. so you can pass all the shadows you want.

An example using it:

@include box-shadow(2px 2px 2px rgba(#ccc, .8), -2px -2px 2px rgba(#ccc, 0.8)) ;

Pass in color variables like this:

$shadow-color: red;  // could also be a #F9F8F6 type color
@include box-shadow(0 2px 2px rgba($shadow-color, .9));

UPDATE

$shadow-color: red;  // could also be a hex (like #F9F8F6) type color
@include box-shadow(0 2px 2px rgba($shadow-color, .9));

In case you haven't seen arguments with the elipsis allowing a variable number of arguments before (ie splats) check this link: http://sass-lang.com/documentation/file.SASS_CHANGELOG.html#variable_keyword_arguments

Will
  • 4,498
  • 2
  • 38
  • 65
  • How do you pass variables in using this method? @include(1px 1px 1px $backgroundColor) doesn't seem to work? – Robert Feb 22 '13 at 02:38
  • Actually looking at what you have put there, you have missed the mixin name. – Will Feb 22 '13 at 03:24
  • Sorry - @include box-shadow(0 0 3px $yellow); is returning a css definition that has string "$yellow" for the color, rather than the value that's in $yellow; – Robert Mar 02 '13 at 04:53
  • How are you defining $yellow? this gives me the shiny sun coloured border: $yellow : #f2e00f; @include box-shadow( 0 0px 20px $yellow); : ) – Will Mar 04 '13 at 00:53
  • Have to admit, I didn't understand the @mixin definition at the very first sight because of the ellipsis '…'. You don't need anything else, apart from valid CSS values for box-shadow properties. Will upvote after clarification. – Volker E. Aug 06 '14 at 20:23
  • 1
    the elipsis makes it a splat argument - splats allow for a variable number of arguments. It's a ruby concept that has spread to coffeescript & sass. @VolkerE. - see http://sass-lang.com/documentation/file.SASS_CHANGELOG.html#variable_keyword_arguments – Will Aug 07 '14 at 03:54
19

I prefer to keep basic CSS parameter without commas: 0 0 1px rgba(0, 0, 0, .5) rather than with commas: 0, 0, 5, 0, 0, 0, .25.

This is my solution:

@mixin box-shadow($shadow1, $shadow2:false, $shadow3:false, $shadow4:false, $shadow5:false) {
 $params: $shadow1;
  @if $shadow2 
    { $params: $shadow1, $shadow2; }
    @if $shadow3 != false
      { $params: $shadow1, $shadow2, $shadow3; }
      @if $shadow4 != false
        { $params: $shadow1, $shadow2, $shadow3, $shadow4; }
        @if $shadow5 != false
          { $params: $shadow1, $shadow2, $shadow3, $shadow4, $shadow5; }

  -webkit-box-shadow: $params;
     -moz-box-shadow: $params;
          box-shadow: $params;

}

@include box-shadow(-1px -1px 2px rgba(0, 0, 0, .05), 0 1px 1px white inset), ...
Volker E.
  • 5,911
  • 11
  • 47
  • 64
raymondralibi
  • 1,933
  • 1
  • 17
  • 25
  • Much better than 17 parameters. Thanks! – Ben Hull Sep 02 '11 at 02:24
  • Though with this solution the number of shadows you can have are limited to the number of parameters (in this case 5 shadows). [Will's answer](http://stackoverflow.com/a/13043659/573634) is a lot cleaner and doesn't have a limit on the amount of shadows. – Hanna Mar 08 '16 at 17:34
6

You could use collections and only have one parameter:

@mixin box-shadow($params) {
  -webkit-box-shadow: $params;
  -moz-box-shadow: $params;
  box-shadow: $params;
}

$shadows: 0px 0px 5px rgba(0, 0, 0, 0.25), 0px 1px 0px #000 inset;

.myclass { 
   @include box-shadow($shadows);
}
Jason
  • 51,583
  • 38
  • 133
  • 185
4

I've added a bit of logic to handle the cases you described. Here is the mixin:

@mixin boxShadow($xOffSet, $yOffSet, $blur, $red, $green, $blue, $opacity, $inset : false, $two : false, $xOffSet2 : 0, $yOffSet2 : 0, $blur2 : 0, $red2 : 0, $green2 : 0, $blue2 : 0, $opacity2 : 0, $inset2 : false) {
  @if $inset {
    @if $two {
        @if $inset2 {
            -moz-box-shadow: #{$xOffSet}px #{$yOffSet}px #{$blur}px rgba($red,$green,$blue, $opacity) inset, #{$xOffSet2}px #{$yOffSet2}px #{$blur2}px rgba($red2,$green2,$blue2, $opacity2) inset;
        } @else {
            -moz-box-shadow: #{$xOffSet}px #{$yOffSet}px #{$blur}px rgba($red,$green,$blue, $opacity) inset, #{$xOffSet2}px #{$yOffSet2}px #{$blur2}px rgba($red2,$green2,$blue2, $opacity2);
        }
    } @else {
        -moz-box-shadow: #{$xOffSet}px #{$yOffSet}px #{$blur}px rgba($red,$green,$blue, $opacity) inset;
    }
  } @else {
    @if $two {
        @if $inset2 {
            -moz-box-shadow: #{$xOffSet}px #{$yOffSet}px #{$blur}px rgba($red,$green,$blue, $opacity), #{$xOffSet2}px #{$yOffSet2}px #{$blur2}px rgba($red2,$green2,$blue2, $opacity2) inset;
        } @else {
            -moz-box-shadow: #{$xOffSet}px #{$yOffSet}px #{$blur}px rgba($red,$green,$blue, $opacity), #{$xOffSet2}px #{$yOffSet2}px #{$blur2}px rgba($red2,$green2,$blue2, $opacity2);
        }
    } @else {
        -moz-box-shadow: #{$xOffSet}px #{$yOffSet}px #{$blur}px rgba($red,$green,$blue, $opacity);
    }
  }
}

The mixin takes 17 parameters. Sorry for having so many, but SASS does not handle arrays or objects. In any event, 10 are optional. They are:

  1. $xOffSet - the x offset of the first shadow
  2. $yOffSet - the y offset of the second shadow
  3. $blur - the blur of the first shadow
  4. $red - the red value of the first shadow
  5. $blue - the blue value of the first shadow
  6. $green - the green value of the first shadow
  7. $opacity - the opacity of the first shadow
  8. $inset (Optional) - True or False. Indicates if the first shadow should be inset (defaults to false)
  9. $two (Optional) - True or False - True if you want to define two borders (defaults to false)
  10. $xOffSet2 (Optional) - the x offset of the second shadow
  11. $yOffSet2 (Optional) - the y offset of the second shadow
  12. $blur2 (Optional) - the blur of the second shadow
  13. $red2 (Optional)- the red value of the second shadow
  14. $blue2 (Optional) - the blue value of the second shadow
  15. $green2 (Optional) - the green value of the second shadow
  16. $opacity2 (Optional) - the opacity of the second shadow
  17. $inset2 (Optional) - True or False. Indicates if the second shadow should be inset (defaults to false)

You could set your styles like this:

#someDiv {
    @include boxShadow(0, 0, 5, 0, 0, 0, .25);
}
#someOtherDiv {
    @include boxShadow(0, 0, 5, 0, 0, 0, .25, true);
}

#theLastDiv {
    @include boxShadow(0, 0, 5, 0, 0, 0, .25, false, true, 0, 1, 0, 255, 255, 255, .2, true);
}

Which generates the following CSS:

/* line 9, ../src/screen.scss */
#someDiv {
  -moz-box-shadow: 0px 0px 5px rgba(0, 0, 0, 0.25);
}

/* line 12, ../src/screen.scss */
#someOtherDiv {
  -moz-box-shadow: 0px 0px 5px rgba(0, 0, 0, 0.25) inset;
}

/* line 16, ../src/screen.scss */
#theLastDiv {
  -moz-box-shadow: 0px 0px 5px rgba(0, 0, 0, 0.25), 0px 1px 0px rgba(255, 255, 255, 0.2) inset;
}
Adam
  • 12,236
  • 9
  • 39
  • 44
  • Yeah. A little bit after I posted the question I constructed something pretty much the same thing. It's really unfortunate that there's no better way to construct things like this in SASS. It was be interesting if SASS added some concatenation type functionality to it. Maybe that's asking too much. – Steve Klein Mar 25 '11 at 04:41
1

I was facing same issue when themifying sass (ref: How to properly generate Sass Themes).

I figured it out by using variable for box-shadow and then using that variable in code:

$btn-shadow-light: rgba(15, 15, 15, 0.1) 0px 0px 0px 1px, rgba(15, 15, 15, 0.1) 0px 2px 4px;
$btn-shadow-dark: rgba(15, 15, 15, 0.2) 0px 0px 0px 1px, rgba(15, 15, 15, 0.2) 0px 2px 4px;
$themes: (
  light: (
    color: rgba(55, 53, 47, 0.6),
    fill: rgba(55, 53, 47, 0.6),
    background: rgba(55, 53, 47, 0.08),
    floating-btn-shadow: $btn-shadow-light,
  ),
  dark: (
    color: rgba(255, 255, 255, 0.6),
    fill: rgba(255, 255, 255, 0.6),
    background: rgb(71, 76, 80),
    floating-btn-shadow: $btn-shadow-dark,
  ),
);
GorvGoyl
  • 42,508
  • 29
  • 229
  • 225
0

Compass code might be worth studying:

However, they seem to use some server side helpers as well.

@mixin box-shadow(
  $shadow-1 : default,
  $shadow-2 : false,
  $shadow-3 : false,
  $shadow-4 : false,
  $shadow-5 : false,
  $shadow-6 : false,
  $shadow-7 : false,
  $shadow-8 : false,
  $shadow-9 : false,
  $shadow-10: false
) {
  @if $shadow-1 == default {
    $shadow-1 : -compass-space-list(compact(if($default-box-shadow-inset, inset, false), $default-box-shadow-h-offset, $default-box-shadow-v-offset, $default-box-shadow-blur, $default-box-shadow-spread, $default-box-shadow-color));
  }
  $shadow : compact($shadow-1, $shadow-2, $shadow-3, $shadow-4, $shadow-5, $shadow-6, $shadow-7, $shadow-8, $shadow-9, $shadow-10);
  @include experimental(box-shadow, $shadow,
    -moz, -webkit, not -o, not -ms, not -khtml, official
  );
}
hakunin
  • 4,041
  • 6
  • 40
  • 57