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:

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.

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>

Bacon ipsum dolor amet pork belly capicola ground round brisket. Shank pork belly cupim fatback. Flank porchetta chicken spare ribs. Ham prosciutto pork belly turkey beef picanha strip steak swine alcatra.

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. 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: 350px;
}

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!

Note that I'm not an accessibility expert, so the following may not be the best way to go about it. Feel free to get in touch if you have a better way.

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>

Careen fluke jolly boat keel topgallant Admiral of the Black barkadeer sloop poop deck salmagundi. Avast red ensign parley clap of thunder no prey, no pay killick stern clipper execution dock splice the main brace. Grog blossom yardarm bilge water marooned cog wherry tackle aye Shiver me timbers come about.

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: 350px;
}

.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!

  Tweet It
✖ Clear

🕵 Search Results

🔎 Searching...