Skip to main content

Semantic Tab Panels via HTML Tables

There are plenty of tutorials online for creating tabbed panels in HTML documents, some using JavaScript, some using pure CSS tricks. Most of the approaches seem to assume that a list of some type is the appropriate element to use for a tabbed interface, but I'd argue that lists are not semantically descriptive of tabbed interfaces. They either include the main tab content along with the tab name, like so:

<ul class="tabs">
    <li class="tab-active"><a href="">Active</a> Active tab content goes here</li>
    <li><a href="">Second</a> Inactive second tab content goes here</li>
    <li><a href="">Third</a></li>
</ul>

Or the tab content has no semantic relationship with the tabs that control their display, like:

<ul class="tabs">
    <li class="tab-active"><a href="#first">Active</a></li>
    <li><a href="#second">Second</a></li>
    <li><a href="#third">Third</a></li>
</ul>
<div class="tab-body">
    <div id="first">Active tab content</div>
    <div id="second">Second tab content</div>
    <div id="third">Third tab content</div>
</div>

It's simply bizarre to mix the tab name with the main tab content as with the first case. The tab name controls interaction with the tab content, it's not part of the tab's content. They are semantically distinct, and so should be distinct in the markup.

In the second case, name and content are separated, but not in a semantically meaningful way, which makes it invisible to screen readers unless you add extra annotations. The connection between tab name and tab content is completely indirect since it depends upon interpreting the meaning of a link's target as referring to an element in the current document. This is obviously only one approach to separating tab name from content, but every approach I've seen so far makes a similar tradeoff: no technique expresses the relationship between tab name and tab content using markup that properly describes their relationship in a semantically meaningful way.

So what is the semantic relationship between tab name and tab content? Well clearly the tab name is a heading of some kind that summarizes and/or meaningfully describes the content it references.

Secondly, a set of tabs are logically related in some larger context, and so markup that also expresses a grouping is the most semantically meaningful. For instance, expressing tab content as a set of divs doesn't express this association, nor does expressing tab names as standard headings H1..H6.

However, there is an HTML element that provides headings and semantically associates those headings with its contents: tables!

<table class="tabs">
    <thead><tr>
        <th class="active" id="first">Active</th>
        <th id="Second">Second</th>
        <th id="Third">Third</th></tr>
    </thead>
    <tbody>
        <tr>
            <td headers="first">Active tab content</td>
            <td headers="second">Second tab content</td>
            <td headers="third">Third tab content</td>
        </tr>
    </tbody>
</table>

The table headings are the tab names, and the table cells contain its contents. Each cell designates the header/tab name to which it belongs, which is meaningful semantic markup for tabbed interfaces. Now all that remains is providing the interactive nature of tabbed interfaces. I think most of the standard techniques will work just as well here, but I used a simple javascript function and some basic CSS you can see in this jsfiddle. Basically, the CSS sets the backgrounds, borders and mouseover behaviour needed to mimic the familiar tab layout:

body {background-color:white;}
table.tabs{border:none; border-collapse:collapse}
table.tabs>thead>tr>th{text-align: left;padding:15px;position:relative;bottom:-1px;background-color:white}
table.tabs>thead>tr>th:hover{background-color:lightgrey; cursor:pointer}
table.tabs>thead>tr>th.active:hover{background-color:white}
table.tabs>thead{border-bottom:1px solid}
table.tabs>thead>tr>th.active{border:1px solid;border-bottom:none}

Then a simple javascript function run at document load progressively enhances the table to act like a tabbed interface:

<script type="text/javascript">
    function regTabs() {
        [].forEach.call(document.querySelectorAll('table.tabs td[headers]'), function (x) {
            var t = x.parentElement.parentElement; // table or tbody tracks active tab
            var th = document.getElementById(x.attributes.headers.value);
            if (th != null) {
                x.th = th;
                th.style.float = 'left';
                th.addEventListener('click', function() {
                    t.activeTab.style.display = 'none';
                    t.activeTab.th.classList.remove('active');
                    x.style.display = '';
                    th.classList.add('active');
                    t.activeTab = x;
                });
            }
            if (th == null || !th.classList.contains('active'))
                x.style.display = 'none';
            else
                t.activeTab = x;
        });
    }
    regTabs();
</script>

Basically, the headers are all floated left and the the active tab content is tracked as a property of the table. The headings capture the click event and modify the display styling property of the tab content and the active tab name/heading.

I'm no CSS or javascript wizard, so this can all probably be improved, but it suffices to make the point that mimicking tabbed interfaces using tables is a good semantic fit.

Given the use of DomElement.classList, I believe this requires IE10 or up, but it certainly could be ported to older browsers without too much difficulty.

Edit: a redditor pointed out that description lists, dl/dt/dd, are also appropriate semantic markup for tabbed panels. These would indeed work almost as well as tables, provided you don't try to use multiple dt elements for any given dd. I've created a jsfiddle demonstrating semantic tabs using description lists, but it requires strange, non-semantic arrangement of the markup to work easily.

Edit: here's a better semantic version using the <nav> tag for the tabs, since tabs are semantically a type of navigation within the current document. Each nav link directly references the id of the corresponding tabbed content. All you need to do is decorate the outer block with "tabbed" to get the dynamic tab interface.

Comments

Popular posts from this blog

async.h - asynchronous, stackless subroutines in C

The async/await idiom is becoming increasingly popular. The first widely used language to include it was C#, and it has now spread into JavaScript and Rust. Now C/C++ programmers don't have to feel left out, because async.h is a header-only library that brings async/await to C!Features:It's 100% portable C.It requires very little state (2 bytes).It's not dependent on an OS.It's a bit simpler to understand than protothreads because the async state is caller-saved rather than callee-saved.#include "async.h" struct async pt; struct timer timer; async example(struct async *pt) { async_begin(pt); while(1) { if(initiate_io()) { timer_start(&timer); await(io_completed() || timer_expired(&timer)); read_data(); } } async_end; } This library is basically a modified version of the idioms found in the Protothreads library by Adam Dunkels, so it's not truly ground breaking. I've mad…

Building a Query DSL in C#

I recently built a REST API prototype where one of the endpoints accepted a string representing a filter to apply to a set of results. For instance, for entities with named properties "Foo" and "Bar", a string like "(Foo = 'some string') or (Bar > 99)" would filter out the results where either Bar is less than or equal to 99, or Foo is not "some string".This would translate pretty straightforwardly into a SQL query, but as a masochist I was set on using Google Datastore as the backend, which unfortunately has a limited filtering API:It does not support disjunctions, ie. "OR" clauses.It does not support filtering using inequalities on more than one property.It does not support a not-equal operation.So in this post, I will describe the design which achieves the following goals: A backend-agnostic querying API supporting arbitrary clauses, conjunctions ("AND"), and disjunctions ("OR").Implementations of this…

Easy Reverse Mode Automatic Differentiation in C#

Continuing from my last post on implementing forward-mode automatic differentiation (AD) using C# operator overloading, this is just a quick follow-up showing how easy reverse mode is to achieve, and why it's important.Why Reverse Mode Automatic Differentiation?As explained in the last post, the vector representation of forward-mode AD can compute the derivatives of all parameter simultaneously, but it does so with considerable space cost: each operation creates a vector computing the derivative of each parameter. So N parameters with M operations would allocation O(N*M) space. It turns out, this is unnecessary!Reverse mode AD allocates only O(N+M) space to compute the derivatives of N parameters across M operations. In general, forward mode AD is best suited to differentiating functions of type:RRNThat is, functions of 1 parameter that compute multiple outputs. Reverse mode AD is suited to the dual scenario:RN → RThat is, functions of many parameters that return a single real …