ponyfoo.com

Your Tab Views Suck

Fix
A relevant ad will be displayed here soon. These ads help pay for my hosting.
Please consider disabling your ad blocker on Pony Foo. These ads help pay for my hosting.
You can support Pony Foo directly through Patreon or via PayPal.

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
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
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
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
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
labels.png

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

tabs.png
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!

Liked the article? Subscribe below to get an email when new articles come out! Also, follow @ponyfoo on Twitter and @ponyfoo on Facebook.
One-click unsubscribe, anytime. Learn more.

Comments