Create a clickable accordion with CSS animation

  • Knowledge needed: Novice HTML, advanced CSS
  • Requires: Text editor, Firefox or WebKit browser
  • Project time: 30 minutes
  • Download source files

With CSS3, many things we used to build with JavaScript can now be done entirely in CSS. That doesn't mean we should always do it and actually, going for a pure CSS solution is often hacky. However, it's interesting to be aware of the possibilities CSS3 gives us in order to be able to choose the best method to develop our web pages.

In this article I’m going to show you how we can achieve a pretty nice accordion effect using nothing but plain old HTML and CSS. I'll mainly use the flexible box model and the target pseudo-class to build this thing. Let's dig in the code.

HTML markup

I'll use some dead simple HTML5 code for this accordion. Basically, each section of the accordion is a section element (makes sense doesn't it?) and contains a title and a paragraph.

  1. <div>
  2. <section>
  3. <h1>Introduction</h1>
  4. <p>Lorem ipsum dolor sit amet...</p>
  5. </section>
  6. <section>
  7. <h1>General information</h1>
  8. <p>Lorem ipsum dolor sit amet...</p>
  9. </section>
  10. <section>
  11. <h1>Contact us</h1>
  12. <p>Lorem ipsum dolor sit amet...</p>
  13. </section>
  14. </div>

Toggle behaviour

When I created this accordion, here's the behaviour I was trying to reach:

  • By default, the accordion is closed
  • When I click a section title, I want this section to expand and show its content
  • When a section opens, I want the previously opened section to close at the same time
  • I want the accordion to stay always centred, whatever the size of the content is
  • I want the accordion to be accessible with the keyboard
  • I want the sections to open and close with a sliding animation, not just a visibility toggle

While it may seem pretty ambitious at first sight, the whole thing is actually not that hard to reach thanks to the awesome flexbox model and the target pseudo-class.

The idea is to wrap the section titles in anchors that link to themselves. Since :target lets us select the targeted element, we'll also be allowed to select the element that follows it using the adjacent sibling selector. Let's try that. Here's the modified HTML markup:

  1. <div>
  2. <section>
  3. <a href="#intro" id="intro">
  4. <h1>Introduction</h1>
  5. </a>
  6. <p>Lorem ipsum dolor sit amet...</p>
  7. </section>
  8. <section>
  9. <a href="#info" id="info">
  10. <h1>General information</h1>
  11. </a>
  12. <p>Lorem ipsum dolor sit amet...</p>
  13. </section>
  14. <section>
  15. <a href="#contact" id="contact">
  16. <h1>Contact us</h1>
  17. </a>
  18. <p>Lorem ipsum dolor sit amet...</p>
  19. </section>
  20. </div>

And here's the incredibly simple CSS to get the very first toggle behaviour:

  1. p {
  2. display:none;
  3. }
  4. :target + p {
  5. display:block;
  6. }

Hooray, it works! Not the most beautiful accordion we've seen in the history so far though.

Add sexiness

I'll start by centring the accordion both horizontally and vertically. The flexbox model is the perfect positioning method for that kind of effect since it allows us to display an element always at the centre of a block (here: the page) without specifying any dimension. You can learn more about it with Peter Gasston's post The CSS3 Flexible Box model explained. I'll use the standard notations for this tutorial but don't forget to use the vendor prefixes in your final code.

  1. * {
  2. margin:0;
  3. padding:0;
  4. }
  5. html,body {
  6. height:100%;
  7. }
  8. body {
  9. display:box;
  10. box-orient:vertical;
  11. box-pack:center;
  12. box-align:center;
  13. /* For Firefox */ width:100%;
  14. }
  15. div {
  16. width:250px;
  17. }

Centred, but still pretty terrible visually. Adding more CSS sexiness:

  1. body {
  2. font:.7em/1.5 "lucida grande", arial, sans-serif;
  3. background:#f3faff;
  4. }
  5. div {
  6. background:#fff;
  7. border-radius:5px;
  8. box-shadow:0 1px 3px rgba(0,0,0,.3);
  9. }
  10. h1 {
  11. font-size:1em;
  12. }
  13. a {
  14. display:block;
  15. height:23px;
  16. line-height:23px;
  17. background:linear-gradient(#eee, #ccc);
  18. color:#333;
  19. text-decoration:none;
  20. text-align:center;
  21. text-shadow:0 1px 0 rgba(255,255,255,.5);
  22. border-bottom:1px solid #aaa;
  23. }
  24. #intro {
  25. border-radius:2px 2px 0 0;
  26. }
  27. #contact {
  28. border-radius:0 0 3px 3px;
  29. }
  30. a:hover, a:focus {
  31. opacity:.9;
  32. }
  33. a:active {
  34. background:linear-gradient(#ccc, #ddd);
  35. color:#000;
  36. }

Woot! It's looking both functional and sexy by now. Regarding my requirements, I'm almost there. Just missing the animated part actually. Bad news: adding those transitions isn't that easy.

The main problem is that you obviously can't perform transitions on a property such as display so you'll have to use numerical values. So, instead of hiding and showing the sections with display, I'll use the height property to switch from 0 to the content height.

Second bad news: you currently can't perform a transition between a numerical value and "auto". That means I'll have to specify a height for each section manually. As far as I can tell, this is something the W3C is aware of and they agree that it should be possible. I hope browser vendors will implement that: it would be so helpful. Here's the final code:

  1. * {
  2. margin:0;
  3. padding:0;
  4. }
  5. html,body {
  6. height:100%;
  7. }
  8. body {
  9. display:box;
  10. box-orient:vertical;
  11. box-pack:center;
  12. box-align:center;
  13. font:.7em/1.5 "lucida grande", arial, sans-serif;
  14. background:#f3faff;
  15. /* For Firefox */ width:100%;
  16. }
  17. div {
  18. width:250px;
  19. background:#fff;
  20. border-radius:5px;
  21. box-shadow:0 1px 3px rgba(0,0,0,.3);
  22. }
  23. p {
  24. height:0;
  25. padding:0 15px;
  26. overflow:hidden;
  27. transition:height .4s ease-out, padding .4s ease-out;
  28. }
  29. h1 {
  30. font-size:1em;
  31. }
  32. a {
  33. display:block;
  34. height:23px;
  35. line-height:23px;
  36. background:linear-gradient(#eee, #ccc);
  37. color:#333;
  38. text-decoration:none;
  39. text-align:center;
  40. text-shadow:0 1px 0 rgba(255,255,255,.5);
  41. border-bottom:1px solid #aaa;
  42. }
  43. #intro {
  44. border-radius:2px 2px 0 0;
  45. }
  46. #contact {
  47. border-radius:0 0 3px 3px;
  48. }
  49. a:hover, a:focus {
  50. opacity:.9;
  51. }
  52. a:active {
  53. background:linear-gradient(#ccc, #ddd);
  54. color:#000;
  55. }
  56. :target + p {
  57. padding:10px 15px;
  58. border-bottom:1px solid #ccc;
  59. }
  60. #intro:target + p {
  61. height:70px;
  62. }
  63. #info:target + p {
  64. height:230px;
  65. }
  66. #contact:target {
  67. border-radius:0;
  68. }
  69. #contact:target + p {
  70. height:180px;
  71. }

Here we are! Our accordion is now animated and fully functional. Here's a live preview of the final version.


As just mentioned above, the major drawback of this technique is the necessity to specify a numerical height for each section. You could obviously let JavaScript calculate it for you and apply it dynamically but heck, the goal was to make a pure CSS accordion! You could also just avoid those animations and use the first display technique and you'd be fine.

The second drawback is the use of those anchors linking to themselves. While they don't really hurt, you'll agree with me they don't look quite right either.

The third and final drawback is browser support. It works fine on Chrome, Safari and Firefox and reasonably well in Opera and IE10. However, the flexible box model is a new and changing spec, so expect some pretty big changes in the future…

It's always important to know of the possibilities and the limitations you're given by a spec. When aware of those techniques, you'll be aware to judge by yourself if it's appropriate to your project or if you should go a different way. Make sure to follow the evolution of those properties and as Andy Clarke would say: Get Hardboiled!