Implementing “Show More/Less” Functionality with Pure CSS

By George Martsoukos

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:

  <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>
      <li><a href="#">Five</a></li>
      <li><a href="#">Six</a></li>
      <!-- more list items here -->

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


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.

  • Sapioit

    Really? This is not nearly close to pure CSS. There is Javascript? Than it’s NOT PURE CSS! For a pure CSS version, try FutureBox versions One and Two: | ; The version Three is for more advanced users… ; The first version can be also seen in the next link, with the code cleaned ot the maximum…

    • LouisLazaris

      You’re looking at the wrong demo. The first demo is a “learning” demo, to help readers understand the concepts that will be used later. That one uses JavaScript, but that’s just to trigger the content for the buttons. That has nothing to do with the tutorial. It was more of an intro thing that George put together.

      Look at the final two demos, those are the ones you want to examine. They have no JS.

  • M S i N Lund

    Have you checked (ahem) if this work in all browsers?

    I tried using that checkbox hack, earlier this year, but couldn’t get it working on several browsers, and some devices with touchscreens.

    That made it pretty much useless in a real live site.

    Neat little hack, though.

    • Hi M S i N Lund,

      I used the CrossBrowserTesting app to test the demo. The technique works fine in all desktop browsers apart from the older versions of IE (i.e. IE < 9). Regarding the mobile devices, I have seen that it doesn't work in some browsers like the Android (4.1 and below), Dolphin (11.2 and below), and Mobile Safari Browsers (5.1 and below). You can also have a look at a relevant question on stackoverflow.

    • LouisLazaris

      Yep, they work fine pretty much everywhere. I even tested on my old iPad 3 which has an older iOS on it, and it works there too. The checkbox hack’s main concern, I think, is accessibility. I think browser support is usually pretty deep.

  • Vincent Young

    I’m always interested in creative solutions like this, but I would never advocate using this at the moment. The semantics are wrong and it doesn’t work well with assistive technology. At the end I’d give solid advice to create an inclusive user experience:

    With JS, have a button that has plain text and aria-expanded/aria-controls attributes.

    Thanks for the share.

    • Hi Vincent,

      Thanks for your feedback and suggestions! As mentioned in the introduction, the goal of this article was just to show the technique and not dive into the process of how to make it accessible.

      • Vincent Young

        I don’t think you need to dive into that, but a mention is important.

  • Jon

    Thanks a lot for this tutorial – this is exactly what I was looking for to use in a project, and the explanation behind understanding how to use it is awesome.

  • Very clever. Bookmarked.

  • Hey,

    Thanks for this. I would like to do more with this.

    How to dynamically show/hide multiple blocks? Like More1 to display 5 and 6, More2 to display 8 and 8, More3 to display 9 and 10 and so on.

  • WebGirl

    I’m trying to utilize this into an existing ul I can’t seem to get it to accept the base ul styles.

  • Phan55

    Thanks for this explanation and sample – I’ve just been tasked by my manager to try to figure out how to hide/show rows in long tables but none of us know CSS well and we are trying to do this in a corporate knowledge base where we don’t control the wrapper code so we can’t use JavaScript. Hope I can fumble my way through this for tables rather than lists :->

  • Jason Hammond

    This is very cool! I’m curious why the checkbox has to be absolute positioned and thrown off the screen rather than just display:none; ?

Get the latest in Front-end, once a week, for free.