Radio-Controlled Web Design

Interactive user interfaces are a necessity in our responsive world. Smaller screens constrain the amount of content that can be displayed at any given time, so we need techniques to keep navigation and secondary information out of the way until they’re needed. From tabs and modal overlays to hidden navigation, we’ve created many powerful design patterns that show and hide content using JavaScript.

Article Continues Below

JavaScript comes with its own mobile challenges, though. Network speeds and data plans vary wildly, and every byte we deliver has an impact on the render speed of our pages or applications. When we add JavaScript to a page, we’re typically adding an external JavaScript file and an optional (usually large) library like jQuery. These interfaces won’t become usable until all the content, JavaScript files included, is downloaded—creating a slow and sluggish first impression for our users.

If we could create these content-on-demand patterns with no reliance on JavaScript, our interfaces would render earlier, and users could interact with them as soon as they were visible. By shifting some of the functionality to CSS, we could also reduce the amount of JavaScript needed to render the rest of our page. The result would be smaller file sizes, faster page-load times, interfaces that are available earlier, and the same functionality we’ve come to rely on from these design patterns.

In this article, I’ll explore a technique I’ve been working on that does just that. It’s still a bit experimental, so use your best judgment before using it in your own production systems.

Understanding JavaScript’s role in maintaining state#section2

To understand how to accomplish these design patterns without JavaScript at all, let’s first take a look at the role JavaScript plays in maintaining state for a simple tabbed interface.

See the demo: Show/hide example


Let’s take a closer look at the underlying code.

<div class="js-tabs">

    <div class="tabs">
        <a href="#starks-panel" id="starks-tab"
            class="tab active">Starks</a>
        <a href="#lannisters-panel" id="lannisters-tab"
            class="tab">Lannisters</a>
        <a href="#targaryens-panel" id="targaryens-tab"
            class="tab">Targaryens</a>
    </div>

    <div class="panels">
        <ul id="starks-panel" class="panel active">
            <li>Eddard</li>
            <li>Caitelyn</li>
            <li>Robb</li>
            <li>Sansa</li>
            <li>Brandon</li>
            <li>Arya</li>
            <li>Rickon</li>
        </ul>
        <ul id="lannisters-panel" class="panel">
            <li>Tywin</li>
            <li>Cersei</li>
            <li>Jamie</li>
            <li>Tyrion</li>
        </ul>
        <ul id="targaryens-panel" class="panel">
            <li>Viserys</li>
            <li>Daenerys</li>
        </ul>
    </div>

</div>

Nothing unusual in the layout, just a set of tabs and corresponding panels that will be displayed when a tab is selected. Now let’s look at how tab state is managed by altering a tab’s class:

...

.js-tabs .tab {
    /* inactive styles go here */
}
.js-tabs .tab.active {
    /* active styles go here */
}

.js-tabs .panel {
    /* inactive styles go here */
}
.js-tabs .panel.active {
    /* active styles go here */
}

...

Tabs and panels that have an active class will have additional CSS applied to make them stand out. In our case, active tabs will visually connect to their content while inactive tabs remain separate, and active panels will be visible while inactive panels remain hidden.

At this point, you’d use your preferred method of working with JavaScript to listen for click events on the tabs, then manipulate the active class, removing it from all tabs and panels and adding it to the newly clicked tab and corresponding panel. This pattern is pretty flexible and has worked well for a long time. We can simplify what’s going on into two distinct parts:

  1. JavaScript binds events that manipulate classes.
  2. CSS restyles elements based on those classes.

State management without JavaScript#section3

Trying to replicate event binding and class manipulation in CSS and HTML alone would be impossible, but if we define the process in broader terms, it becomes:

  1. User input changes the system’s active state.
  2. The system is re-rendered when the state is changed.

In our HTML- and CSS-only solution, we’ll use radio buttons to allow the user to manipulate state, and the :checked pseudo-class as the hook to re-render.

The solution has its roots in Chris Coyier’s checkbox hack, which I was introduced to via my colleague Scott O’Hara in his morphing menu button demo. In both cases, checkbox inputs are used to maintain two states without JavaScript by styling elements using the :checked pseudo-class. In this case, we’ll be using radio buttons to increase the number of states we can maintain beyond two.

Wait, radio buttons?#section4

Using radio buttons to do something other than collect form submission data may make some of you feel a little uncomfortable, but let’s look at what the W3C says about input use and see if we can ease some concerns:

The <input> element represents a typed data field, usually with a form control to allow the user to edit the data. (emphasis mine)

“Data” is a pretty broad term—it has to be to cover the multitude of types of data that forms collect. We’re allowing the user to edit the state of a part of the page. State is just data about that part of the page at any given time. This may not have been the intended use of <input>, but we’re holding true to the specification.

The W3C also states that inputs may be rendered wherever “phrasing content” can be used, which is basically anywhere you could put standalone text. This allows us to use radio buttons outside of a form.

Radio-controlled tabs#section5

So now that we know a little more about whether we can use radio buttons for this purpose, let’s dig into an example and see how they can actually remove or reduce our dependency on JavaScript by modifying the original tabs example.

Add radio buttons representing state#section6

Each radio button will represent one state of the interactive component. In our case, we have three tabs and each tab can be active, so we need three radio buttons, each of which will represent a particular tab being active. By giving the radio buttons the same name, we’ll ensure that only one may be checked at any time. Our JavaScript example had the first tab active initially, so we can add the checked attribute to the radio button representing the first tab, indicating that it is currently active.

Because CSS selectors can only style sibling or child selectors based on the state of another element, these radio buttons must come before any content that needs to be visually manipulated. In our case, we’ll put our radio buttons just before the tabs div:

    <input class="state" type="radio" name="houses-state"
        id="starks" checked />
    <input class="state" type="radio" name="houses-state"
        id="lannisters" />
    <input class="state" type="radio" name="houses-state"
        id="targaryens" />

    <div class="tabs">
    ...

Replace click and touch areas with labels#section7

Labels naturally respond to click and touch events. We can’t tell them how to react to those events, but the behavior is predictable and we can leverage it. When a label associated with a radio button is clicked or touched, the radio button is checked while all other radio buttons in the same group are unchecked.

By setting the for attribute of our labels to the id of a particular radio button, we can place labels wherever we need them while still inheriting the touch and click behavior.

Our tabs were represented with anchors in the earlier example. Let’s replace them with labels and add for attributes to wire them up to the correct radio buttons. We can also remove the active class from the tab and panel as the radio buttons will be maintaining state:

...
    <input class="state" type="radio" title="Targaryens"
        name="houses-state" id="targaryens" />

    <div class="tabs">
        <label for="starks" id="starks-tab"
            class="tab">Starks</label>
        <label for="lannisters" id="lannisters-tab"
            class="tab">Lannisters</label>
        <label for="targaryens" id="targaryens-tab"
            class="tab">Targaryens</label>
    </div>

    <div class="panels">
...

Hide radio buttons with CSS#section8

Now that our labels are in place, we can safely hide the radio buttons. We still want to keep the tabs keyboard accessible, so we’ll just move the radio buttons offscreen:

...

.radio-tabs .state {
    position: absolute;
    left: -10000px;
}

...

Style states based on :checked instead of .active#section9

The :checked pseudo-class allows us to apply CSS to a radio button when it is checked. The sibling selector ~ allows us to style elements that follow an element in the same level. Combined, we can style anything after the radio buttons based on the buttons’ state.

The pattern is #radio:checked ~ .something-after-radio or optionally #radio:checked ~ .something-after-radio .something-nested-deeper:

...

.tab {
    ...
}
#starks:checked ~ .tabs #starks-tab,
#lannisters:checked ~ .tabs #lannisters-tab,
#targaryens:checked ~ .tabs #targaryens-tab {
    ...
}

.panel {
    ...
}
#starks:checked ~ .panels #starks-panel,
#lannisters:checked ~ .panels #lannisters-panel,
#targaryens:checked ~ .panels #targaryens-panel {
    ...
}

...

Now when the tab labels are clicked, the appropriate radio button will be checked, which will style the correct tab and panel as active. The result:

See the demo: Show/hide example


Browser support#section10

The requirements for this technique are pretty low. As long as a browser supports the :checked pseudo-class and ~ sibling selector, we’re good to go. Firefox, Chrome, and mobile Webkit have always supported these selectors. Safari has had support since version 3, and Opera since version 9. Internet Explorer started supporting the sibling selector in version 7, but didn’t add support for :checked until IE9. Android supports :checked but has a bug which impedes it from being aware of changes to a checked element after page load.

That’s decent support, but with a little extra work we can get Android and older IE working as well.

Fixing the Android 2.3 :checked bug#section11

In some versions of Android, :checked won’t update as the state of a radio group changes. Luckily, there’s a fix for that involving a webkit-only infinite animation on the body, which Tim Pietrusky points out in his advanced checkbox hack:

...

/* Android 2.3 :checked fix */
@keyframes fake {
    from {
        opacity: 1;
    }
    to {
        opacity: 1
    }
}
body {        
    animation: fake 1s infinite;
}

...

JavaScript shim for old Internet Explorer#section12

If you need to support IE7 and IE8, you can add this shim to the bottom of your page in a script tag:

document.getElementsByTagName('body')[0]
.addEventListener('change', function (e) {
    var radios, i;
    if (e.target.getAttribute('type') === 'radio') {
        radios = document.querySelectorAll('input[name="' +
            e.target.getAttribute('name') + '"]');
        for (i = 0; i < radios.length; i += 1) {
            radios[ i ].className = 
                radios[ i ].className.replace(
                    /(^|\s)checked(\s|$)/,
                    ' '
                );
            if (radios[ i ] === e.target) {
                radios[ i ].className += ' checked';
            }
        }
    }
});

This adds a checked class to the currently checked radio button, allowing you to double up your selectors and keep support. Your selectors would have to be updated to include :checked and .checked versions like this:

...

.tab {
    ...
}
#starks:checked ~ .tabs #starks-tab,
#starks.checked ~ .tabs #starks-tab,
#lannisters:checked ~ .tabs #lannisters-tab,
#lannisters.checked ~ .tabs #lannisters-tab,
#targaryens:checked ~ .tabs #targaryens-tab,
#targaryens.checked ~ .tabs #targaryens-tab {
    ...
}

.panel {
    ...
}
#starks:checked ~ .panels #starks-panel,
#starks.checked ~ .panels #starks-panel,
#lannisters:checked ~ .panels #lannisters-panel,
#lannisters.checked ~ .panels #lannisters-panel,
#targaryens:checked ~ .panels #targaryens-panel,
#targaryens.checked ~ .panels #targaryens-panel {
    ...
}

...

Using an inline script still saves a potential http request and speeds up interactions on newer browsers. When you choose to drop IE7 and IE8 support, you can drop the shim without changing any of your code.

Maintaining accessibility#section13

While our initial JavaScript tabs exhibited the state management between changing tabs, a more robust example would use progressive enhancement to change three titled lists into tabs. It should also handle adding all the ARIA roles and attributes that screen readers and other assistive technologies use to navigate the contents of a page. A better JavaScript example might look like this:

See the demo: Show/hide example


Parts of the HTML are removed and will now be added by additional JavaScript; new HTML has been added and will be hidden by additional JavaScript; and new CSS has been added to manage the pre-enhanced and post-enhanced states. In general, our code has grown by a good amount.

In order to support ARIA, particularly managing the aria-selected state, we’re going to have to bring some JavaScript back into our radio-controlled tabs. However, the amount of progressive enhancement we need to do is greatly reduced.

If you aren’t familiar with ARIA or are a little rusty, you may wish to refer to the ARIA Authoring Practices for tabpanel.

Adding ARIA roles and attributes#section14

First, we’ll add the role of tablist to the containing div.

<div class="radio-tabs" role="tablist">
  
    <input class="state" type="radio" name="houses-state"
        id="starks" checked />
    ...

Next, we’ll add the role of tab and attribute aria-controls to each radio button. The aria-controls value will be the id of the corresponding panel to show. Additionally, we’ll add titles to each radio button so that screen readers can associate a label with each tab. The checked radio button will also get aria-selected="true":

<div class="radio-tabs" role="tablist">
  
    <input class="state" type="radio" title="Starks"
        name="houses-state" id="starks" role="tab"
        aria-controls="starks-panel" aria-selected="true"checked />
    <input class="state" type="radio" title="Lanisters" 
        name="houses-state" id="lannisters" role="tab" 
        aria-controls="lannisters-panel" />
    <input class="state" type="radio" title="Targaryens" 
        name="houses-state" id="targaryens" role="tab" 
        aria-controls="targaryens-panel" />

    <div class="tabs">

We’re going to hide the visual tabs from assistive technology because they are shallow interfaces to the real tabs (the radio buttons). We’ll do this by adding aria-hidden="true" to our .tabs div:

    ...
    <input class="state" type="radio" title="Targaryens"
        name="houses-state" id="targaryens" role="tab"
        aria-controls="targaryens-panel" />

    <div class="tabs" aria-hidden="true">
        <label for="starks" id="starks-tab"
            class="tab">Starks</label>
    ...

The last bit of ARIA support we need to add is on the panels. Each panel will get the role of tabpanel and an attribute of aria-labeledby with a value of the corresponding tab’s id:

   ...
   <div class="panels">
        <ul id="starks-panel" class="panel active"
            role="tabpanel" aria-labelledby="starks-tab">
            <li>Eddard</li>
            <li>Caitelyn</li>
            <li>Robb</li>
            <li>Sansa</li>
            <li>Brandon</li>
            <li>Arya</li>
            <li>Rickon</li>
        </ul>
        <ul id="lannisters-panel" class="panel"
            role="tabpanel" aria-labelledby="lannisters-tab">
            <li>Tywin</li>
            <li>Cersei</li>
            <li>Jamie</li>
            <li>Tyrion</li>
        </ul>
        <ul id="targaryens-panel" class="panel"
            role="tabpanel" aria-labelledby="targaryens-tab">
            <li>Viserys</li>
            <li>Daenerys</li>
        </ul>
    </div>
    ...

All we need to do with JavaScript is to set the aria-selected value as the radio buttons change:

$('.state').change(function () {
    $(this).parent().find('.state').each(function () {
        if (this.checked) {
            $(this).attr('aria-selected', 'true');
        } else {
            $(this).removeAttr('aria-selected');
        }       
    });
});

This also gives an alternate hook for IE7 and IE8 support. Both browsers support attribute selectors, so you could update the CSS to use [aria-selected] instead of .checked and remove the support shim.

...

#starks[aria-selected] ~ .tabs #starks-tab,
#lannisters[aria-selected] ~ .tabs #lannisters-tab,
#targaryens[aria-selected] ~ .tabs #targaryens-tab,
#starks:checked ~ .tabs #starks-tab,
#lannisters:checked ~ .tabs #lannisters-tab,
#targaryens:checked ~ .tabs #targaryens-tab {
    /* active tab, now with IE7 and IE8 support! */
}

...

The result is full ARIA support with minimal JavaScript—and you still get the benefit of tabs that can be used as soon as the browser paints them.

See the demo: Show/hide example


That’s it. Note that because the underlying HTML is available from the start, unlike the initial JavaScript example, we didn’t have to manipulate or create any additional HTML. In fact, aside from adding ARIA roles and parameters, we didn’t have to do much at all.

Limitations to keep in mind#section15

Like most techniques, this one has a few limitations. The first and most important is that the state of these interfaces is transient. When you refresh the page, these interfaces will revert to their initial state. This works well for some patterns, like modals and offscreen menus, and less well for others. If you need persistence in your interface’s state, it is still better to use links, form submission, or AJAX requests to make sure the server can keep track of the state between visits or page loads.

The second limitation is that there is a scope gap in what can be styled using this technique. Since you cannot place radio buttons before the <body> or <html> elements, and you can only style elements following radio buttons, you cannot affect either element with this technique.

The third limitation is that you can only apply this technique to interfaces that are triggered via click, tap, or keyboard input. You can use progressive enhancement to listen to more complex interactions like scrolling, swipes, double-tap, or multitouch, but if your interfaces rely on these events alone, standard progressive enhancement techniques may be better.

The final limitation involves how radio groups interact with the tab flow of the document. If you noticed in the tab example, hitting tab brings you to the tab group, but hitting tab again leaves the group. This is fine for tabs, and is the expected behavior for ARIA tablists, but if you want to use this technique for something like an open and close button, you’ll want to be able to have both buttons in the tab flow of the page independently based on the button location. This can be fixed through a bit of JavaScript in four steps:

  1. Set the radio buttons and labels to display: none to take them out of the tab flow and visibility of the page.
  2. Use JavaScript to add buttons after each label.
  3. Style the buttons just like the labels.
  4. Listen for clicks on the button and trigger clicks on their neighboring label.

Even using this process, it is highly recommended that you use a standard progressive enhancement technique to make sure users without JavaScript who interact with your interfaces via keyboard don’t get confused with the radio buttons. I recommend the following JavaScript in the head of your document:

<script>document.documentElement.className+=" js";</script>

Before any content renders, this will add the js class to your <html> element, allowing you to style content depending on whether or not JavaScript is turned on. Your CSS would then look something like this:

.thing {
    /* base styles - when no JavaScript is present
       hide radio button labels, show hidden content, etc. */
}

.js .thing {
    /* style when JavaScript is present
       hide content, show labels, etc. */
}

Here’s an example of an offscreen menu implemented using the above process. If JavaScript is disabled, the menu renders open at all times with no overlay:

See the demo: Show/hide example


Implementing other content-on-demand patterns#section16

Let’s take a quick look at how you might create some common user interfaces using this technique. Keep in mind that a robust implementation would address accessibility through ARIA roles and attributes.

Modal windows with overlays#section17

  • Two radio buttons representing modal visibility
  • One or more labels for modal-open which can look like anything
  • A label for modal-close styled to look like a semi-transparent overlay
  • A label for modal-close styled to look like a close button

See the demo: Show/hide example


Off-screen menu#section18

  • Two radio buttons representing menu visibility
  • A label for menu-open styled to look like a menu button
  • A label for menu-close styled to look like a semi-transparent overlay
  • A label for menu-close styled to look like a close button

See the demo: Show/hide example


Switching layout on demand#section19

  • Radio buttons representing each layout
  • Labels for each radio button styled like buttons

See the demo: Show/hide example


Switching style on demand#section20

  • Radio buttons representing each style
  • Labels for each radio button styled like buttons

See the demo: Show/hide example


Content carousels#section21

  • X radio buttons, one for each panel, representing the active panel
  • Labels for each panel styled to look like next/previous/page controls

See the demo: Show/hide example


Other touch- or click-based interfaces#section22

As long as the interaction does not depend on adding new content to the page or styling the <body> element, you should be able to use this technique to accomplish some very JavaScript-like interactions.

Occasionally you may want to manage multiple overlapping states in the same system—say the color and size of a font. In these situations, it may be easier to maintain multiple sets of radio buttons to manage each state.

It is also highly recommended that you use autocomplete="off" with your radio buttons to avoid conflict with browser form autofill switching state on your users.

Radio-control the web?#section23

Is your project right for this technique? Ask yourself the following questions:

  1. Am I using complex JavaScript on my page/site that can’t be handled by this technique?
  2. Do I need to support Internet Explorer 6 or other legacy browsers?

If the answer to either of those question is “yes,” you probably shouldn’t try to integrate radio control into your project. Otherwise, you may wish to consider it as part of a robust progressive enhancement technique.

Most of the time, you’ll be able to shave some bytes off of your JavaScript files and CSS. Occasionally, you’ll even be able to remove Javascript completely. Either way, you’ll gain the appearance of speed—and build a more enjoyable experience for your users.

About the Author

Art Lawry

Art Lawry has been building things on the web for more than 14 years and is currently a JavaScript engineer at Aereo in Boston. When he’s not trying to find the balance between responsive design, performance, and progressive enhancement, he’s probably making awful puns, enjoying a good cocktail bar, or relaxing with his wife and dog. He can be found on Twitter at @artlawry.

24 Reader Comments

  1. The only problem I see with your method is the JavaScript to support ARIA relies on jQuery, which defeats the page load requirement you started out with.

  2. The shim for IE7 and IE8 utilizes `addEventListener` which is not supported by either of those browsers. It also uses `document.querySelectorAll` which is not supported by IE7.

  3. Thanks Chris,

    While I personally prefer to work in native JavaScript I find that jQuery’s simpler syntax and familiarity within the web design and development communities makes it easier to explain the logic behind what’s actually being done.

    The use of jQuery in the codepen.io examples is meant to make understanding the technique easier for those that may not be as familiar with native JavaScript. If you’re interested in the native JavaScript required to accomplish the same thing, here’s the radio-controlled tabs example in straight JavaScript.

  4. Nice article, thanks Art. I happen to read this on an iphone 4 though, using Chrome. I cant get the CSS examples to work. The functionak tabs are the ones that use javascript only for me. May be because of the code editor that is used to showcase the examples I am not sure. I will definitely get back and test it on my desktop pc as well.

  5. Thanks Thodwris,

    I’m very interested in what you find. Can you let me know what version of iOS your iPhone 4 is currently on as well?

  6. your ie7 and ie8 JS won’t work in those browsers because neither supports addEventListener and only ie8 supports querSelectorAll

  7. Thanks Shaw,

    Checkboxes will work (I reference Chris Coyier’s and Scott O’Hara’s examples which both use checkboxes). I used radio buttons, even for the two-state use-cases, just to keep the complexity down. I personally prefer using checkboxes for two-state systems.

    There may be some benefit to using radio buttons everywhere for implementation consistencies, however, that is if you have at least one system with more than 2 states.

  8. Good thoughts.

    To me, a single state toggle is different enough from a multi-state system that the difference in implementations is warranted. The single state toggle code is much easier to modularize with general classes and all that changes in HTML per module is the input ID and label’s “for” attribute. A quick example: http://codepen.io/shshaw/pen/Bsbhl

    Thanks for the response, and thanks for the article. It got me rethinking some of my simple toggle uses on the site I’m currently developing.

  9. Hi Art,

    Love the article. I tried to build something based completely on focus states of a button recently but couldn’t get it working on mobile in the time I had, great to see that people are thinking along the same sort of lines.

    I’m of a similar opinion to Shaw, that checkboxes are really useful for open/close systems, otherwise I’ve found I had to hack together solutions such moving a second radio button label above the first to get the binary behaviour. With checkbox inputs in mind, have you got any thoughts on how to create a checkbox & radio input shim for ie7 and 8? As far as I can tell, it’s currently limited to interpreting radio inputs only.

    Interested to hear your thoughts!

    Cheers,
    Josh

  10. @Josh

    I’m working on a revised shim for radio buttons. It turns out that IE7 and IE8 _really_ don’t like triggering onchange events when you interact with their labels.

    For the current time I think it’s safer to think of this technique as IE9-forward, but I’ll post here once I have a more robust shim that handles checkboxes as well. It will most likely involve listening for a click on the labels, triggering change on the radio button, and blurring and focussing to try to force the change. If anyone has experience with this particular IE7 and IE8 shortcoming feel free to contribute.

  11. @Josh

    Here’s the revised shim for IE7 and IE8 that also supports checkboxes: https://gist.github.com/artlawry/6d2c15bc6aa1fe499611

    IE7/8 really doesn’t like triggering onchange events until the radios/checkboxes lose focus, so a bit of click triggering was necessary.

    Also, IE7/8 does not like re-drawing parts of the page that dynamically change via sibling selectors, so the solution was to add and remove a class from the radio/checkbox parent which triggers a redraw of everything the input would have access to via the sibling selector.

    One final IE8 bug is that any comma-grouped css selectors will fail if any one of them do, so I had to separate out the _starks:checked_ rules from the _starks.checked_ rules.

    It’s not pretty, but it works.

  12. @RSpil, thanks for having my nerd back! I’ll update my Codepens _immediately_, may George. R. R. Martin have mercy on my soul.

  13. Great article!It is really nice to know that there are others dealing with similar situations, I am not alone. Having people skills as well as technical skills is an awesome combination.We should all work at it! Read more about my website here: Adjustable electric beds

  14. @Paul, thanks for pointing that out. Can you give me any more specifics to track down the problem?

  15. Why create a shim of your own?
    For IE lt 9 I use html5.js and ie9.js (and throw respond.js into the mix) – I don’t care if IE8 has to load several scripts, users of that browser should be glad to get a working page 😉

  16. I was so hopeful this would help with make tab panel components work in a consistent way on screen readers, because every implementation of tabs I’ve seen is problematic. Radio buttons groups function in a way that is almost identical to the recommendations for how tabs should work in a tablist. Unfortunately, JAWS (15 at least) in the latest version of IE on Windows 8.1 does not play well with this method. In Forms mode, it voices “Insert+F! for help” twice every time you move to a different tab. Also, the semantics are a little muddled. Radio buttons have strong native semantics that don’t completely overlap with tabs. This means that, though we are told by the screen reader that we are on a tab, we are also informed that the state of that tab is “checked”, which doesn’t really make sense for a tab. Specifically, aria-checked is not one of the inherited states and properties of something which has the “tab” role. Tabs can be selected and maybe even expanded, but being checked is, I think, pushing it. Also, some of the less common screen reader/browser combinations on Windows are confused by this approach. Try it out in JAWS/Firefox or NVDA/IE, and you’ll see what I mean. Haven’t tried it with VoiceOver yet, but it would be interesting to see how it works on OS X and iOS devices.

  17. I like the general idea, but I dislike the idea of hard-coding the ids into the css. I might (for example) want to have a variable number (or variably named) tabs on a page, generated within the html, and I don’t want to have to change the css for each case.

    However, there is a rather neat way round this: I wonder if you see any downsides? You can put all the labels first to make a tabbed list at the top of the page

    <label for=”starks” …>
    <label for=”lannisters” …>

    then have each input followed directly by the content

    <input id=”starks” …>
    <ul class=”panel”>content</ul>

    <input id=”lannisters” …>
    <ul class=”panel”>content</ul>

    and in the css you can then simply do something like

    .radio-tabs .panel {
    display: none;
    }
    .radio-tabs input:checked + .panel {
    display: block;
    }

    without having to code any ids at all into the css

Got something to say?

We have turned off comments, but you can see what folks had to say before we did so.

More from ALA

I am a creative.

A List Apart founder and web design OG Zeldman ponders the moments of inspiration, the hours of plodding, and the ultimate mystery at the heart of a creative career.
Career