PatientsLikeMe Tech Blog

We’re a team of Ruby on Rails developers and UX practitioners at PatientsLikeMe, where patients share data about their treatments, symptoms, and disease outcomes. We’re classically trained ninjas, pirates, rockstars, and dinosaur hunters. Our keyboards are magic wands — with lots of buttons, so they’re even better than regular magic wands. Meet the Team ↓

Deconstructing My Favorite Sass Mixin

I originally set out to write a post about my favorite Sass mixins, but I realized I covered many of them in a previous post here. Instead I’m just going to review three of the CSS3-based mixins I frequently use and then go into deeper explanation of a mixin I just did for one of our analytics products.

First, if you’re not familiar with Sass or the complete awesomeness of Sass mixins, I recommend you start with the Introduction to Compass screencast. Wait, what’s Compass now? Compass is essentially pre-packaged Sass. Just check out the screencast. If you’ve been writing your own CSS by hand, you’ll be blown away. I promise.

Rounded Corners

So, let’s review those three mixins. Rounded corners are all the rage and Webkit and Mozilla are happy to make our lives easier by supporting border-radius. Internet Explorer is another story, but hey… there’s always Version 9! Here’s the mixin I use for rounding corners:
=border_radius(!radius)
  -moz-border-radius = "#{!radius}"
  -webkit-border-radius = "#{!radius}"
  border-radius = "#{!radius}"
Then, if you want to round a box, you just add:
.box
  +border_radius(5px)

That will generate:

.box {
  -moz-border-radius: 5px;
  -webkit-border-radius: 5px;
  border-radius: 5px;
}

How about variable corners? Easy as this:

.box
  +border_radius(5px 10px 2px 25px)

Not only does the latest versions of Safari and Chrome support the border-radius shorthand—they even support plain old border-radius (without the browser extension!). We’ll keep the -webkit-border-radius in there for older versions, though.

Drop Shadows

Who doesn’t love a drop shadow? Well, a nice subtle one anyway. Every default setting on a drop shadow I’ve ever seen made me cringe. It’s the type of drop shadow you see on a yard sale sign. But a subtle, soft shadow? Very nice. Here’s how we do it:

=box_shadow(!color, !values)
  -webkit-box-shadow = "#{!color} #{!values}"
  -moz-box-shadow = "#{!color} #{!values}"
  box-shadow = "#{!color} #{!values}"

Applying that to a .box will give you:

.box {
  -webkit-box-shadow: #CCC 1px 1px 4px;
  -moz-box-shadow: #CCC 1px 1px 4px;
  box-shadow: #CCC 1px 1px 4px;
}

What I love about these is not having to add all the vendor extensions every time I want to round a corner or drop a shadow. One and done.

Gradients, Heck yeah

What I love about this next one is how many images I can get rid of by replacing them with CSS gradients. Not only that, but through some IE-specific vendor extensions, you can actually get it to work in IE! Gradients are often a far more important design element than some corner rounding and shadowing, so that’s a big win right there. Here’s the guts:

=box_gradient(!top,!bottom)
  background-image = "-moz-linear-gradient(top, #{!top}, #{!bottom})"
  background-image = "-webkit-gradient(linear,left top,left bottom,color-stop(0, #{!top}),color-stop(1, #{!bottom}))"
  -ms-filter = "\"progid:DXImageTransform.Microsoft.gradient(startColorStr=’#{!top}’, EndColorStr=’#{!bottom}’)\""
  *filter = "progid:DXImageTransform.Microsoft.gradient(startColorStr=’#{!top}’, EndColorStr=’#{!bottom}’)"

That will render as:

.box {
  background-image: -moz-linear-gradient(top, white, #fcf5de);
  background-image: -webkit-gradient(linear,left top,left bottom,color-stop(0, white),color-stop(1, #fcf5de));
  -ms-filter: "progid:DXImageTransform.Microsoft.gradient(startColorStr=’white’, EndColorStr=’#fcf5de’)";
  *filter: progid:DXImageTransform.Microsoft.gradient(startColorStr=’white’, EndColorStr=’#fcf5de’);
}

The -ms-filter covers IE8 and newer while just plain ol’ filter has IE6 and IE7 covered. I saw some problems with them conflicting with each other in IE8, so I just added the * to filter so that IE8 and newer (and all other browsers) would ignore it. It looks a bit messy at first, but it’s a damn nice system that’s been working well for me.

I love those three mixins. I use them all the time. I bring them over to any new project I start (which seems to be a lot lately). I used them a lot while designing and building PatientsLikeMeInsights™, but Insights called for something a bit more… well, awesome.

Super Ultra Wicked Mega Dropdown

It all started with one dropdown. But as features were added to Insights, I added more dropdowns. Here’s what they look like:

Modify Categories Dropdown

Oh, but there’s more! We also had a bunch of plain ol’ form select boxes to choose which data you wanted displayed. But, our requirements made them difficult to keep using. Well, mostly it was IE’s shortcomings that made us need to seek alternate solutions. So, I refactored the dropdowns to work as select boxes, too. Like so:

Select Box Dropdown

When I first built these select boxes, I simply applied the regular dropdown styles, then overrode them with an additional set of styles. But I recently turned both into a mixin. You can pass a true/false argument into the mixin that dictates whether or not the dropdown is a select box. The resulting mixin is relatively epic, and here it is:

=lickable_dropdown(!select_box=false)
  +inline_blocks
  position: relative
  z-index: 3
  h3.toggle
    +inline_blocks
    +border_radius(3px)
    background-color: #FCF5DE
    color: #06c
    cursor: pointer
    border: 1px solid #E4C37C
    font-weight: bold
    *margin-right: 2px
    @if !select_box == false
      padding: 4px 10px
    @else
      width: 188px
      padding: 2px 6px
    span
      +inline_blocks
      margin-left: 2px
      width: 8px
      height: 6px
      background: url(/images/dropdown-arrows.png) no-repeat 0 0
      vertical-align: middle
      *margin-top: 4px
      * html &
        margin-top: 0
        background: url(/images/dropdown-arrows.gif) no-repeat 0 6px

  a.toggle
    color: #06c
    cursor: pointer

  .dropdown-container
    position: absolute
    top: 100%
    left: 0
    z-index: 0
    padding: 0
    margin-right: 0
    @if !select_box == false
      margin-top: -1px
    @else
      margin-top: 0
      width: 202px

    .dropdown-inner
      position: relative
      background-color: #FCF5DE
      border: 1px solid #E4C37C
      @if !select_box == false
        +border_radius(0 3px 3px 3px)
        +box_shadow(#CCC, 1px 1px 4px)
        +box_gradient(#FFFFFF,#FCF5DE)
        padding: 10px
      @else
        +border_radius(0 0 3px 3px)
        padding: 6px
        border-width: 0 1px 1px 1px
        margin-top: -1px

  &.active
    z-index: 5
    h3.toggle
      background-color: #FFF
      +border_radius(3px 3px 0 0)
      border-bottom: 0
      z-index: 2
      position: relative
      *padding-bottom: 5px
      span
        background-position: 0 -61px
        * html &
          background-position: 0 -56px

The markup for one of these puppies resembles this:

<div class="dropdown" id="modify">
  <h3 class="toggle">
    Modify Categories
    <span>&nbsp;</span>
  </h3>
  <div class="dropdown-container">
    <div class="dropdown-inner">
      … content goes here …
      <input class="update-table" type="submit" value="Update">
      <a class="toggle">Cancel</a>
    </div>
  </div>
</div>

So, let’s run through this really quickly, since I apparently love to use code samples:

=lickable_dropdown(!select_box=false)
  +inline_blocks
  position: relative
  z-index: 3

Just apply +lickable_dropdown(true) or +lickable_dropdown(false) to that container div (div#modify). These three lines will inline-block it, relatively position it (so the popup contents can be positioned relative to the container), and give it a z-index (this page is a z-index free-for-all!). What’s +inline_blocks? It’s a sweet mixin (that I didn’t write) that makes inline-block work in IE6.

  h3.toggle
    +inline_blocks
    +border_radius(3px)
    background-color: #FCF5DE
    color: #06c
    cursor: pointer
    border: 1px solid #E4C37C
    font-weight: bold
    *margin-right: 2px
    @if !select_box == false
      padding: 4px 10px
    @else
      width: 188px
      padding: 2px 6px
    span
      +inline_blocks
      margin-left: 2px
      width: 8px
      height: 6px
      background: url(/images/dropdown-arrows.png) no-repeat 0 0
      vertical-align: middle
      *margin-top: 4px
      * html &
        margin-top: 0
        background: url(/images/dropdown-arrows.gif) no-repeat 0 6px

All this for the toggle that triggers the popup! I have it as an h3, since it’s essentially a header for the dropdown contents. It can really be whatever you want. We have stuff you’d expect like borders, colors, cursors (to make it look like a typical link), and font weights. We’ve also got some inline_blocks and border_radius mixins going.

But here, we see our first if/then statement. If the dropdown isn’t a select_box, just give it some padding. If it is, give it different padding and also hard-code a width.

The stuff applied to the span is just to get that little arrow next to the h3 text.

  a.toggle
    color: #06c
    cursor: pointer

This is pretty simple. Just the “Cancel” or “Close” link at the bottom of the boxes. The “toggle” class (like with the h3) triggers the Javascript to show-hide the next div we’re going to cover.

  .dropdown-container
    position: absolute
    top: 100%
    left: 0
    z-index: 0
    @if !select_box == false
      margin-top: -1px
    @else
      width: 202px

This div is the one that we show and hide when we click the .toggle. It is absolutely positioned all the way to the left and at 100% from the top. Basically, this means the top of this div will be aligned with the bottom of the container div. We’ve also got another if/then. In this case, if it’s a select_box, it gets another hard-coded width. If not, we’re giving it a -1px top margin. Why? The select_box’s toggle and content are the same width. The standard popup’s toggle is more tab-like on the top. This way, we can give it that tab effect by overlapping the tab over the content by one pixel.

This might be best illustrated with a song illustration:

Toggle Overlap

And finally:

    .dropdown-inner
      position: relative
      background-color: #FCF5DE
      border: 1px solid #E4C37C
      @if !select_box == false
        +border_radius(0 3px 3px 3px)
        +box_shadow(#CCC, 1px 1px 4px)
        +box_gradient(#FFFFFF,#FCF5DE)
        padding: 10px
      @else
        +border_radius(0 0 3px 3px)
        border-top-width: 0
        padding: 6px

This is where we see the three CSS3 mixins above. First, we’re going to relatively position this (so there can be some absolutely positioned content inside, if needed. Then we’ve got a border and background color. Now for some more if/then fun.

One last thing… when you activate a dropdown, the container element is given a class of .active. When a dropdown is active, the z-index is adjusted and the toggle gets some different styles:

  &.active
    z-index: 5
    h3.toggle
      +border_radius(3px 3px 0 0)
      border-bottom: 0
      z-index: 2
      position: relative
      @if !select_box == false
        *padding-bottom: 5px
        background-color: #FFF
      span
        background-position: 0 -61px
        * html &
          background-position: 0 -56px

First, on the toggle, I just rounded the top two corners, gave it a z-index that would cover the content by a pixel (to achieve the tab effect shown above), and axed the bottom border. Then, if it’s a standard dropdown, I changed the background color (to feed nicely into the gradient) and gave IE an extra pixel of padding. For the arrow span, I flipped the arrow upside down.

And there you have it. I hope this step-by-step example is useful for you in some way (be it to understand what Sass can do or to get an idea of how I build things with CSS). If you have suggested improvements, I’m all ears!

4 Comments

  1. On September 10th, 2010 at 3:06 pm Chris B said:

    Fantastic stuff, Adam. I didn’t realize mixins could be so powerful.

  2. On October 1st, 2010 at 2:58 am qaov said:

    sweet :)
    more mixins are coming?

  3. On October 1st, 2010 at 7:41 am Adam Darowski said:

    Sure thing, I hope to do more soon. What part to find the most useful? Seeing the capabilities of the mixins or actually seeing the step-by-step process of putting them together? Just curious. :)

  4. On October 2nd, 2010 at 8:24 am qaov said:

    deconstruction generally is cool,
    i`ll try to recontruct this dropdown on my website :)
    it will become, more minimalistic

    only that inline_block mixin is missing ;)

    a few days earlier i`ve found mixin that uses mroe functions and for loop, for generating grid
    i`ts converted to use in new version of sass

    /* grid

    $numCols: 12
    $baseUnit: 14px
    $gridUnit: 1.28571529em
    $colWidth: $gridUnit * 3.5
    $gutterWidth: $gridUnit * 0.5

    .container_#{$numCols}
    font-size: $baseUnit
    margin-left: auto
    margin-right: auto
    width: $numCols * ($colWidth+(2*$gutterWidth))

    @for $i from 1 through $numCols
    .container_#{$numCols}
    .grid_#{$i}
    font-size: $baseUnit
    width : ($i * $colWidth) + (($i – 1)*(2*$gutterWidth))
    float: left
    display: inline
    margin:
    left: $gutterWidth
    right: $gutterWidth
    @if $i < $numCols
    .prefix_#{$i}
    padding-left: ($i * $colWidth) + ($i * 2 * $gutterWidth)
    .suffix_#{$i}
    padding-right: ($i * $colWidth) + ($i * 2 * $gutterWidth)

    .container_#{$numCols} .alpha
    margin-left: 0

    .container_#{$numCols} .omega
    margin-right: 0