Search code examples
sasscompass-sass

Sass multi-state navigation bar issue


I have a navigation bar, where the buttons have a hover state but also need to be "active" on their respective page. Since this navigation bar is part of an include, I decided to do the active state via a class on the body tag, as in .dashboard .dashboard-button. So I have the hover and this.

I was trying to figure out the most streamlined way to do this with Sass/Compass. I created a couple of mixins:

@mixin nav-button-on($icon) {
    background-color: #2f3684; // Old browsers
    @include filter-gradient($offwhite, #E5E5E5, vertical); // IE6-9
    @include background-image(url('../images/'+$icon+'-on.png'),linear-gradient(top, $offwhite 0%, #E5E5E5 100%));
    border-bottom: $borderbottom solid #F31A35;
    a { color: $red;}
}
@mixin nav-button($icon){
    background-color: #fff; // Old browsers
    @include filter-gradient(#fff, #fff, vertical); // IE6-9
    @include background-image(url('../images/'+$icon+'.png'),linear-gradient(top, #fff 0%, #fff 100%));
}

And then in the LI where the buttons are defined, I have

 li {
 &.dashboard { 
            @include nav-button('dashboard'); 
        }
        .dashboard &.dashboard { 
            @include nav-button-on('dashboard'); 
            &:hover {
                 @include nav-button-on('dashboard'); 
            }
        }
}

HTML:

<body class="dashboard">
<div id="nav">
    <ul>
    <li class="first dashboard"><a href="/index.php">Dashboard</a></li>
    <li class="challenges"><a href="/challenges.php">Challenges &amp; Teams</a></li>
    <li class="goals"><a href="/goals.php">Goals</a></li>
    <li class="activity"><a href="/my-activity.php">My Activity</a></li>
    <li class="destinations"><a href="/destinations.php">Destinations</a></li>
    <li class="fitness last"><a href="/fitness-resources.php">Fitness Resources</a></li>
</ul>
</div>

This seems a bit convoluted, I was wondering if anyone had any advice to streamline this at all.

NOTE: I had to add the white-to-white gradient, since when hovering over a solid-color background with the gradient hover state caused a flash.


Solution

  • There are a number of things to improve here:

    1. Are you really dealing with that hierarchy of dashboard classes? For example, you're currently compiling to:

      li.dashboard {
          ...
      }
      li .dashboard li.dashboard {
          ...
      }
      

      This seems wrong or at least poorly structured. Perhaps you could simplify things here.

    2. Assuming you need this for each of your nav <li> elements, DRY it up with an iterated mixin:

      li {
          @each $item in dashboard, challenges, goals, activity, destinations, fitness {
              &.#{$item} { 
                  @include nav-button($item); 
              }
              .#{$item} &.#{$item} { 
                  @include nav-button-on($item); 
                  &:hover {
                      @include nav-button-on($item); 
                  }
              }
          }
      }
      
    3. But #2 is not actually the best way. Use placeholders rather than mixins for this kind of stuff, or combine the two. I'd do something like this:

          %nav-button {
              background-color: #fff; // Old browsers
              @include filter-gradient(#fff, #fff, vertical); // IE6-9
          }
      
          %nav-button-on {
              background-color: #2f3684; // Old browsers
              @include filter-gradient($offwhite, #E5E5E5, vertical); // IE6-9
              border-bottom: $borderbottom solid #F31A35;
              a { color: $red;}
          }
      
          @mixin li-image($icon) {
              @include background-image(url('../images/'+$icon+'.png'),linear-gradient(top, #fff 0%, #fff 100%));
          }
      
          @mixin li-image-on($icon) {
              @include background-image(url('../images/'+$icon+'-on.png'),linear-gradient(top, $offwhite 0%, #E5E5E5 100%));
          }
      
          @each $item in dashboard, challenges, goals, activity, destinations, fitness {
              body.#{$item} li, li.#{$item}:hover {
                  @extend %nav-button-on;
                  @include li-image-on($item);
              }
              li.#{$item} {
                  @extend %nav-button;
                  @include li-image($item);
              }
          }
      

    Compare the outputs and maintainability of these and I think you'll find this quite a bit more streamlined!