I’m Dudley Storey, the author of Pro CSS3 Animation. This is my blog, where I talk about web design and development with , and . To receive more information, including news, updates, and tips, you should follow me on Twitter or add me on Google+.

web developer guide

my books

Book cover of Pro CSS3 AnimationPro CSS3 Animation, Apress, 2013

my projects

CSSslidy: an auto-generated #RWD image slider. 1.5K of JS, no JQuery. Drop in images, add a line of CSS. Done.

tipster.ioAutomatically provides local tipping customs and percentages for services anywhere.


HTML5 Window Toggle Events In Pure CSS3

css / navigation

Estimated reading time: 5 minutes, 30 seconds

In the past, animated expansion and contraction of web page elements in response to user clicks has been tied to , particularly JQuery and other frameworks. I suspect this is partly due to web developer’s unfamiliarity with CSS3, the ubiquity of JQuery, desire for support in older versions of IE, and confusion about what to use: we know we can use :hover to initiate such events, but :hover is not a click. :focus and :active might work, but then how do you get “back” from that state to close the element again?

Before I proceed it should be noted that I’m hardly the first to explore this area: notably, Corey Mwamba wrote a particularly good article on the subject over at the excellent Opera developer site. What follows are merely my thoughts, with a little form element accessibility trickiness, and what I think is a simpler and more elegant solution.

It seems obvious that in order to toggle a window event we need an HTML element that has distinct “on” and “off” states. Two immediately spring to mind: radio buttons and checkboxes. In , they’re valid in almost any context. I’ll place a checkbox element to act as our toggle switch inside a div with a class of window. Directly underneath the checkbox, we’ll place the content that is going to be shown and hidden. Assuming that this could be a lot of content, I’ll place it inside its own <div>. So the markup will look like this:

<div class=window>
<input type=checkbox class=toggle>
<div><p>Here’s my content. Beep Boop Blurp.</p></div>

Next, I’ll add some CSS. The outer div will be styled as our window container element:

div.window { color: white; width: 220px; padding: .42rem; border-radius: 5px; background: #c6a24b; margin: 1rem; text-align: center; }

The inner div will initially be hidden by the fact that we’re going to set its height to 0 and overflow property to hidden, so that content does not “spill out” of the div and become visible:

div.window div { height: 0px; margin: .2rem; overflow: hidden; }

That pulls our outer div closed. Now to initiate our expansion. First, we’re going to change the selector used above to be a little more precise:

input.toggle ~ div { height: 0px; margin: .2rem; overflow: hidden; }

This is the sibling selector. It will provide the same result in your browser as the code above. If you’re saying to yourself “Why not use an adjacent selector instead?” you’re right... but we’ll see why a sibling selector is better choice in a moment.

Now we can get to our open / close routine. The CSS status for a checked input is, not surprisingly, :checked… so we’ll use that to set the height of the inner div to show all of its contents.

input.toggle:checked ~ div { height: 180px; }

The neat part of this is the fact that we don’t have to worry about anything else: these two lines of code make the whole system work, and anything else we choose to add is an embellishment. Turning on the checkbox expands the inner div, which in turn expands the outer window.

“But I Don’t Want A Checkbox!”

Here’s the trick. We’re going to add a <label> after the checkbox. And we’re going to use the value of the label’s for attribute to link it to the checkbox, which will have a matching id. No CSS; this is all markup:

<div class=window>
<input type=checkbox class=toggle id=punch>
<label for=punch>Punch It, Chewie<label>
<div><p>Here’s my content. Beep Boop Blurp.</p></div>

With the for attribute applied, the label acts as an alternate interface for the checkbox: clicking on the label is exactly the same as using the checkbox directly. Adding label elements has always been a best practice for ; as is so often the case, knowing and using even a little accessibility opens up a world of opportunity for all users.

(It should also be clearer why we used the sibling selector: placing the label between the checkbox and the inner div in the markup renders an adjacent selector null and void).

You can probably anticipate where we’re headed next. We can completely turn off the checkbox, and leave just the label:

input.toggle { display: none; }

The result still works!

We can improve the default appearance of the label:

div.window label { display: block; background: #660b0b; border-radius: 5px; padding: .6rem; }

We should also make it clear to the user that the label is an active interface:

div.window label:hover, div.window label:focus { cursor: pointer; background: #311; }

It would also be nice to give the label a different look when the content is expanded:

input.toggle:checked + label { background: red; }

Even though we no longer see the checkbox, its status can still influence the appearance of the label (and this is why the checkbox appears in the code before the label rather than after: we can’t yet do “reverse” selections in CSS).

Using a checkbox also makes it really easy to set the window to an open state by default: just place the checked attribute in the input:

<div class=window>
<input type=checkbox checked class=toggle id=punch>

The label will continue to work: now clicking it the first time will close the window.

“Make It Go Sproing!”

So far there’s no animation: the window div simply snaps open and closed. We’ll smooth this motion and add a little bit of anticipation and follow-through.

What this means is that when the animation begins, the affected element will recoil slightly: think of a pitcher winding up in anticipation of throwing a speedball in a game of baseball, his body curving backwards before the pitch. At the end of the animation the element will overshoot its mark slightly before recovering to its final state. Rendered as prefix-free properties, the code would look something like this:

input.toggle ~ div { height: 0px; margin: .2rem; overflow: hidden; transition: .6s all cubic-bezier(0.730, -0.485, 0.145, 1.620); }


This technique has many possible applications, and just one downside: we must know the dimensions of the inner element when it is closed and open. You can define the element's height and width using whatever CSS unit you like, but we must state the dimensions explicitly. If the size of the content changes regularly, this approach will not be practical. That’s the reason I still use JQuery on the sidebar “Web Developer Guide” navigation for this site, rather than pure CSS: having to change the height of elements each time I added something to the navigation would be burdensome. But if the content you wish to show and hide does not change often, or at all, this animation solution may be ideal. Play with this code on CodePen

comments powered by Disqus

This site helps millions of visitors while remaining ad-free. For less than the price of a cup of coffee, you can help pay for bandwidth and server costs while encouraging further articles.