Building a CSS toggle feature with :checked and flexbox

I wrote a post a while ago when I learnt how to use the :checked pseudo class to build a toggle feature. I was pretty excited because I didn't have to use any JavaScript to build it. This week, I have created another toggle feature, still using CSS only, which is much more exciting than the first. Why is it more exciting? Firstly, I was able to change the text on the link for each toggle state, which I haven't done before. Then I added flexbox, which is kind of my favourite thing at the moment, to position the link under the revealed content

Here is how it works.

The HTML consists of a checkbox input, a label and some content to toggle. As we can add text and styles to the label, we can hide the input element and let users click the label to select the checkbox. Note: the label's for attribute must match the ID on the input so that the label can be used to select the checkbox. When the checkbox is selected, the pseudo class :checked is added to it. So, by adding styles to :checked, we can style the different states of the toggle.

<div class="toggle"> 
   <input type="checkbox" value="selected" id="someID" class="toggle__input"> 
    <label for="someID" class="toggle__label">show me <span class="toggle__more>more</span><span class="toggle__less>less</span> stuff </label> 
    <div class="toggle__content"> <p>Lorem ipsum dolor sit amet, consectetur adipisicing elit. Reiciendis sequi quidem officia quis, repellendus voluptas voluptatibus in adipisci magnam consectetur, reprehenderit laborum ducimus ipsum dolores dicta vero. Laudantium inventore, reprehenderit!</p> </div> 
</div><!--Toggle-->

You may be wondering why there are two spans inside the label. One has the class "toggle__more", and the other "toggle__less". This allows us to show and hide the text based on the :checked status. The aim is that when the toggle is closed, the label will read "Show me more stuff", and when it is open it'll say "Show me less stuff".

The CSS

Step 1: Hide the input element.

We will look at how to show and hide the content shortly.

.toggle__input {
    display: none;
}

Step 2: Style the label in any way desired.

I made mine look like a button. You will also see that I have added a "+" before the label. Later on, I will explain how to change this to a "-" on toggle. An image could be used instead, or even nothing at all. It is completely optional. All of the text that the user needs to see is in the HTML, where it should be, so the content generated here in the CSS is supplementary.

.toggle__input + label {
    background-color: #D65D5D;
    border-radius: 5px;
    color: #fff;
    cursor: pointer;
    font-szie: 0.8em;
    margin-top: 0.5em;
    margin-bototm: 0.5em;
    padding: 0.5em;
    text-align: center;
    width: 12em;
}
.toggle__input + label:before {
    color: #fff;
    content: "+";
    font-size: 1em;
    padding-right: 0.3em;
    width: 1em;
}

Step 3: Show the content when the input is :checked, and hide it when it is not checked.

We can do this using the :checked pseudo class. This is where the fun starts.

.toggle__input:checked ~ .toggle__content {
    display: block;
}    
.toggle__input:not(:checked) ~ .toggle__content {
    display: none;
}

Note: Browsers that don't recognise the :checked pseudo-class may not display it correctly. To solve this, I have used the negation pseudo class :not(), so browsers that don’t support :checked don’t follow these rules. Therefore, if :checked is not supported, the content will not be hidden.

Step 4: Change the "+" to a "-" when the checkbox is checked.

If you look back at step 2 where we added "+" to .toggle__input + label:before, you will see that the only difference here is the addition of the :checked class and the new content specified.

.toggle__input:checked + label:before {
    content: "\2212";
}

Step 5: Swap the less and more text.

Remember adding "less" and "more" to the label inside spans? One had a class .toggle__less and the other .toggle__more. So on the unchecked state we can hide .toggle__less, like below. When the box is checked, you can see that it will be displayed inline. Then we hide the .toggle__more text.

.toggle__input + label .toggle__less,
.toggle__input:checked + label .toggle__more  {    
    display: none;
}
.toggle__input:checked + label .toggle__less {
    display: inline;
}

Step: 6: Move the button underneath the revealed content.

Up to now, the text is displayed underneath the button, which is fine, unless you want the revealed content to be a continuation of some content above it. We want the link to stay where it should be in the markup (above the revealed content), but we want it to appear underneath the revealed content. So, flexbox is your friend.

In the HTML, I added a wrapper div called .toggle. By making this a flex container using display: flex; I can change the order of the content. Flex-direction column-reverse will tell the label and content to stack vertically and in reverse order, so we don't have to rearrange the HTML. Nice!

.toggle {
    display: flex;
    flex-direction: column-reverse;
}

As usual, I have posted an example on Codepen

See the Pen CSS toggle feature by Lottejackson (@lottejackson) on CodePen.

Browser support

:checked seems to be well supported in most modernbrowsers, including IE9 upwards. I have used caniuse.com to check this.