Some time ago, I wrote about how to make a popup window without JavaScript. Someone left this comment on the copy of this post that I left on CodePen:

Keep in mind that this seriously ***** up your history (and therefore your back button) – a much better solution would be to use a styled checkbox.
—Johan Fagerbeg (@birjolaxew)

I knew about the history thing. Anything that changes the url, and the :target hack does precisely that, will modify the url. So after opening and closing the popup, clicking the back button will do the same in reverse. Trippy.

I was vaguely aware of being able to hack the <input> element’s :checked state, but I’d never had any experience with it firsthand. So I took Johan’s comment to heart, and set out to recode my popup windows to use :checked instead of :target. This is the result:

See the Pen Popup windows without JavaScript, mark two by Paul (@peiche) on CodePen.0

This version is superior to using :target for two reasons. Firstly, there is no effect on browser history. You won’t be able to traverse the history and see popup windows open and close. Secondly, there is no page jump. A side effect of having a target link (where the link directs to a point on the page, like “#popup1” or even just “#”) is that the page jumps to the top.

Under normal circumstances, and in normal use, the target to which the link points is down on the page somewhere. However, in my popup example, that’s not the case. My demo had a small window, and therefore nowhere to scroll. Were it on a long page, where you might have to scroll down, it would always jump back to the top. This is annoying.

Point of fact, this annoying page jump even happens with many JavaScript-based solutions as well, but purely due to a developer’s oversight.

A link which fires a JavaScript event often has the value of “#” because the link has to have some value, but it really doesn’t matter, because it’s the JavaScript that’s handling the real work.

What many developers forget is to stop the event from bubbling up. This is easy to do in jQuery, but if nothing else, the function should do a return false; at the end in order to prevent the page jump.

But I digress. Instead of links, as in the original example, we will use checkboxes and their corresponding label elements to control our popups.

<input type="checkbox" class="hide" id="p1">
<input type="checkbox" class="hide" id="p2">

<label for="p1" class="button">Click me</label>
<label for="p2" class="button">Click me too</label>

Normally, you’d put a checkbox inside its label. In our case, though, we’re going to keep the checkboxes at the same level as the popups. (I’ll show you why in a moment.) Instead, we’ll attach the label to the checkbox by using the for attribute.

As for the markup of the popups themselves, we’ve kept them nearly identical to the previous version. The only difference is in how we toggle their visibility:

#p1:checked ~ #popup1.overlay {
  visibility: visible;
  opacity: 1;
}
#p2:checked ~ #popup2.overlay {
  visibility: visible;
  opacity: 1;
}

This right here is why we kept the checkbox elements at the same level as the popups: so we could use the general sibling selector. Given the following markup:

<div class="a">
</div>
<div class="b">
</div>
<span class="c">
</span>

We could use + to select one element immediately following another, like so:

div.a + div {
  /* selects div.b */
}

But we can use ~ to select an element that is general sibling; that is, anywhere on the same level in the markup.

div.a ~ span {
  /* selects span.c */
}

So even though our first popup is between the second checkbox and the second popup, the second popup still works. But now we need to make sure the popups are able to be closed, and that’s where the <label> element’s for attribute comes in handy again. This is also where the markup for the popup differs between the first example and this one. Instead of a link, we’ll make it a label:

<label for="p1" class="close">&times;</label>

Because I used the class (.close) instead of the element (<a>) to style its appearance, I don’t need to make any changes to the stylesheet. It just works. This also works for the overlay-style close functionality of the second popup:

<label for="p2" class="cancel"></label>

That’s pretty much it. This won’t work in Internet Explorer 8 or below, because it relies on the :checked selector. (Fun fact: IE8 does, however, support the general sibling selector! See caniuse.com for more details.)

I had a lot of fun building the original JavaScript-less popup window demo, and modifying it further here was a great learning experience.

Happy coding!

Husband. Daddy. Programmer. Artist. I’m not an expert, I just play one in real life.

  • Sam

    Hi! Great tutorial, but my specific situation requires even more lean approach – I can use inline CSS only! So, no pseudo-elements whatsoever. Is there a way to do this or “Popup windows without JavaScript” without any pseudo-elements? Thanks!

  • This demo actually doesn’t use pseudo-elements, but the checkbox hack is so named for a reason: it’s a hack. :)

    You need to be able to target a checked checkbox (“:checked” isn’t a pseudo-element, but rather a selector), and you can’t target states with inline CSS. The same goes for hover states; “:hover” isn’t possible with inline CSS either.

  • Sam

    Hi Paul, thanks for the quick reply!
    Sorry, my terminology was off, but that was my question, is there any way I can do this without any inline CSS psudo-selectors, “:hover”s and such?
    (not an advanced user, as you could see already… :D )