HTML & CSS - - By George Martsoukos

Implementing “Show More/Less” Functionality with Pure CSS

Nowadays developers take advantage of different CSS techniques to create sliders, modals, tooltips, and many more Javascript-based components.

In this article, we’ll work with some of those techniques to implement what we might refer to as the “Show More/Less” functionality, and doing it without writing any JavaScript. We’ll also create a fully functional accordion-style show/hide demo that you can use as a starting point for your projects.

We have a lot to cover (techniques, pitfalls, and challenges), so let’s get started!

Note: This article will not discuss how to make this component accessible, but that could certainly be a valid improvement and maybe a topic for another post.

CSS Requirements

To better understand the process for creating this functionality, you have to be familiar with the following key CSS concepts:

To help out, I’ve created a demo, which you can see below, to give you the knowledge needed to follow along with this article.

See the Pen Lessons to prepare for the checkbox hack by SitePoint (@SitePoint) on CodePen.

Just click on one of the buttons to view a description and demo of the technique.

The Markup

The HTML structure for our demo is shown below:

<ul>
  <li><a href="#">One</a></li>
  <li><a href="#">Two</a></li>
  <li><a href="#">Three</a></li>
  <li><a href="#">Four</a></li>
  <li class="container">
    <input type="checkbox" id="check_id">
    <label for="check_id"></label>
    <ul>
      <li><a href="#">Five</a></li>
      <li><a href="#">Six</a></li>
      <!-- more list items here -->
    </ul>
  </li>
</ul>

As you can see, we define an unordered list with five list items. The last item acts as the container for our checkbox and its corresponding label. In addition, we include a second unordered list within the wrapper element. This list holds the items that we want to show as soon as the checkbox becomes checked.

Defining Basic Styles

After structuring the demo, we define a few reset styles for our elements. Nothing fancy, just some simple CSS rules that will allow us to enhance the appearance of our component. Have a look at the two rule sets below:

ul {
  width: 50%;
  margin-left: auto;
  margin-right: auto;
  list-style-type: none;
  background: white;
}

li {
  height: 50px;
  line-height: 50px;
  border-top: 1px solid #e9ecef;
}

Styling the Checkbox Container

In this part of the tutorial, we will set up the styles for the last list item.

First, we change the value of the aforementioned height property. Then, we set its position to relative. This property value is important because it will help us position the label element:

.container {
  position: relative;
  height: auto;
}

Next, we hide the checkbox:

[type="checkbox"] {
  position: absolute;
  left: -9999px; 
}

…and style the relevant label:

label {
  background: #e4e3df;
  display: block;
  width: 100%;
  height: 50px;
  cursor: pointer;
  position: absolute;
  top: 0;
}

Notice that we position our label absolutely and give it a top offset of 0 pixels. So now you might be wondering if we really need those two properties. At first glance they seem unnecessary. But as we’ll see in a moment, these are required declarations.

If you look back at the HTML structure, you’ll see that the label is an empty element. We made that assumption because we want its content to change depending on the checkbox state. To achieve this, we take advantage of CSS’s pseudo-elements. When the checkbox isn’t checked (default behavior), the More keyword appears. Also, for styling we use a few characters taken from the lovely CopyPasteCharacter web app.

The following code snippet shows how we style the pseudo-elements related to the label element:

label:before,
label:after {
  position: absolute;
}

label:before {
  content: 'More';
  left: 10px;
}

label:after {
  content: '⇣●';
  right: 10px;
  animation: sudo .85s linear infinite alternate;
}

@keyframes sudo { 
  from { 
    transform: translateY(-2px); 
  }
  to { 
    transform: translateY(2px); 
  }   
}

Τhe last thing required is to ensure the adjacent sibling element is hidden by default before the checkbox is checked:

[type="checkbox"] ~ ul {
  display: none;
}

Mimic the onclick Event

As this point, if we trigger the “checked” state of the checkbox, a number of CSS rules are fired. Let’s explore them!

The first of those rules, which allows us to reveal the hidden elements, is shown below:

[type="checkbox"]:checked ~ ul {
  display: block;
}

Next, we change the position of our label. The goal is to position it at the bottom of its parent, right underneath the unordered list. To do so, we use top: 100%;. As a quick reminder (even though it’s beyond the scope of this article) keep in mind that top: 100%; and bottom: 0; don’t produce the same result. In our case, we use the former:

[type="checkbox"]:checked + label {
  top: 100%;
}

Last but not least, we have to modify the content of the label. Similar to the previous section, we generate the desired content by using pseudo-elements:

[type="checkbox"]:checked + label:before {
  content: 'Less';
}

[type="checkbox"]:checked + label:after {
  content: '⇡●'; 
}

Having followed all the steps above, we should now be able to implement a nice toggle effect with plain CSS.

See the CodePen demo below:

See the Pen Pure CSS “Show More/Less” functionality without Transitions (slideToggle effect with no JavaScript). by SitePoint (@SitePoint) on CodePen.

One thing to point out here: The content that expands can be pretty much anything. I’m using a list, and the component looks a lot like a traditional accordion widget. But that’s not what this is. This is a component that has half its content hidden and allows you to show/hide the rest of it with a click.

The component looks nice, but there’s still room for improvement! More specifically, if you take a closer look at the demo, you’ll notice that there aren’t any established transitions between the “on” and “off” states of the checkbox. That makes the final result look a little bit ugly, doesn’t it? So let’s fix this issue by working on a second updated version!

Adding Transitions

The new version aims to smoothly transition between the “checked” and “unchecked” states. However, before showing how we can make that happen, we should first understand which CSS properties are animatable. Thankfully, the W3C informs us in a useful table.

Let’s now revisit the following CSS rules:

[type="checkbox"] ~ ul {
  display: none;
}

[type="checkbox"]:checked ~ ul {
  display: block;
}

The rules above specify the behavior of the second unordered list during the toggle switches. Unfortunately, as the table linked above shows, we cannot transition the display property, so we have to find an alternative. The height property seems to be a good option and thus, let’s redefine our rules as follows:

[type="checkbox"] ~ ul {
  overflow: hidden;
  height: 0;
  transition: all .45s cubic-bezier(.44,.99,.48,1);
}

[type="checkbox"]:checked ~ ul {
  height: 300px; /* this value will work */
  height: auto; /* this value will not work */
}

Once again, keep in mind that we cannot transition the height property to a value of auto. That means we have to define a static value (e.g. 300px). So, due to this restriction, the height property cannot be considered as a good solution.

A better approach is to use the max-height property. However, this option has limitations as well. Depending on the real height of the content that we want to show and the property value that we set, the transition effect will have a different speed. In general, the max-height property can be a solution (in fact an effective workaround) only when we can control the height of the corresponding element. Otherwise, JavaScript would be the only option.

Be sure to check out this StackOverflow thread about transitioning the height property.

Here are the styles that we replace compared to the previous ones:

[type="checkbox"] ~ ul {
  max-height: 0;    
}

[type="checkbox"]:checked ~ ul {

  /**
   * the value below corresponds to the 
   * height of the `ul` element 
     * if we set a slightly larger value (e.g. 500px)
     * we won't see a great difference
     */

  max-height: 300px;

  /**
   * try to replace the previous line with
   * the following one and see the difference:
   * max-height: 9999px;
   */
}

The last step is to transition our label. Remember that its top value changes depending on the state. Although, as mentioned in a previous section, its initial top: 0; property value seemed useless, it actually provides a great way to transition this property. That said, we update the relevant rule so as to include the transition property:

label {
  position: absolute;
  top: 0;
  transition: top .45s cubic-bezier(.44,.99,.48,1);
}

[type="checkbox"]:checked + label {
  top: 100%;
}

Moreover, note that both transitions share the same properties (i.e. timing functions and duration). Even though this isn’t necessary, it helps us produce a smooth transition effect.

See the final demo below:

See the Pen Pure CSS “Show More/Less” functionality with Transitions (slideToggle effect with no JavaScript). by SitePoint (@SitePoint) on CodePen.

Things to Keep in Mind

Before closing, I want to point out something that I noticed while I was testing the demo on different browsers.

If you invoke the “checked” state (click on the label to open it) and don’t move your cursor, the following things happen:

  • On Chrome, Safari, IE, and Opera the cursor sticks and the “Fifth” element isn’t immediately highlighted unless you move the cursor.
  • On Firefox the demo works as expected.

You can further test this by adding the following CSS:

label:hover {
  background: yellow;
}

By adding this, you’ll see that when you toggle the label and don’t move the position of your cursor, the label also sticks in its hover state.

Please keep in mind that the issue above happens mainly on the second demo where we added the transitions.

That being said, I don’t think it’s a big deal but I wanted to share it with you!

Conclusion

In this article, we used the “checkbox hack” technique along with the sibling selectors in order to implement the “Show More/Less” functionality with plain CSS. By creating two different versions of our demo, we gained good knowledge of how far we can push the limits of CSS.

I hope you enjoyed the article and maybe it will motivate you to build something interesting with just CSS.

Sponsors