The idea of “CSS Tabs” has been around for a long time. If you Google it, a lot of what you get is styled CSS tabs, but less stuff on the building of an actually functional tabbed area. At least, functional in the sense as we think of tabbed areas today: click a tab, see a new content area with no page refresh.
Tackling functional CSS tabs has less of a deep history. Brad Kemper was messing around with it in 2008 trying to utilize the :checked
pseudo selector with radio buttons and adjacent sibling combinators. This is a really cool technique that can be utilized to do things like an expand/contract tree-style menu or visually replace form elements with graphics (pioneered by Ryan Seddon).
I personally tried messing with functional tabs a while back, and came up with seven different ways to do it. Most of them centered around the use of the :target
pseudo-class selector and most of those techniques sucked. One was OK. They all had one major flaw and that was that URL hashes needed to be used, which “jumps” the page down to the element with the matching ID, and that is totally unexpected, jerky, and just a bad overall experience.
Working with the radio-button/:checked
technique is way better, but there was a long-standing WebKit bug that prevented pseudo-class selectors and adjacent sibling combinators from working together. Good news! That’s fixed as of stable browser releases Safari 5.1 and Chrome 13.
So let’s get this thing done the :checked
way, which I think is the cleanest way to do it for now and for the next few years.
HTML Structure
A wrapper for the whole group, then each tab is a div that contains the radio button (for the functionality), a label (the tab), and a content div.
<div class="tabs">
<div class="tab">
<input type="radio" id="tab-1" name="tab-group-1" checked>
<label for="tab-1">Tab One</label>
<div class="content">
stuff
</div>
</div>
<div class="tab">
<input type="radio" id="tab-2" name="tab-group-1">
<label for="tab-2">Tab Two</label>
<div class="content">
stuff
</div>
</div>
<div class="tab">
<input type="radio" id="tab-3" name="tab-group-1">
<label for="tab-3">Tab Three</label>
<div class="content">
stuff
</div>
</div>
</div>
CSS Layout
Basically:
- Hide the radio buttons (we don’t need to see them, we just need them to be checked or unchecked).
- Float the tabs so the labels fall into a row-of-links structure.
- Absolutely position the content areas exactly on top of each other.
- When a radio button is :checked, make the adjacent content area sit on top with z-index (visually revealing it and hiding the others).
.tabs {
position: relative;
min-height: 200px; /* This part sucks */
clear: both;
margin: 25px 0;
}
.tab {
float: left;
}
.tab label {
background: #eee;
padding: 10px;
border: 1px solid #ccc;
margin-left: -1px;
position: relative;
left: 1px;
}
.tab [type=radio] {
display: none;
}
.content {
position: absolute;
top: 28px;
left: 0;
background: white;
right: 0;
bottom: 0;
padding: 20px;
border: 1px solid #ccc;
}
[type=radio]:checked ~ label {
background: white;
border-bottom: 1px solid white;
z-index: 2;
}
[type=radio]:checked ~ label ~ .content {
z-index: 1;
}
This is pretty darn lightweight CSS and it’s totally expandable to any number of tabs just by adding more “tab” divs in the HTML.
JavaScript
There isn’t any, captain!
Why this way is awesome
- It doesn’t use :target so no page-jump-suck and back-button-hijacking.
- It’s accessible. The weird radio buttons are hidden with display: none so screen readers won’t see them and be confused (presumably) and none of the actual content is hidden with display: none;
- It works in Safari 5.1+, Chrome 13+, Firefox 3.6+, Opera 10+, and IE 9+. Maybe a little deeper on Chrome, Firefox, and Opera, but Safari and IE are definitely correct.
Why this way isn’t awesome
- It requires a set height to the tabbed area which sucks. I feel like there may be a way to fix this though I just haven’t quite gotten it yet.
- The radio button thing is a bit hacky.
- It doesn’t have the deepest browser support (IE 9 is kinda a lot to ask). If you need deeper go JavaScript.
Get it, Got it, Good
Just for funsies, I added some transitions to the tabs in the live demo.
See the Pen
Functional CSS Tabs by Chris Coyier (@chriscoyier)
on CodePen.
The Awesome Theoretical Future – display: stack;
As I mentioned, the radio button thing is a little hacky. It’s cool that we are now able to do this and the experience is pretty good, but the way we have to code isn’t elegant or intuitive. Tab Atkins, who writes CSS specs, thinks display: stack;
is probably the future for a tabbed user interface through CSS.
“Tab Atkins”?? Surely you jest!
Yeah he’s taking over for Radio Vasquez
CHRIS NEVER JESTS.
thank you for Chris,
this proved very helpful in my site as i coulndn’t use the Js method,
but its not working on mobile, did you experience this as well, and do you have any solution for this issue,
thanks again,
Hi Chris,
I just tested the demo and the page header flickers when changing tabs, weird! (it’s not a complain)
I’m on OSX Safari 5.1 (in Chrome it doesn’t happen)
Does that for me in Safari (and not Chrome) as well. It’s related to the transitions, so just ditch that if it’s a problem.
Using Chrome 13 on a Mac running Leopard and the whole page flickers black after every transition. Strange that no one else has this problem so far…
Really simple/cool demonstration, in any case! I may use something like this very soon! (w/ js, though)
Old stuff, but it solves a few problems in old browsers…
http://tjkdesign.com/articles/spry/tabbed-panels_better_version.asp
I took Jacob Dubail’s awesome idea and built a fading gallery http://jsfiddle.net/chrisbuttery/fWNnr/18/
The problem I see why this it doesn’t work on iOS you click the tabs and nothing happens
I love seeing the all-CSS solutions to different design obstacles. Doesn’t mean I always go for them. These days, for tabs I usually end up with a CSS and JavaScript method where all the content is visible (often stacked) if JavaScript is unavailable.
Anyway, thanks for the work. It gets the mind moving :)
CSS pretty much always sucks at usability when it comes to this sort of thing. Personally I don’t understand this fascination with using a visual display language to change functionality and behaviour on a web page. That is exactly what Javascript is intended for.
I know I know, testing the limits of the language is great. But I often wonder, what’s the point?
+1
I second that although it is pretty “cool” trying to make this happen with CSS ;)
I agree with you in principle. I think sometimes this blog has been guilty of seeking out pure CSS solutions just because it can be done. However, aren’t tabs more of a “visual display” method than a “functionality” or a “behavior?” They are so basic and ubiquitous, it seems like it shouldn’t be necessary to bring in javascript.
https://css-tricks.com/13758-functional-css-tabs-revisited/
@jon It’s a fair question, but I don’t think so. The ubiquity or otherwise of an interface element shouldn’t affect whether it should be implemented in one layer or another. I think the interactive aspect of tabs is always present, regardless of how common a visual element they are, and that interaction should still be handled by the behaviour layer, while it is still a separate one.
Very nice. I’ve always wanted to do this kind of thing, but I just couldn’t find a way I liked it.
Just need to add an iOS-specific menu that changes page completely.
Thanks, Chris! What I would add to this is
cursor: pointer
on tab hover :)In firefox the left tab shows a ‘missing’ pixel when selected. This is cause the white bottom border is active for the entire tabwidth. Setting border-bottom to zero has resolved this issue for me.
[type=radio]:checked ~ label {
background-color:#FFFFFF;
border-bottom:0px; /* minor firefox glitch – left bottom of first tab has a missing borderpixel */
z-index:2;
}
Ben Cavens, then it shows the bottom border from the
.tab label
selector in at least Opera and Chrome (didn’t test it in IE and Safari). Not good!Sorry, my mistake, it doesn’t :)
This is very cool.
It seems to me that for tabs, though, you actually want the fragment identifier (
#tab1
,#tab2
, etc.) for linking, and so a solution using the same technique Jenna Smith (amongst others) has used for JS-free lightboxes (like this one) should be possible. And would have the advantage of much wider browser support, right the way down to IE6 (9.7% worldwide market — but falling fast, thank the diety). Downside there (and a big one) is that it would probably only be useful for the main tabs on a page, since it does take over the fragment identifier and probably only works well when the page isn’t scrolled.On Chrome 15 when changing from the demon cat tab to another tab, it transitions to the new tab, then flashes the cat picture, then shows the real content for that cat tab again. Probably best to remove the animation – for anyone – see as how there’s a bug with it in multiple browsers.
Very Nice,
I like the simplicity of this way of doing it.
There are some fairly easy enhancements I can see that can be added with Javascript with this as a base.
Not working in chrome 9 on windows…
The browser support is listed in the article above. Chrome auto-updates and is stable in v13. Do you turn auto updating off? I didn’t even know that was possible.
Could you make a video of it; i mean to explain all those selectors
Thanks this Chris. I was just starting to implement a tab solution for my webpage and this works perfectly. Thanks for the your articles and teaching people like me how to successfully modify websites. Everyday I feel more and more like a designer thanks to good articles like this. What im am very happy about is the fact that it uses no javascript. Thanks A Bunch
Not working for me on Chrome XP. It works with Chrome on Ubuntu Linux.
Same for me.
But this is probably happening because I run XP on a virtual machine.
I might be able to use this for a project I’m working on, I’ll show it off if I do.
Here one more demo http://jsfiddle.net/laukstein/fWNnr/39/
Wow without Javascript! That’s the way :)
Clever idea with checkboxes :)
But this has problem if tabs content have different heigth.
So I made own version without
position:absolute
(andclear: both, float:left
) and html structure is not so nice as yours.I use
:checked + * + label
so more tabs require more+ * +
:(Here is code http://jsfiddle.net/bUjQm/18/
Previous version won’t work with newest Chrome 13 and Safari 5.1 but I found solution – and after that I read your news about Webkit bug :)
One of my Webkit fix:
body ~ :checked {}
(or
anything ~ any_sibling_to_+_*_+ {/*empty*/}
)New code: http://jsfiddle.net/bUjQm/21/
the width of the tabs container can be set to anyting?
what about overflows?
This primarily works because clicking on
, as a part of browser default behaviour, checks the invisible radio-box automatically.
The unexpected space is actually
<label>
.I’m *so* using this in my personal website redesign. Thanks much!
Chris, this is super stuff. Keep up the good work! :)
Love this, but wish I could rig up some jQuery/JavaScript to cycle through the tabs every 30 seconds or so…
I used It with checkboxes, to make a Tree file browser.
show the subtree if checked.
and the brilant thing Is that the checkboxes remember their value even after they are hiden.
great CSS-Trick , thanks
Very nice and easy way to integrate sliding content in websites, just one thing if we could show hand cursor instead of arrow on tabs mouse over. I didnt try it but hope it would be possible
Doesn’t look like it works on iPad. Shucks.
I’ve done some work before on getting tabs to work with more semantic markup, i.e. by grouping the headers and content “properly” like you do in your example: http://fittopage.org/2010/05/tabs-done-right/
My solution still relies on JavaScript, but that has the added benefit of being cross-browser right now.
Added a dirty bit of extra HTML and a touch of animation to make these little tinkers collapsable drop downs. Have a play!
Was so excited to get play with this I forgot the lack of IOS support which is unfortunate :(
http://jsfiddle.net/paullferguson/Sv54G/3/
I’ve wanted to have tabs with text below, AND content (slideshows) ABOVE them, related to the text content IN the tabs’ panels. Don’t have the js/jq skills to write it, and the plugins and such I’ve tried haven’t worked out.
Came across this (couple of years old) string tonight, and… http://jsfiddle.net/pixelsound/bwarQ/3/
I think this will work!
-I’ve added the “above” content’s div in the tab, absolute positioned above it… (text placeholder for now – when this is a slideshow, it will fade in/out with the tab panel content, replacing the previously running slides)
-The relative positioning/height of whole tabs div will push footer or content below down nicely.
-I think that with a little media query action, I’ll get this collapsing to an accordion for small/mobile screens.
-I also think that the iOS support issue has been cleared up since 2011 (?)…
THANK YOU!THANK YOU!THANK YOU!
hey Chris!
Awesome stuff- a suggestion (although I haven’t tried it out) – what about placing the unseen tabs below the body (which should have a background on it) with z-index: -1; – that *should* solve the height issues?
one thing – I’m using chrome 14 – and I see the radio buttons? and the tabs don’t work – the highlight changes but the content stays on tab 2.. :/
wait.. my bad – was looking at the wrong demo there I think .. Your’s does work :D
Is there any way of making this work for IE8 without Javascript? I’m working for a client at the moment who would prefer the entire website to be coded javascript free. They use IE8 as their primary browser (they have lots of security issues).
Probably not around to answer this but I have found this code pretty handy, mostly easy for me to get to work how I need it…except for the Tabs themselves. I can’t seem to figure out how to resize the tabs, I may be reading something wrong since I am relatively new at HTML and Dohtml. The issue I am having is that when I type something; such as a long name in a tab, the tab stretches for the name. What I’d like to do; is have the name layered, as in first name on top and last name under it. I’ve tried just adding in a between names and that didn’t do anything but mess up the way the tab looked. I’ve tried adding in a height to the tabs as in height or line height and I couldn’t get that to work either. Could totally be doing this wrong or in the wrong place though….
@AJ – To adjust the height of the tab (as in, the part you click that says “Tab1…2…3… etc”), you need to add a “display:block” line to the “.tab label” class, then add height and width attributes as normal. I think. And I don’t guarantee that won’t mess everything else up.
This is a useful piece of CSS. I like it.
Although pure CSS solutions to problems may look indulgent and pointless (some solutions are), one good reason for CSS tabs is within eBay listings. You’re not allowed to use JS in your listings, so CSS is my best friend in that respect. I’ve foregone CSS tabbing in my eBay listings until now due to cross-browser compatibility issues.
But alas, whilst the above solution does better than my previous attempts (achieving the same results far more elegantly), Internet Explorer still refuses to play ball. But apparently, IE is perfect and it’s most of the world’s other major browsers that have a problem. Obviously – I mean what do Mozilla, Google, Opera, Apple et. al. know? Who needs a global standard anyway?
And what self-respecting web monkey doesn’t enjoy coding a totally separate solution for IE every single frickin’ time they want to do something?
OK, you found me out. I only came here to rant about Microsoft. Enjoy your serendipitous piece of information, AJ.
:-D
For information’s sake, the fix to get this to work on older versions of iOS is with an emtpy click handler, whcih you can apply to all Labels using this Javascript code:
That solution is not working on IE8, have anyone the resolution for IE?
Is there a way that I can have the tabs controll a content area befor the tabs and the content below the tabs?
Thanks,
Aaron
I’d like to know this, too! I’m looking to have tab selection trigger a change in a javascript slideshow that is outside the tabs’ content panels.
This is a very good tabs plug-in that I’ve been testing:
https://github.com/jellekralt/Responsive-Tabs
…but I’m running into the variable height problem as well as wanting tab selection to determine a switch in the slideshow.
for variable height problem, I’m going to try some of Vesa’s solution (see below; http://codepen.io/Merri/pen/bytea )
Have you found a solution, Aaron? I’m new to javascript, but not shying away from it to solve this tab triggering change…
Help, anyone?
I spent some time with these tabs to fix the variable height issue, which was an interesting issue to solve. I then also got interested in fixing it to work in IE8 and below. So that means added JavaScript. However I kept the JS to a minimum and instead let CSS do the heavy lifting in IE8, and in IE7 and even in IE6.
I even added some responsive into it with a simple media query.
http://codepen.io/Merri/details/bytea
I’d say the end result is now quite a well thought CSS based tabs. Adding the fallback support did also add more classes to the HTML to avoid issues in using inputboxes and labels inside, but that shouldn’t be that big of an issue to anyone really.
The only unresolved issue here is the linkability to content in tabs, which can be a requirement in real life. Linking to tabs via hashes is done by many, but I haven’t seen many solutions that would also open the correct tab if content inside the tab is linked to. For that I have a different solution:
http://codepen.io/Merri/details/qsdza
Which is IE8+ only and in need of some code cleanup, but is quite solid in what it does.
Vesa – Your code is awesome, thank you! The one complaint is that if you have this tabbed setup somewhere lower on the page, it scrolls the browser up in Chrome.
I’ll probably try to compensate for that in JS (going to use it in our WordPress plugin and gave credit to you), but if you know of a better way, I’m all ears!
Great post and great discussion here.
This looks great – giving it a try…
any thoughts re: Aaron’s question above – to have tab selection effect an element outside the tab’s content? I’d like tab selection to trigger a change in a javascript slideshow that is outside the tab div. Javascript solution? Any help greatly appreciated!
Thanks,
AK
I would love to know how to make the tabs fixed width and have the text inside the tab wrap. (pretty much what AJ asked)
Otherwise it is a lovely implementation.
O
anyone know how to work in ie8?
Thanks
Nice work, but it’s not working on my tablet. I’ve been trying to add some javascript to make it work, but I’m stuck. This code seems to work a little bit, but it requires the user to click multiple times on the tabs to make the toggle function show the content as desired.
Would anyone with more javascaript knowledge than me please take a look?
And the HTML:
and
So, what I’m trying to do is using the toggle effect to add and remove the tab content. Been stuck with this for four days, so any help would be deeply appreciated… :)
Aaaaand the HTML. I added this onclick event to the input type=”radio” tags.
To clarify,
This is the onclick code I have added to the tab input links:
The first tab:
onclick=”var b = toggleAdd(‘tabc-2’); if (b) toggleRemove(tabc-1); return b;”
and the second tab:
onclick=”var b = toggleAdd(‘tabc-1′); if (b) toggleRemove(tabc-2); return b;”
It kinda’ works, but the user has to click one time on the tab where the content should be removed (toggleRemove), and then two times on the tab he wants to see (toggleAdd). What am I doing wrong? :/
The “min-height: 200px;” can be easily removed with css for the same height of columns. You need to add display: table to the parent element of those columns and display: table-cell; to its children:
I have to correct this (sorry): The example is NOT accessible. This is just because you can not control the tabs via keyboard. The Problem lies here:
.tab [type=radio] {
display: none;
}
This makes the radios “invisible” for the keyboard as well. Better would be to do somthing like this:
.tab [type=radio] {
position: absolute !important;
height: 1px; width: 1px;
overflow: hidden;
clip: rect(1px 1px 1px 1px); /* IE6, IE7 */
clip: rect(1px, 1px, 1px, 1px);
}
Hi great work – I want to use this code with flexible hight because I am not able to use more jquerey stuff to make sure that the site is work well. Acutally your code is working fine for me, but I am missing the flexible hight. Can you give advice to add this feature to your code above?
Alex
Worst script ever! The tab content doesn’t expand! He has fixed height!
Flexbox powered approach from Klass Moerman:
https://kmoerman.github.io/css-tabs/