Implementing “Show More/Less” Functionality with Pure CSS

George Martsoukos
George Martsoukos
Share

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.

Frequently Asked Questions (FAQs) about Implementing Show More/Less Functionality with Pure CSS

How can I implement a “Show More/Less” functionality using only CSS?

Implementing a “Show More/Less” functionality using only CSS involves using the :target pseudo-class. This pseudo-class allows you to style an element that has been targeted by a fragment identifier in the URL. You can use this to hide and show content. The HTML structure will include a container for the content, a “Show More” link, and a “Show Less” link. The CSS will then style these elements and use the :target pseudo-class to hide and show the content when the links are clicked.

Can I use this method to hide and show any type of content?

Yes, you can use this method to hide and show any type of content that can be contained within an HTML element. This includes text, images, videos, and even other HTML elements. The content just needs to be placed within the container element that is being targeted by the “Show More” and “Show Less” links.

Is it possible to animate the transition between the hidden and shown states?

While CSS does provide transition and animation properties that can be used to animate changes in an element’s style, these cannot be used with the :target pseudo-class. This is because the :target pseudo-class does not change the element’s style gradually over time, but instantly when the fragment identifier in the URL changes. However, you can achieve a similar effect by using JavaScript in addition to CSS.

Can I use this method on multiple elements on the same page?

Yes, you can use this method on multiple elements on the same page. Each element just needs to have its own unique ID that can be targeted by the “Show More” and “Show Less” links. The CSS will then hide and show each element independently based on which link is clicked.

How can I style the “Show More” and “Show Less” links?

You can style the “Show More” and “Show Less” links just like any other link in CSS. You can change their color, font, size, background, border, and other properties. You can also add hover effects using the :hover pseudo-class.

What browsers support the :target pseudo-class?

The :target pseudo-class is supported by all modern browsers, including Chrome, Firefox, Safari, Edge, and Internet Explorer 9 and later. However, it may not work correctly in older browsers or in browsers that do not fully support CSS3.

Can I use JavaScript instead of CSS for this functionality?

Yes, you can use JavaScript instead of CSS for this functionality. JavaScript provides more control and flexibility, and it allows you to animate the transition between the hidden and shown states. However, using CSS is simpler and does not require any programming knowledge.

Can I use this method to hide and show content based on other conditions, not just a link click?

The :target pseudo-class only reacts to changes in the fragment identifier in the URL, which is typically changed by clicking a link. However, you can use other CSS pseudo-classes or JavaScript to hide and show content based on other conditions, such as the :hover pseudo-class for when the mouse is over an element, or the :checked pseudo-class for when a checkbox is checked.

Can I use this method to create a dropdown menu?

Yes, you can use this method to create a dropdown menu. The menu items would be placed within the container element, and the “Show More” link would be used to open the menu, and the “Show Less” link to close it. The CSS would then hide and show the menu items when the links are clicked.

Can I use this method to create a collapsible section in a form?

Yes, you can use this method to create a collapsible section in a form. The form fields would be placed within the container element, and the “Show More” link would be used to expand the section, and the “Show Less” link to collapse it. The CSS would then hide and show the form fields when the links are clicked.