0

I'm looking to make a tab system like jQuery tabs, where users can between toggle different panels to view different content:

enter image description here

However, I need to accomplish this without the use of javascript, so that users without javascript enabled can easily use the site. Furthermore, I'd like to avoid navigating to different static html pages, each with a different style corresponding to the "tab." What's a good way to approach this?

royhowie
  • 11,075
  • 14
  • 50
  • 67
  • 3
    @T.J.Crowder What about duplicates? While the answer isn't nearly as detailed/specific, a quick search brings up this: http://stackoverflow.com/a/4937615/1253479 (from 2011). I could understand a Q/A from OP, if the question was "[unique](http://stackoverflow.com/q/18365315/1253479)", but I don't see the benefit of this specific post. – Jack Sep 20 '14 at 00:43
  • @royhowie: Odd, that *used* to work. – T.J. Crowder Sep 20 '14 at 06:49

1 Answers1

14

An easy way to implement CSS-only tabs is to use radio buttons!

The key is to style labels that are attached to a respective button. The radio buttons themselves are hidden, with a little absolute positioning, off the side of the screen.

The basic html structure is:

div#holder
    input[type="radio"]
    div.content-holder
        label
        div.tab-content (all your tab content goes here)
    input[type="radio"]
    ... keep repeating

The key is in the selectors. We are going to style the input[type="radio"] buttons with

input[type="radio"] {
    position: absolute;
    left: -100%;
    top: -100%;
    height: 0;
    display: none;
}

This hoists them off the side of the screen, as mentioned above. But how do we click them then? Fortunately, if you target a label, it can click the input for you!

<label for="radioInputId1">tab title</label>

Then we style the actual labels (I'm going to leave out the aesthetic styling for brevity):

input[type="radio"] + div.content-holder > label {
    display: inline-block;
    float: left;
    height: 35px;
    width: 33%; /* or whatever width you want */
}

Now our labels should look like "tabs" at the top of the div#holder. But what about all that content? Well, we want it to all be hidden by default, so we can target it with the following selector:

input[type="radio"] + div.content-holder > div.tab-content {
    display: none;
    position: absolute;
    top: 65px;        /* this depends on your label height */
    width: 100%;
}

The above CSS is the minimal CSS required to get it working. Everything other than display: none; is what you will see when the div is actually displayed. But this shows nothing in the tabs, so… now what?

input[type="radio"]:checked + div.content-holder > div.tab-content {
    display: block;
}

The reason the above works is because of the :checked pseudo-class. Since the labels are attached to a specific radio button, they trigger :checked on click. This automatically turns all the other radio buttons off. Because we have have wrapped everything within a div.content-holder, we can use the next sibling CSS selector, +, to make sure we only target a specific tab. (Try using ~ and see what happens!)

Here's a fiddle, for those of you who don't like stack snippets, and here's a stack snippet, for those of you who do:

#holder {
  border: solid 1px black;
  display: block;
  height: 500px;
  position: relative;
  width: 600px;
}
p {
  margin: 5px 0 0 5px;
}
input[type="radio"] {
  display: none;
  height: 0;
  left: -100%;
  position: absolute;
  top: -100%;
}
input[type="radio"] + div.content-holder > label {
  background-color: #7BE;
  border-radius: 2px;
  color: #333;
  display: inline-block;
  float: left;
  height: 35px;
  margin: 5px 0 0 2px;
  padding: 15px 0 0 0;
  text-align: center;
  width: 33%;
}
input[type="radio"] + div.content-holder > div {
  display: none;
  position: absolute;
  text-align: center;
  top: 65px;
  width: 100%;
}
input[type="radio"]:checked + div.content-holder > div {
  display: block;
}
input[type="radio"]:checked + div.content-holder > label {
  background-color: #B1CF6F;
}
img {
  left: 0;
  margin: 15px auto auto auto;
  position: absolute;
  right: 0;
}
<div id="holder">
  <input type="radio" name="tabs" value="1" id="check1" checked>
  <div class="content-holder">
    <label for="check1">one</label>
    <div class="tab-content">
      <p>All my content for the first tab goes here.</p>
    </div>
  </div>
  <input type="radio" name="tabs" value="2" id="check2">
  <div class="content-holder">
    <label for="check2">two</label>
    <div class="tab-content">
      <h2>You can put whatever you want in your tabs!</h2>
      <p>Any content, anywhere!</p>
      <p>
        Remember, though, they're absolutely positioned.
        This means they position themselves relative to
        their parent, div#holder, which is relatively positioned
      </p>
    </div>
  </div>
  <input type="radio" name="tabs" value="3" id="check3">
  <div class="content-holder">
    <label for="check3">three</label>
    <div class="tab-content">
      <p>
        And maybe I want a picture of a nice cat in my third tab!
      </p>
      <img src="https://i.stack.imgur.com/Bgaea.jpg">
    </div>
  </div>
</div>

The tabs I styled are really rather basic. If you want them to "wrap" into the content, you can do that with a little extra CSS legwork.

royhowie
  • 11,075
  • 14
  • 50
  • 67