Prerequisite knowledge

HTML, CSS and some knowledge of new CSS3 features.

User level


Since the advent of CSS3 and the continuing improvement in browser support for new features, more developers are coming up with unique ways to add functionality to web pages without the use of JavaScript.
In the last couple of years, a number of developer techniques have sprung up using form elements to replace functionality normally done with JavaScript and, commonly, with a library like jQuery.
In this article, I run through some of these techniques and discuss the concepts involved. This should help you get a good overall understanding of how these tricks work, and may give you insight into creating something with pure CSS for one of your future web projects.

Why Use These Techniques?

There are a few reasons why these techniques might be beneficial to your code. First of all, any extra JavaScript makes our websites slower because you'd be adding more code and in some cases potentially an extra external resource for the page to load. Additionally, a CSS-only solution is much cleaner and easier to maintain. With good commenting, it should be fairly easy to track down how certain functionality is operating, should you ever need to make any necessary corrections or improvements.

Initial Warning!

The fact is, the techniques mentioned in this article may not be good options because of the semantic problems and accessibility challenges that arise when using form elements in a way not intended.
So although this article has definite experimental and educational benefits for those learning CSS, take into consideration any drawbacks that might occur before using these techniques extensively in any project. Of course, if you're able to ensure you can use these without accessibility problems, then by all means do so. But please be forewarned that they could be problematic.

Tab switcher with radio buttons

Many websites feature a "tab switcher". In brief, this is a single unit, or widget, sometimes appearing in the sidebar of a website, that has two or more content areas that the user can choose from. Only one of these content areas is visible at one time, and the options are displayed in the form of tabs at the top of the widget.
Most older tutorials and demos use list items, div elements, and jQuery to create this functionality. But in the past couple of years, a number of scripts and tutorials have begun to use radio buttons and some new CSS3 pseudo-classes to assist with the functionality of the tabs.
Let's run through how this might be done. Our markup can be divided into multiple sections. Each section might look something like the following markup:
<section class="tab"> <input type="radio" id="tab-one" name="tab-set" checked> <label for="tab-one">Tab #1</label> <div class="tab-content"> <p>First tab content goes here.</p> </div><!-- tab-content --> </section><!-- .tab (1) -->
This might be repeated three or four times. Then, we could have a wrapper with a class of tab-switcher holding all these sections together. The key parts of the markup are the input elements with type radio and their associated label elements.
Here is the example in JS Bin. Select CSS to see that code.
Throughout this article, vendor CSS3 prefixes are omitted for brevity. If you want to see this code in action, you can check out this JS Bin demo. Now let's consider the concepts involved in creating this tab switcher. Notice the demo is not using any JavaScript.
Hidden Radio Buttons
Although this example is using radio buttons, we don't actually see any radio buttons on the page. This is because the radio buttons are set to display: none in our CSS. But if the radio buttons aren't visible on the page, how do they function?
Well, notice the markup also includes a label element that's associated with each radio button using IDs and for attributes. Although the radio buttons are not visible or clickable, the associated label elements are. And if you are familiar with label elements, you know that browsers allow the labels to be clicked to select their associated form elements.
Thus, all we have to do is style our labels to look like tabs (I used rounded corners and a few other styles) and then target them using the :checked pseudo-class along with the general sibling selector (the tilde character, ~).
The general sibling selector lets us target both the content section of the selected tab, as well as the label element. Using :checked in combination with the sibling selector lets us target only the parts of the tab switcher that are currently selected, or active.
With some absolute positioning along with z-index, we then can make the chosen section visible. In the demo, I also added some CSS3 transitions to prevent the switching from occurring too quickly.
Concepts to Remember
To summarize this technique, here are the concepts to keep in mind:
  • The radio buttons are not visible on the page.
  • Any label element can be associated with any form element, allowing the form element to be selected via the label without the form element being visible.
  • The :checked pseudo-class in combination with the general sibling selector lets us target elements associated with the selected form element.

Accordion slider with checkboxes

Once again, we create a commonly-used web page widget while avoiding any JavaScript. This time, we create a vertical accordion slider.
For our markup, similar to the tab switcher, we divide each accordion section using HTML5 section elements. We have a wrapper with a class of accordion-wrap enclosing all five sections. Each section looks something like the following markup:
<section class="accordion-item"> <input type="checkbox" id="accordion-one" name="accordion-group" checked> <label for="accordion-one">Accordion Section #1</label> <div class="accordion-content"> <p>content here... </p> </div><!-- .accordion-content --> </section><!-- .accordion-item -->
The actual example has five of these, but I'm showing you just one for brevity. Again, the key parts of the code are the checkbox input element and its associated label element.
Our CSS is similar to the previous example, and once again we utilize the :checked pseudo-class along with the general sibling selector. Here is the example in JS Bin. Select CSS to see that code.
Using max-height
This example uses checkboxes, which, unlike radio buttons, allow multiple choices to be selected. This means the different items in the accordion can all appear expanded at the same time.
The tabs example used a combination of opacity and z-index to show the selected item. But this time, we're using the max-height property. Any unchecked item starts out with a max-height value of 0, along with overflow: hidden, so it's not visible. When an item is checked, we set the max-height to any large number that exceeds the height value of the largest item.
We use max-height and not the height property, because although height can be animated via CSS3, you cannot animate from 0 to auto. So we have to choose an arbitrary max-height value, to ensure the height of the element doesn't extend far past the actual content it holds. (Credit to Lea Verou for this technique.)
Concepts to Remember
Here are the main points to remember in this example:
  • Checkboxes allow multiple elements to be selected, thus mimicking what we might do with JavaScript.
  • Again, we use labels to trigger the selection, but keep the checkboxes hidden.
  • We show the content of each accordion item by animating from max-height: 0 to a large max-height value that exceeds the tallest item.
  • We use a different transition-duration value for the checked state compared to the unchecked state, which is one of the flexibilities of CSS3 transitions.

Revealing a text field with focus

This final technique is simply a form button that, when clicked, animates to reveal a text input element. Let's look at the markup first, which is quite simple:
<input type="text" class="txt" id="textfield"> <label for="textfield">Click to Type</label>
In this example, we're using nothing but a single text field with an associated label element, as in the following JS Bin example. Select CSS to see the related code.
Using :focus
In this example, we're using the :focus pseudo-class to make the form field visible. The label element is styled to look like a button. It's positioned to appear on top of the text field, so the text field is not immediately visible.
We then use our super-handy sibling selector to animate the left property on the label element. This reveals the text field by moving the label element out of the way.
Take a look at this code in action at this JS Bin demo.
Concepts to Remember
To summarize what we did in this last example:
  • The :focus pseudo-class is used to detect if the user has clicked the label element, thus adding focus to the text field.
  • The label and the form element are positioned absolutely and stacked using z-index.
  • A CSS transition animates the left property to move the label out of the way when the user is ready to type.
  • The general sibling selector is once again used to target an element adjacent to the selected form field.

Bugs, problems, and warnings

As already mentioned, these techniques should be considered carefully before being used. First of all, older versions of WebKit had a bug that didn't allow the :checked pseudo-class to work in conjunction with a sibling selector. Fortunately, not many people are using older versions of WebKit so this is less of a problem today.
The other problem occurs in WebKit on iOS5 or below, which doesn't allow a checkbox's state to change by touching a label element. A workaround for this is to add an empty inline onclick handler to all label elements, as I did in the demos for this post. Additionally, iOS has slow reaction to label clicks that trigger input state changes.
A more significant problem, however, is the fact that the markup for these elements could cause problems with assistive technology devices like screen readers. This accessibility issue should be taken into consideration before implementing a method like these.
There has been discussion about the possibility of adding this type of behavior natively to elements with pure CSS, to prevent these bugs and accessibility problems, but that would be much further down the road and would not be implemented in all browsers for a very long time.

Where to go from here

To deal with some of the cross-browser problems, you can attempt to use a selector polyfill like Selectivizr to fill in any gaps, but you might end up with buggy or unpredictable results, and, unless you provide an animation polyfill, transitions are missing.
As mentioned in the introduction, if you take care to avoid any accessibility problems, these techniques can be useful. By avoiding JavaScript, your pages load faster, the functionality is  less likely to have any lag, and with a few simple comments in your HTML or CSS your code is easier to maintain.
Much of the credit and inspiration for this post is due to work done by a number of developers. Be sure to check out the articles and demos shown below if you want to delve more into this topic.