Eklavya's Blog

Making a keyboard-navigable collapsible without JavaScript

A 'collapsible' is content whose visibility can be toggled. Here is an example:

This is the body of the collapsible. Its visibility can be toggled using the 'click me' button.

Here we will learn how to make such a collapsible without using JavaScript.

In this article, we'll look at how to make one.

Important update: This article talks about a hacky solution to make a collapsible without using JavaScript. I recently (September 2020) found a much simpler, cleaner and semantic solution using the <details> tag.

Skeleton

Let's first create the basic structure without collapsibility.

HTML:

<div class="collapsible">
    <div class="collapsor-lbl"> Click me </div>
    <div class="collapsible-content">
        This is the body of the collapsible.
    </div>
</div>

CSS (for beautification):

.collapsible {
    max-width: 40em;
    border: 1px solid black;
    border-radius: 0.5rem;
}
.collapsor-lbl:hover {
    background-color: rgba(0, 0, 0, 0.1);
}
.collapsor-lbl {
    text-align: center;
    padding: 0.5rem 1rem;
}
.collapsible-content {
    padding: 0.5rem;
    border-top: 1px solid black;
}

This is what the output looks like:

Click me
This is the body of the collapsible.

Collapsibility

To add collapsibility, we're going to use a checkbox. I read about it on the blog post 'Implementing A Pure CSS Collapsible' by alligator.io.

  • A checkbox maintains state about whether it's checked or not. We can use that to maintain state about whether our collapsible has been clicked or not.
  • We can use the CSS style display:none to make the checkbox disappear, but still retain its functionality.
  • We can use the CSS pseudo-selector :checked to select a checked checkbox.
  • We will make sure that .collapsible-content is a sibling of the checkbox. Then we can use the CSS sibling combinator '~' to select it. When used together with :checked on checkbox, we can select .collapsible-content only when the checkbox is checked.

Change the HTML to this:

<div class="collapsible">
    <input id="checkbox1" class="collapsor" type="checkbox" />
    <label for="checkbox1" class="collapsor-lbl"> Click me </label>
    <div class="collapsible-content">
        This is the body of the collapsible.
    </div>
</div>

Add this CSS:

.collapsor-lbl {
    display: block;
}
.collapsor, .collapsible-content {
    display: none;
}
.collapsor:checked ~ .collapsible-content {
    display: block;
}

Output:

This is the body of the collapsible.

Accessibility

Web accessibility is the inclusive practice of ensuring there are no barriers that prevent interaction with, or access to websites, by people with disabilities.

I once had a mouse that sometimes stopped functioning, so I can feel a bit of the pain of users who cannot use a mouse. Also, some people like using the keyboard for navigation and it would be bad to force them to use a mouse.

I don't know much about web accessibility standards and what it takes for my websites to be fully accessible, but the least I can do is make my pages keyboard-navigable.

The blog post by alligator.io says how to make a collapsible using only CSS, but to make it accessible they had to use JavaScript. I, however, have a way of doing it without JavaScript.

When navigating a web page using the tab key, certain HTML elements have the potential of receiving focus. This generally includes links (<a> tags) and form elements (<input> tags). When an element receives focus, it gets the :focus CSS pseudo-class. Also, that element and all its descendants get the :focus-within class.

We will therefore wrap .collapsible-content within a <div>. We will make that <div> capable of receiving focus via tab by setting the attribute tabindex to "0". Then whenever that div has the :focus-within pseudo-class set, we will set display: block on .collapsible-content.

Change the HTML to this:

<div class="collapsible">
    <label for="checkbox2" class="collapsor-lbl"> Click me </label>
    <div class="focus-capturer" tabindex="0">
        <input id="checkbox2" class="collapsor" type="checkbox" />
        <div class="collapsible-content">
            <p>This is the body of the collapsible.<p>
            <ul>
                <li><a href="">link1</a></li>
                <li><a href="">link2</a></li>
                <li><a href="">link3</a></li>
            </ul>
        </div>
    </div>
</div>

Add this CSS:

.focus-capturer:focus-within .collapsible-content {
    display: block;
}

Output:

This is the body of the collapsible.

Try using the tab key to navigate all the 3 links in the collapsible. When the focus moves out of the collapsible, it closes (unless you had clicked on it to open it).