Empire state of mind
< Back
Posted on March 24, 2017

CSS for simple state management

In my last post I ranted a little about JavaScript and how there is a ready made, shiny, new library for just about everything.

In this post we will discuss a few techniques I adopted and developed over the last years to prevent the usage of JavaScript for vital parts of a website.

What means vital?

Well, glad you asked! By vital I mean things that are used for navigation or displaying conditional content.

This includes the main navigation but also tab boxes, accordions and even simple filter solutions.

Why should you care?

Many websites seem shiny on the outside, but I dare you to turn JavaScript off! What you may see will likely look broken beyond repair…

This is due to the excessive use of JavaScript plugins for every little functionality that needs click interaction.

In itself this would not be a huge problem since JavaScript is nearly everywhere. However there are some pitfalls to this approach:

These are the more obvious reasons, but there are other problems which might be even more important to you:

Ok, but what is the alternative?

These are not really buttons… please read on

These are not really buttons… please read on

Check it out…

There are multiple ways to achieve a simple on/off state in CSS. In this post I will focus on the uses of checkboxes.

The most common usecase for using the :checked state of an input element is maybe the checkbox itself. Oftentimes you need a custom style for it, like the following:

As you can see we use a <label> that wraps an <input type="checkbox">. In CSS we then define what happens with a subsequent element, when the checkbox is checked. Essentially this looks like

.foo:checked + .bar {
  /* custom styles... */
}

Note the usage of the next sibling selector + here. This is only possible when an element with the class .bar follows directly after an element with the class .foo.

You can work around this by using the ~ selector instead which targets all siblings that follow. This is important!

Gotchas

Due to the fact that we need to use sibling selectors we are bound to a certain markup. So this technique will not work in every situation.

Let’s implement

You may not only play the accordion

You may not only play the accordion

My favorite example of “you might not need JS” is the accordion: A simple content container with always only one open panel at a time.

The basic markup might look like this:

<div class="accordion">
  <div class="panel">
    <header>
      <h3>Some headline</h3>
      <button class="toggle-panel"></button>
    </header>
    <section>
      Lorem ipsum dolor sit amet, consectetur adipisicing elit.
    </section>
  </div>
  <!-- more panels ... -->
</div>

We have to adjust this layout a little for our CSS only solution:

<div class="accordion">
  <div class="panel">
    <input type="radio" id="open-panel-one" name="accordion1" />
    <header>
      <h3>Some headline</h3>
      <label for="open-panel-one" class="toggle-panel"></label>
    </header>

<!-- ... -->

The button became a label and we added an input as the first child to our .panel. Also note that we use type="radio" and name="accordion1".

Remember that we wanted to have only one panel open at a time? Well this is how we achieve it: Inside of a radio group, there can only ever be one checked input. name="..." denotes the inputs as belonging to one another.

See the id="..." and for="..." attributes? We use them because the input elements state affects the styling of all subsequent siblings.

Talk is cheap. How does the (relevant) css look?

.panel > [type="radio"] {
  display: none;
}

.panel > section {
  display: none;
}

.panel > [type="radio"]:checked ~ section {
  display: block;
}

.panel > [type="radio"]:checked ~ header label {
  color: red;
}

So if there is a checked radio input inside of a panel, all sections on the inputs level should be displayed as block. Also we want all <label> tags living inside of a <header> tag that are on the same level as the checked radio input to get a font color of red.

Example

In the following code pen you can see the result. As you might notice this is extremely basic and hardly usable at the current stage.

Since this blog post is already relatively lengthy I will show you how to put this technique to fancy use in the next one.

Stay tuned! :-)

Picture reference