The trick with using hidden checkboxes/radio buttons, the :checked pseudo class selector, and adjacent sibling (~) combinators really enables some neat functional possibility with pure CSS1. If you need a primer, we recently used this to build a functional tabbed area.

Let's exploit it again to build "fold out popups". That is, links/buttons you can click to reveal tooltip-like rich HTML popup content.

View Demo   Download Files

HTML Structure

We need a checkbox for the on/off clickable functionality (don't worry, it's hidden). Then right after it is a label. Like any good label, the for attribute matches the inputs id attribute. That way we can click it to toggle the checkbox.

The label contains within it a number of spans. The .box span is the container for the rich HTML popup. In other words, put whatever you want in there. Well, whatever you want that are inline elements. It's tempting to use a div and header tags and stuff, but some browsers don't dig that and will render those things outside of the label instead of within. Making the .box outside of the label wouldn't be too big of a deal, as we could chain ~ selectors to get it to hide/show anyway, but having them within means we'll be able to position the popup relative to the position of the label, like a "popup" should.

<input type="checkbox" id="popup" class="popUpControl">
<label for="popup">
   Click me!
   <span class="box">
       Super cool popup content

CSS Basics

Our goal with the .box is to absolutely position it relative to the label it's within, so it pops up attached to it. By default, it will be hidden by having zero opacity. We'll also scale it down to nothing, skew it, and add transitions. Then when we reveal it by way of the checkbox becoming checked, we'll scale it back up, remove the skew, and make it visible by bringing the opacity back up to 1.

.box {
	position: absolute;
	left: 0;
	top: 100%;
	z-index: 100;
	/* Prevent some white flashing in Safari 5.1 */
	-webkit-backface-visibility: hidden;

	-moz-border-radius:    20px; 
	-webkit-border-radius: 20px; 
	border-radius:         20px; 
	width: 260px; 
	padding: 20px;
	opacity: 0;
	-webkit-transform: scale(0) skew(50deg);
	-moz-transform:    scale(0) skew(50deg);
	-ms-transform:     scale(0) skew(50deg);
	-o-transform:      scale(0) skew(50deg);
	-webkit-transform-origin: 0px -30px;
	-moz-transform-origin:    0px -30px;
	-ms-transform-origin:     0px -30px;
	-o-transform-origin:      0px -30px;
	-webkit-transition: -webkit-transform ease-out .35s, opacity ease-out .4s;
	-moz-transition:    -moz-transform    ease-out .35s, opacity ease-out .4s;
	-ms-transition:     -ms-transform     ease-out .35s, opacity ease-out .4s;
	-o-transition:      -o-transform      ease-out .35s, opacity ease-out .4s;

.popUpControl { 
	display: none; 
.popUpControl:checked ~ label > .box {
	opacity: 1;
	-webkit-transform: scale(1) skew(0deg);
	-moz-transform:    scale(1) skew(0deg);
	-ms-transform:     scale(1) skew(0deg);
	-o-transform:      scale(1) skew(0deg);

The actual CSS for the box has some additional styling, but this the important functional stuff. Snag the download that accompanies this tutorial for the complete set of CSS.

Changing Text of Button When Open

This isn't vital to the idea here, but another neat trick in this demo is changing the text of the button when the popup is open. This works by actually hiding the text inside the button when :checked and inserting new text. The basics:

<input type="checkbox">
   <span>Default text</span>
[type=checkbox]:checked ~ label span { 
   display: none; 
[type=checkbox]:checked ~ label:before { 
   content: "Text to show when checked";

Yet another "hack" (bad, as generated content isn't very accessible or selectable). But hey, we're still having fun right?

1 Yeah but... JavaScript

This definitely walks "separation of concerns" line. Even though I've created this in "pure" CSS, I actually think this type of behavior is probably better suited to JavaScript. And by "use JavaScript", this is what I would actually do:

  1. Toggle a class name for "open" and "closed" on the .box, rather than use the checkbox.
  2. Change the text through the JavaScript, not use CSS generated content.

But everything else should stay in the CSS. The styling, the transitions, the transforms... That stuff is style not functionality.

Boy I'm a hypocrite hey?


Thanks to Victor Coulon who sent me this idea that I riffed on for this.