This pen refuses to use Javascript

To navigate, you can use the prev/next buttons or the thumbnails to the right. If you prefer to get crazy, you can also use accesskey to jump to sections by using the following keyboard shortcuts:

Mac:          alt + ctrl + [1-7]
PC Chrome/IE: alt + [1-7]
PC FF:        alt + shift + [1-7]

More on accesskeys


This interface relies on the CSS :target selector. An element is a “target” when its id is hash-referenced in the url.

<a href="">Section 1</a>
#section-1:target { 
  display: block; 

In the above example, whenever the link is clicked, the content with an id of section-1 will be displayed.

CodePen removes the hash from the visible url, but right now you are looking at this pen with #s2 as the target. If you were to manually add #s2 to the end of this url and reload this pen, you will be taken to this screen instead of the first.

~ Swinton

In CSS, the tilde ~ is used as the “General sibling combinator”. In English, that means “any following sibling”.

.a ~ .b {
  display: none;

This would hide any .b element that comes after an .a and is a sibling.

<div class="b">All good</div>

<div class="a">All good</div>

<div class="x">
  <div class="b">Still good</div>

<div class="b">Hidden</div>

Only the last .b would be hidden. The first comes before the .a, and the second is not a direct sibling.

Getting Cray / On Fleek

By using :target and ~ we can do some pretty ridiculous things without touching Javascript. In this pen, we are using :target to trigger different state-related styles.

At the top of this pen, there are <a> elements. Their only purpose is being used as targets.

<a id="s1" class="s"></a>
<a id="s2" class="s"></a>

Using their :target state, we can then select and style anything else on the page.

Moving the main content area over -300% to reveal this fourth section.

#s4:target ~ main {
  left: -300%;

Changing the background color and image:

#s4:target ~ #background {
  background-image: url(;
  background-color: #265273;

Changing the progress bar width:

#s4:target ~ #progress {
  width: 50%;

Styling the fourth thumbnail as active:

#s4:target ~ .thumbs li:nth-child(4) a {
  opacity: 1;
  width: 80px;
  height: 60px;
  box-shadow: 0px 0px 0px 1px rgba(255, 255, 255, 0.1);

Showing the appropriate previous link and making it say the right thing:

#s4:target ~ .prevnext li[class*="p4"] {
  display: block;
  float: left;
#s4:target ~ .prevnext li[class*="p4"] a:after {
  content: "Prev";

Showing the appropriate next link and making it say the right thing:

#s4:target ~ .prevnext li[class*="n4"] {
  display: block;
  float: right;
#s4:target ~ .prevnext li[class*="n4"] a:after {
  content: "Next";


Each of the next/prev navigation items link to each of these spans and have an accesskey attribute. This allows them to be fired using the browser’s accesskey plus whatever key is provided. In this case, we are using the corresponding section number.

<a href="#s1" accesskey="1"></a>
<a href="#s2" accesskey="2"></a>

Each browser and operating system use different keyboard shortcuts for accesskey, but the support is suprisingly universal.

Mac:          alt + ctrl
PC Chrome/IE: alt
PC FF:        alt + shift

To switch to the next section, you can hold down your accesskey combination above and then press 6.

There’s more to read about accesskey on w3schools.


As with any “Who needs JS?” approach, there are more than a few things to consider when doing something like this.


Confining yourself to CSS makes any interactive experience more inaccessible. There are a handful of ways one could optimize this for screen readers more than I (hardly) have, but it would be a lot of effort for a unsatisfactory result.

Additionally, since the “prev/next” links are actually new links on each section change, the :focus that occurs when you have tabbed into them disappears because the link is actually disappearing. You need to re-tab in every time. Blech. No one likes excess tab unless we’re talking about the soda.


Each hashchange is going to be recorded in the browser history without some Javascript intervention. This can go either way as a pro or con. For something like this, it may be a pro.


All ths content is being dumped into the DOM at once. At a certain size, this approach might not make a lot of sense. In theory, you could get around some of this with some smart selectors and display: none;, but depending on the complexity of your content, you may still have some load time issues. Dynamically loading content with Javascript is a often a very, very good thing.

Acceptable Use Cases

There are some cases where something this inaccessible is technically ok. This could make sense in an environment where it is only being used in a visual setting such as a live presentation or internal documentation tool.

Most importantly, it can be used as a way to show that CSS is very powerful, but can also be very limiting—depending on the need.

If you liked this, you may enjoy more of my bogus CSS in my No JS Collection.