// Tutorial //

Implementing A Pure CSS Collapsible

Published on May 2, 2020
Default avatar
By Alligator.io
Developer and author at DigitalOcean.
Implementing A Pure CSS Collapsible

Collapsible widgets are a popular way to create sections of content that can contract and expand. There are a ton of different implementations out there. Here, thanks to checkbox input elements, label elements and the :checked pseudo-selector, we’ll be able create such widget without the need for extra JavaScript.

Here’s what our collapsible looks like:

See the Pen KKzWqVX by alligatorio (@alligatorio) on CodePen.

And here’s the HTML markup for it:

<div class="wrap-collabsible">
  <input id="collapsible" class="toggle" type="checkbox">
  <label for="collapsible" class="lbl-toggle">More Info</label>
  <div class="collapsible-content">
    <div class="content-inner">
      <p>
        QUnit is by calling one of the object that are embedded in JavaScript, and faster JavaScript program could also used with
        its elegant, well documented, and functional programming using JS, HTML pages Modernizr is a popular browsers without
        plug-ins. Test-Driven Development.
      </p>
    </div>
  </div>
</div>

If you want a collapsible to be opened by default, simply set the checked attribute on the checkbox:

<input id="collapsible2" class="toggle" type="checkbox" checked>

See the Pen qBZrjqG by alligatorio (@alligatorio) on CodePen.

Note that each label should be associated with the correct checkbox, so each checkbox element needs a unique id and each label’s for attribute should point to the corresponding checkbox’s id.

Styling Our Collapsible

Let’s breakdown the styles bit by bit…

First we set the checkbox element to display: none. The checkbox will be invisible and its label will be used instead to check or uncheck it. Later, you’ll see that we’ll use the CSS :checked pseudo-selector to style things differently when the hidden checkbox is checked:

input[type='checkbox'] {
  display: none;
}

Next, we style our default label. Here nothing really special is going on, except for the fact that we make our label display as a block element with display: block:

.lbl-toggle {
  display: block;

  font-weight: bold;
  font-family: monospace;
  font-size: 1.2rem;
  text-transform: uppercase;
  text-align: center;

  padding: 1rem;

  color: #A77B0E;
  background: #FAE042;

  cursor: pointer;

  border-radius: 7px;
  transition: all 0.25s ease-out;
}

.lbl-toggle:hover {
  color: #7C5A0B;
}

For the small arrow, some clever use of borders makes it easy to create the triangle shape:

.lbl-toggle::before {
  content: ' ';
  display: inline-block;

  border-top: 5px solid transparent;
  border-bottom: 5px solid transparent;
  border-left: 5px solid currentColor;

  vertical-align: middle;
  margin-right: .7rem;
  transform: translateY(-2px);

  transition: transform .2s ease-out;
}

Refer to this post from CSS-Tricks for all your CSS triangle needs.

You may have noticed also that we made use of the currentColor built-in variable so that our triangle is the same color as our label’s text.


Let’s also give some basic styles to the inner content:

.collapsible-content .content-inner {
  background: rgba(250, 224, 66, .2);
  border-bottom: 1px solid rgba(250, 224, 66, .45);

  border-bottom-left-radius: 7px;
  border-bottom-right-radius: 7px;
  padding: .5rem 1rem;
}

Now we can start with the interesting part. By default, the collapsible-content div will have a max-height value of 0px, making it completely hidden:

.collapsible-content {
  max-height: 0px;
  overflow: hidden;

  transition: max-height .25s ease-in-out;
}

When a collapsible’s checkbox gets checked behind the scenes by clicking its label, we’ll set the content div to a high-enough max-height value so that it can grow to display all its internal content.

Instead of trying to figure out a good height manually, you can also just make use of viewport units with something like 100vh. Using something like 100% would also work, but you’d lose the ability to use transitions. Note that you’ll still probably want to using something like 100% if you think the content in the collapsible could be taller than the viewport.

We make use of the adjacent sibling selector (+) to select our content div when the checkbox is checked:

.toggle:checked + .lbl-toggle + .collapsible-content {
  max-height: 100vh;
}

We use max-height instead of height because we want to avoid using a hard-coded height and want to be able to place content of arbitrary height into our collapsibles.

And now we do something really similar using the adjacent sibling selector to rotate our little triangle shape when the collapsible is expanded and to adjust the bottom right and border left radius of our label:

.toggle:checked + .lbl-toggle::before {
  transform: rotate(90deg) translateX(-3px);
}

.toggle:checked + .lbl-toggle {
  border-bottom-right-radius: 0;
  border-bottom-left-radius: 0;
}

And there you have it! A pretty straightforward way to create a collapsible section without using any scripting at all.

A Note on Accessibility

As it is now, our collapsible widget is not really accessible. We’ll have to add a touch of JavaScript to make it accessible. I know, I know, the whole thing is supposed to be implemented using zero JavaScript. What can I say!

Accessibility technology is constantly improving, leave a comment below if there is a better way to make this accessible.

In the following script we select all the toggle labels and listen for keydown events. If the pressed key is either the enter or the spacebar keys, we trigger a click on the label.

let myLabels = document.querySelectorAll('.lbl-toggle');

Array.from(myLabels).forEach(label => {
  label.addEventListener('keydown', e => {
    // 32 === spacebar
    // 13 === enter
    if (e.which === 32 || e.which === 13) {
      e.preventDefault();
      label.click();
    };
  });
});

To make the label focusable, we add tabindex="0" to it:

<label for="collapsible3" class="lbl-toggle" tabindex="0">With A11y</label>

See the Pen OJNpgpb by alligatorio (@alligatorio) on CodePen.

All Styles

Here are the full set styles at once, for your reference:

.wrap-collabsible {
  margin-bottom: 1.2rem 0;
}

input[type='checkbox'] {
  display: none;
}

.lbl-toggle {
  display: block;

  font-weight: bold;
  font-family: monospace;
  font-size: 1.2rem;
  text-transform: uppercase;
  text-align: center;

  padding: 1rem;

  color: #A77B0E;
  background: #FAE042;

  cursor: pointer;

  border-radius: 7px;
  transition: all 0.25s ease-out;
}

.lbl-toggle:hover {
  color: #7C5A0B;
}

.lbl-toggle::before {
  content: ' ';
  display: inline-block;

  border-top: 5px solid transparent;
  border-bottom: 5px solid transparent;
  border-left: 5px solid currentColor;
  vertical-align: middle;
  margin-right: .7rem;
  transform: translateY(-2px);

  transition: transform .2s ease-out;
}

.toggle:checked + .lbl-toggle::before {
  transform: rotate(90deg) translateX(-3px);
}

.collapsible-content {
  max-height: 0px;
  overflow: hidden;
  transition: max-height .25s ease-in-out;
}

.toggle:checked + .lbl-toggle + .collapsible-content {
  max-height: 100vh;
}

.toggle:checked + .lbl-toggle {
  border-bottom-right-radius: 0;
  border-bottom-left-radius: 0;
}

.collapsible-content .content-inner {
  background: rgba(250, 224, 66, .2);
  border-bottom: 1px solid rgba(250, 224, 66, .45);
  border-bottom-left-radius: 7px;
  border-bottom-right-radius: 7px;
  padding: .5rem 1rem;
}

🌈 Enjoy your fancy almost-CSS-only collapsible!


Want to learn more? Join the DigitalOcean Community!

Join our DigitalOcean community of over a million developers for free! Get help and share knowledge in our Questions & Answers section, find tutorials and tools that will help you grow as a developer and scale your project or business, and subscribe to topics of interest.

Sign up
About the authors
Default avatar
Developer and author at DigitalOcean.

Default avatar
Community Builder

Then: Learned to build the internet on DigitalOcean Community. Now: Building DigitalOcean Community on the internet.


Still looking for an answer?

Was this helpful?

Just installed on my website, looking good after customizing it, but I need help with something.

I put content in it, an image(535x315) and a YouTube video(585x315) both contents are side by side while must resolutions works perfectly well, I found issues with resolutions between 700 and 1268, how can I make those resolutions could work properly with the content that I put inside.


Wrap-collapsible at 1080p https://i.imgur.com/cIOecNB.png



Wrap-collapsible at 1268 https://i.imgur.com/Cr7wAP5.png


Thanks in advance.

Is it somehow possible to use multiple of these at once? Currently when using more than one, clicking on any of them just opens the first one

Thank you for this tutorial.

Would you mind clarifying what you mean by making it ‘Accessible’? My intention is to use this without javascript and when I try this collapsible method on my website, without javascript, it works as intended. Am I using it wrong?

Also, what’s the browser compatibility with this method compared to:

  • <summary> <details> -method
  • :target selector -method

?

Thanks again and hope to hear from you soon.

Another option is the <details> element, which I assume is accessible by default since it’s a browser built-in.