Grow your CSS skills. Land your dream job.

Transformer Tabs

Published by Chris Coyier

Tabs are a simple design pattern in which a row of links are obviously clickable navigation and when a link is clicked, new content is shown. There are loads of variations of course, but it's one of the most ubiquitous navigation design patterns out there. When arranged in a horizontal row, it is also one of the least small-screen-friendly design patterns out there.

We can make it work though.

Without doing anything at all to help tabs on a small screen, you'll run into some kind of problem

  • You let the page be "zoomed out" and the tabs are tiny tap targets
  • You let the page be device-width and...
    • there isn't room for the tabs so they cut off.
    • the tabs wrap and look weird and take up too much space.

Basically: we need to do something here to make tabs better for small screens.

This Has Been Done Before

Popular solutions include:

I'd like to do it again with some specific goals.

Goals

Here's the plan:

  • Normal "tabs" look when there is room, dropdown when there isn't.
  • "Current" tab always shown and obvious.
  • Works with #hash tabs where all the content is on the page and content panels are hidden/shown.
  • Works with linked tabs where the tabs link to a different URL.
  • The HTML is semantic.
  • There is one version of the HTML that doesn't change.
  • There is one version of the JavaScript that doesn't change.
  • You can link to a particular tab.

So we're not just tackling the design but the functionality.

Patterns like the "off canvas" style won't work here as we're trying to show the current tab, not hide it. Converting in a <select> dropdown won't work because that's different HTML and different JavaScript.

The HTML

Tabs are navigation, so <nav>. The role should be implied by the tag, but you can't always count on that, thus we add the role. We use a class for the CSS on the outermost element (the <nav>). The navigation items themselves are in a list because that's best. Each link has either a #hash target or a valid URL.

<nav role='navigation' class="transformer-tabs">
    <ul>
      <li><a href="#tab-1">Important Tab</a></li>
      <li><a href="#tab-2" class="active">Smurfvision</a></li>
      <li><a href="#tab-3">Monster Truck Rally</a></li>
      <li><a href="http://google.com">Go To Google &rarr;</a></li>
    </ul>
</nav>

Nothing superfluous.

The Tabbed View CSS

There is a set of specific CSS for the tabs in each "state" (tabs or dropdown). You can start "mobile first" by styling the dropdown then using min-width media query to re-arrange to look tabbed or you can start "desktop first" by styling the tabs first then using a max-width media query to re-arrange into a dropdown. They are so different I don't see any big advantage either way, but I'd match what you are doing elsewhere on the site.

Desktop first and SCSS for this demo.

.transformer-tabs {
  ul {
    list-style: none;
    padding: 0;
    margin: 0;
    border-bottom: 3px solid white;
  }
  li {
    display: inline-block;
    padding: 0;
    vertical-align: bottom;
  }
  a {
    display: inline-block;
    color: white;
    text-decoration: none;
    padding: 0.5rem;
    &.active {
      border-bottom: 3px solid black;
      position: relative;
      bottom: -3px;
    }
  }
}

Just a row of links with a line beneath it. The anchor link with an "active" class gets a different color border that overlaps the edge-to-edge border. vertical-align keeps them all on the same baseline when the active link gets the border the others don't have.

The Dropdown View CSS

We need to find a viewport width in which the tabbed look breaks down and put a media query there. 700px for this demo.

.transformer-tabs {
  ...
  @media (max-width: 700px) {
    ul {
      border-bottom: 0;
      overflow: hidden;
      position: relative;
      background: linear-gradient(#666, #222);
      &::after {
        content: "☰"; /* "Three Line Menu Navicon" shows up */
        position: absolute;
        top: 8px;
        right: 15px;
        z-index: 2;
        pointer-events: none;
      }
    }
    li {
      display: block; /* One link per "row" */
    }
    a {
      position: absolute; /* Stack links on top of each other */
      top: 0;
      left: 0;
      width: 100%;
      height: 100%;
      &.active {
        border: 0;
        z-index: 1; /* Active tab is on top */
        background: linear-gradient(#666, #222);
      }
    }
  }
}

When the media query hits, this gets us here:

The active tab is still obvious (it's the only one shown) and a three line menu navicon is shown, which is getting universally known as a symbol to reveal more navigation.

The JavaScript Structure

The functionality we need:

  • If it's a #hash link, selecting it reveals the panel with that ID and hides the currently shown one.
  • Change the URL to indicate that #hash, but don't affect history (no back-button annoyances).
  • Visually indicate the now-currently-active tab (toggle classes appropriately).
  • If it's a linked tab, allow that link to work.
  • If the page loads with a hash that matches a tab, go to that tab.
  • In the dropdown state for small screens, allow the dropdown to open/close when you select it.

Some structure based on those requirements:

var Tabs = {

  init: function() {
    this.bindUIfunctions();
    this.pageLoadCorrectTab();
  },

  bindUIfunctions: function() {
  },

  changeTab: function(hash) {
  },

  pageLoadCorrectTab: function() {
  },

  toggleMobileMenu: function(event, el) {
  }

}

Tabs.init();

Changing Tabs Upon Selection

The only time we need to change a tab on selection is when the selected tab is a #hash link. Otherwise it's a linked tab and it should just follow that link. (And by "selection" I mean clicked or tapped or tabbed to and activated or whatever.) Thus our changeTab function can just accept that hash value and use it.

  changeTab: function(hash) {
    
    // find the link based on that hash
    var anchor = $("[href=" + hash + "]");

    // find the related content panel
    var div = $(hash);

    // activate correct anchor (visually)
    anchor.addClass("active").parent().siblings().find("a").removeClass("active");

    // activate correct div (visually)
    div.addClass("active").siblings().removeClass("active");

    // update URL, no history addition
    window.history.replaceState("", "", hash);

    // Close menu, in case in dropdown state
    anchor.closest("ul").removeClass("open");

  },

Handling Tab Clicks

We'll use standard event delegation here just to be efficient. Any "click" (which works fine for taps), assuming it's not the already-active tab and it's a #hash link, will just pass along that hash to the the changeTab function.

    // Delegation
    $(document)
      .on("click", ".transformer-tabs a[href^='#']:not('.active')", function(event) {
        Tabs.changeTab(this.hash);
        event.preventDefault();
      })

... and preventDefault() so the page doesn't jump down awkwardly.

Toggling the Dropdown

If the media query is in effect and thus the tabs in their dropdown state, we can toggle the dropdown "open" and "closed" when the .active tab is clicked. Because of our styling, we know the active tab covers the entire clickable area.

    $(document)
      // ... first click handler, chaining for efficiency 
      .on("click", ".transformer-tabs a.active", function(event) {
        Tabs.toggleMobileMenu(event, this);
        event.preventDefault();
      });

The toggleMobileMenu function is very simple. But I still like that we've abstract it into it's own function in case some day it needs to do more, we aren't getting all spaghetti'd up.

  toggleMobileMenu: function(event, el) {
    $(el).closest("ul").toggleClass("open");
  }

The .open class visually opens the menu via some CSS changes.

.transformer-tabs {
  ...
  @media (max-width: 700px) {
    ul {
      ...
      &.open {
        a {
          position: relative;
          display: block;
        }
      }
    }
  }
  ...
}

Removing the absolute positioning on those tabs and making them block-level makes the menu expand down and push the content below down as well. This is what makes it a dropdown.

Load the Correct Tab When Page is Loaded with a #hash

Turns out this is super easy. Just look at the hash in the URL and pass that to the changeTab function.

  pageLoadCorrectTab: function() {
    this.changeTab(document.location.hash);
  },

This is why we abstract functionality into functions we can re-use instead of spaghetti-land.

This Isn't Just Theoretical

I'm writing this after fixing up the tabs on CodePen, where the tabs kinda sucked until I did this. We have both #hash link tabs and real linked tabs on CodePen, thus the requirements.

Wanna Make It Better?

Perhaps a version where the menu doesn't push the content below down, the expanded dropdown just sits on top of the content would be cool. Perhaps one that could handle a ton of links (more than a small screen's worth) with some kind of scrolling or pagination. Perhaps one with some animations/transitions.

One issue with the exact demo we've built here is that you can't close the dropdown unless you select a tab. You can select the current tab to close it without doing anything, but you can't just click the three-line menu to close it. That's because you can't (as far as I know) bind a click event to a pseudo element. On CodePen I just used a <span> for the three-line menu so you could toggle it that way, but that does mean additional markup.

Demo

Note: The "Go To Google →" is a linked tab just to test it. It doesn't work here on CodePen because of the sandboxed iframe. It would work under normal circumstances.

See the Pen Transformer Tabs by Chris Coyier (@chriscoyier) on CodePen

Comments

  1. Just had to deal with this exact problem last week. Found a couple of examples where folks have tried to solve it before.

    http://foundation.zurb.com/docs/components/section.html

    http://jquerytools.org/demos/tabs/accordion.html

    • Permalink to comment#

      For tabs on small screens, I’m a fan of the accordion method above. It’s worth noting that jQuery Mobile uses the accordion concept, and does it really well.

      I like what Chris has done, it’s impressive, but something about it feels slightly clunky: I have to press to reveal the navigation and then press to my tab, and then somehow my new tab ends up on top…. it just seems a bit unexpected, for lack of a better word.

      Check out the jQuery Mobile solution, if you haven’t already:

      http://jquerymobile.com/demos/1.3.0-rc.1/docs/content/content-collapsible-set.html
      ( scroll to the very bottom of the page and look at the examples at the end )

    • There is a lot of love for the accordion approach. I’m not against it, but I think it is highly dependent on the situation. In the CodePen situation, if I were to have gone accordion style, it absolutely would have needed to be an “all closed” accordion at first so at least you get a glance of all the available options. I would not be OK with navigation that is pushed down to the bottom of the page that you have to swipe down to even discover. Then in the all-closed scenario, you aren’t showing any content at all, which certainly wouldn’t work in cases like the CodePen homepage. So… I kinda dig the dropdown approach.

    • Permalink to comment#

      I could see the accordion method working if each tab had very little content.
      If the user would otherwise have to scroll to find the menu, a link at the top/bottom of the tab that takes the user to the other menu items might suffice.

  2. dmsr
    Permalink to comment#

    Make better transform tabs (desktop) to accordion (mobile)… because hide the it’s not very intuitive.

  3. Permalink to comment#

    Didn’t know about achieving the three line nav icon that way, thanks!

    Also, sorry to nit-pick, but you’ve forgotten to close the comment in your second SCSS snippet, and you’ve written “[...] CodePen, where the kinda sucked” under This Isn’t Just Theoretical.

  4. Here’s how I did it

    It’s not perfect but it kind of works. The more tabs you have the more unwieldy it becomes with them stacked up above each other. With only 6 tabs or less I feel it works OK.

  5. Permalink to comment#

    I like the idea of the dropdown. A few months ago I built a different take on it for my team. My goal was tabs on large displays, accordions on small displays:.

    I’ll have to nitpick your code when I get home from work.

    • Great! You’re the first one that doesn’t place the navigation extra. I like the approach of Dirk Ginaders “Accessible Tabs“, where the navigation is built with JS out of the headlines. All other scripts do it wrong in my book.

  6. Eric
    Permalink to comment#

    How does anyone feel about the Metro…. err…. Modern UI approach? Essentially, that approach shows how many tabs can fit horizontally and then makes an ellipsis as the final tab that is a drop down with the remaining tab options that wouldn’t fit.

    I’ll be the first to confess that it seems like a lot of theoretical JavaScript work but my day job went from full on web development to SharePoint Evangelist so to point out this option seems to be my duty.

  7. Permalink to comment#

    Hi Chris,

    What do you think of the menu approach where it’s placed at the bottom of the page and there’s a big link to it from the top?

    Normally, I’d go dropdown, but one of my clients wants a responsively designed website, but an always-expanded menu.

    Thanks!

  8. Hi Chris,
    I like your approach. But mostly I guess I would change the tabs into an accordion. It is a known UI and needs less clicking than your approach.
    But what wonders me always when looking at tab-scripts is that everyone builds the naviation. In my view it would be better to let JS build the navigation, as these tabs are only usable with JS. I like the approach of Dirk Ginaders “Accessible Tabs“, where the navigation is built with JS out of the headlines. As far as I can see this script is unique in this detail. And I don’t understand, why.

    • I wrote my opinion on the accordion thing in this comment.

    • Permalink to comment#

      The best (dare I say semantic?) markup for a situation like this where you’re building the nav via Javascript would be a dl and each dt is a nav item and the dt is the tab content. Without Javascript or styles, the content is still presented in a readable way and logically grouped together with the dt acting as a heading. You could easily add Accordion styles and use JS to build the nav for a Tab or Dropdown layout and just have an option or a class that toggles between the layouts.

      I’ve done similar in the past, but not nearly as cleanly as Chris’s code. Might be worth revisiting.

  9. NSDCars5
    Permalink to comment#

    Thanks a lot! This one goes to my bookmark list. I think I’ll be needing this soon.

  10. Am i the only one that is seeing it? http://puu.sh/5almw.png

  11. Hi Chris,
    I like this approach very much. I think it is a grat solution for mobile sites. I used this code on a site i’m working on. I use it for a product detail page with a lot of different informations. you don’t need an “all tabs closed” state, because you want to give the user the information as quick as possible.

    Your solution has one problem though. When you use more than one tab box on a site the tab controls only trigger the content divs of the first tab-box. i forked your pen and edited the js part http://codepen.io/maxkarkowski/pen/wJmcH

  12. ainos
    Permalink to comment#

    If I open the tabs and want to close it, i’m “forced” to go to “Important Tabs”

    • Or select the tab you are currently on.

      In order to fix that, you’ll need one extra element (for the three-line icon probably) and you’d bind the toggling of the menu to that instead. An acceptable tradeoff.

  13. Glenn
    Permalink to comment#

    I would like to see an example of making http://jqueryui.com/tabs/ responsive.

    Especially since they are so widely used.

  14. Deric Mendez
    Permalink to comment#

    I had to deal with a similar issue where I needed to switch up the interface on smaller screens. At the time I couldn’t find a good solution so I came up with my own.

    https://github.com/dmmendez/content-switcher/
    This solution has the ability to toggle between tabs/dropdowns/accordion all with simple section markup. Given, I’m not a huge fan of accordions it’s there if needed.

    My approach is using more of a sectional approach rather than a nav approach that’s shown in this article. At the time, I chose the sectional approach because I had the need to transform into accordions rather than dropdowns and sections gave me a bit more flexibility to do that.

    Thanks for the article.

  15. Nour Akalay
    Permalink to comment#

    Concerning Ainos comment

    If I open the tabs and want to close it, i’m “forced” to go to “Important Tabs”

    I used a different approach than Chris suggested and it works pretty well I have to say.
    You can check it at Codepen

  16. JoeNord
    Permalink to comment#

    On a recent project we made the tab buttons nice and big and stacked them on top of each other on small screens, added a down-arrow icon and scroll-to-tab-content on click. So far users seem to respond well.

    Note1: site is in Norwegian, read it like lorem ipsum?
    Note2: scroll-on-click is annoying on desktop, but who browses on <768px on desktop anyway?
    Note3: site uses Weebly so markup is far from perfect.

  17. VastGirth
    Permalink to comment#

    I prefer doing it in pure CSS. It’s easy if you have another copy of your top navigation in your footer, which happens often.

    Use media queries to hide your top navigation and replace with a hyperlink to the footer menu (using the drop down menu icon) when viewing on smaller screens. Style the footer menu so it looks like a nice mobile friendly menu.

    See it action on the site i am working on at the moment. http://net-evidence.com

  18. C.A Brown
    Permalink to comment#

    I am use to using CSS, but this is something I am definitely going to try tonight, I am looking for something different for my next project. Thanks

  19. MaddTech

    I’m using this in conjuction with Foundation and everything works except for the MobileToggle. Any suggestions?

This comment thread is closed. If you have important information to share, you can always contact me.

*May or may not contain any actual "CSS" or "Tricks".