Search

Pony Foo

Ramblings of a degenerate coder

Your Tab Views Suck

(5 comments)reading time: , published

What if I told you... we can write a tabbed UI view without using JavaScript, which works in every modern browser, and even clocks around 20 total lines of code?

In this article we'll discuss the pitfalls of resorting to jQuery UI and its ilk, and then compare it with an approach that doesn't even involve any JavaScript, but merely some lateral thinking and a couple of CSS tricks.

Why would I even want that? I have jQuery UI for that!

jquery-ui.png

Sure you do, and that's why you want these. You don't need jQuery. Consider an example taken straight from the jQuery UI Tabs page. First, let's glance at the HTML.

<div id="tabs">
  <ul>
    <li><a href="#tabs-1">Nunc tincidunt</a></li>
    <li><a href="#tabs-2">Proin dolor</a></li>
    <li><a href="#tabs-3">Aenean lacinia</a></li>
  </ul>
  <div id="tabs-1">
    <p>some lipsum</p>
  </div>
  <div id="tabs-2">
    <p>some lipsum</p>
  </div>
  <div id="tabs-3">
    <p>some lipsum</p>
  </div>
</div>

That is exactly what we want, non-repetitive HTML that just marks up the tabs and their content. This is as good as it gets, though, because then there's the JavaScript.

<script>
  $(function() {
    $( "#tabs" ).tabs();
  });
</script>

Okay, okay, we're fine with this as well, sort of. It's just a selector, and a function. You should be wondering why the heck we'd need to wait until DOM ready to cash in some tabs, though. Your real problem should be with having to pile up 7 extra HTTP requests just so you can get a few tabs there, and then wait until those finish so that you can have some tabs show up.

<link rel="stylesheet" href="http://code.jquery.com/ui/1.10.3/themes/smoothness/jquery-ui.css" />
<script src="http://code.jquery.com/jquery-1.9.1.js"></script>
<script src="http://code.jquery.com/ui/1.10.3/jquery-ui.js"></script>

Ever wonder what goes on in the Chrome DevTools network tab? Mayhem, pure mayhem.

network.png

So it's not even just those 3 resources, which by the way amount to well over 200k, compressed. Apparently we're fetching four small images as well, just because we can. This time, compressed to... oops! two times their original size.

Never mind the well known rule to always push scripts to the bottom, which you can't follow now, because otherwise your precious tabs would FOUC (Flash of Unstyled Content) all over your human, which we'd never want to happen. jQuery recognizes that last one, so they silently put all of their example code in the <head>, and now it's your problem.

7 HTTP requests, 200k in our pocket, and a DOMContentLoaded event later...

At long last! We're now able to render these awesome looking UI tabs! Yes!! Except they look really hideous, and with a bunch of classes, like ui-tabs-nav ui-helper-reset ui-helper-clearfix ui-widget-header ui-corner-all, they are virtually impossible to style.

jquery-tabs.png

Luckily, we don't need jQuery for any of this. In fact, I'd argue that jQuery UI is utterly useless, if it weren't for drag and drop, and particularly the sortable functionality. That being said, that's a piece of jQuery UI people rarely need. You most certainly don't need that kind of thing to create a tabbed view. So you have a combination of rarely used components mixed and matched with completely useless ones. True, you can download a customized package that trims away the fat components you don't need, but did you even bother? That won't fix the through-the-roof HTTP request count issue, anyways. Let's forget about jQuery for a while, try to think, and resolve this without using it.

Of course, there's a better way

Okay, so no jQuery. We've got a clean slate. Good! How do we get tabs? Hmmm... Let's start with what we know, like all ambitious projects do. The HTML was mostly fine, we just ditched the JavaScript. What is the single best way to toggle state without using JavaScript at all? The answer lies in <input> types radio and checkbox. The latter isn't all that useful to us, we need to be able to toggle between tabs, not turn them on and off. Only one should be enabled at any given point, this is a perfect scenario to use radio buttons!

Radio buttons have state, and only one can be checked at any time. Let's imagine a world without tabs. Something like the code below would be enough for us, then.

<input type='radio' name='tab-group' class='tv-radio' checked />
<input type='radio' name='tab-group' class='tv-radio' />
<input type='radio' name='tab-group' class='tv-radio' />

That's great for state, but we'd like some content for each tab to show up with that. Well, consider adding a <div> after each of those inputs.

<input type='radio' name='tab-group' class='tv-radio' checked />
<div class='tv-content'>Tab 1</div>
<input type='radio' name='tab-group' class='tv-radio' />
<div class='tv-content'>Tab 2</div>
<input type='radio' name='tab-group' class='tv-radio' />
<div class='tv-content'>Tab 3</div>

Using a combination of the :checked pseudo-selector and the + next sibling selector, we'd quickly be able to show one of those <div>s, depending on which radio button is on! Amusing stuff.

.tv-content {
  display: none; /* hide tabs next to unselected radios */
}

.tv-radio:checked + .tv-content {
  display: block;
}

This is fine and minimalistic, but we can agree it looks even worse than the jQuery UI version. It's not even close to usable, and styling radios is not doable.

radios.png

Half-way there now...

The radios are ugly, and they don't belong, thus, we need to hide them. However, that presents the fundamental inconvenience that we can't click on them now. Or can we? Time to pull another trick from the bag, this time we'll turn to <label> elements. Labels allow us to use the for attribute, making it so that when we click on them, the related input gets the click, too. This reference requires an id attribute on the inputs, but that's no big deal.

The HTML ends up looking like below.

<label for='tab-1'>Tab 1</label>
<label for='tab-2'>Tab 2</label>
<label for='tab-3'>Tab 3</label>
<input type='radio' name='tab-group' class='tv-radio' id='tab-1' checked />
<div class='tv-content'>Contents 1</div>
<input type='radio' name='tab-group' class='tv-radio' id='tab-2' />
<div class='tv-content'>Contents 2</div>
<input type='radio' name='tab-group' class='tv-radio' id='tab-3' />
<div class='tv-content'>Contents 3</div>

The only tweak to the CSS is hiding the radio buttons.

.tv-radio {
  display: none;
}

Still not really cute, but easy to style, and barely uses any code.

labels.png

Below is a screenshot of how they ended up looking after a tad of styling.

tabs.png

The source code is up on GitHub and CodePen.

<wrapping>up</wrapping>

We've spent a good chunk of this article looking at the problems with assuming libraries just know better, and how we might be perfectly able to home-bake a cake and eat it, too.

The jQuery library excels at reducing conflicts across the board (even though you don't really need it as badly as you might think), but their UI framework does little about that, and a lot about drastically increasing your application's network bandwidth usage.

What other solutions did you learn about where you didn't really need any JavaScript at all? I'd love to hear!

Comments(5)

notatestuser

I'd think the use of bootstrap-tab is something that today's front-end developers may relate to a little better. Sure, jQuery UI was the dog's bollocks in 2008, but these days we're able to enjoy the luxuries of having the selectors watch a data- attribute for code-free initialisation.

You can activate a tab or pill navigation without writing any JavaScript by simply specifying data-toggle="tab" or data-toggle="pill" on an element. Adding the nav and nav-tabs classes to the tab ul will apply the Bootstrap tab styling. (source)

Not that it's a huge improvement, though. Users of bootstrap (heaven forbid for the tabs alone) suffer the same problems with having to cram a bunch of styles and an icon spritesheet down the wire on each load.

starsthatfell

It's a nice case study, but this approach is neither semantic or accessible. One great thing that the jQuery UI team worked out in their tab control is keyboard accessibility, as well as adhering to the WAI-ARIA specification. Not all internet users rely on the mouse. Many mainly just use the keyboard in combination with screen readers. An interface such as this would be next to impossible to traverse via a screen reader.

Tatu Kairi

This was really nice trick.

The problem with this approach however is that you cannot style label based on which radio button is selected -- at least I didn't find a way. Showing which tab is active is pretty standard UX for tab views and for a reason.

However, adding 5 lines of native JS remedies this problem, so it is still extremely light-weight solution, which is nice.

Anton Guz

Smart idea.

But pay attention that hidden checkbox doesn't change his state in IE8. There is workaround with opacity 0.