Tips for ensuring Accordion menu closes upon clicking a second time

I created a basic Accordion menu using just HTML and CSS. The functionality works well, but I'm facing an issue where clicking on an open accordion doesn't close it.

Can this be achieved with CSS alone or would I need to use JavaScript?

.middle a {
  text-decoration: none;
}

.menu {
  width: 100%;
  overflow: hidden;
}

.item {
  border-top: 1px solid #2980b9;
  overflow: hidden;
}

.btn {
  display: block;
  padding: 16px 20px;
  background: #3498db;
  color: white;
  position: relative;
}

.btn:before {
  content: "";
  position: absolute;
  width: 14px;
  height: 14px;
  background: #3498db;
  left: 20px;
  bottom: -7px;
  transform: rotate(45deg)
}

a i {
  margin-right: 10px;
  margin-left: 10px
}

.smenu {
  background: white;
  overflow: hidden;
  transition: max-height 0.3s;
  max-height: 0;
}

.smenu a {
  display: block;
  padding: 16px 26px;
  font-size: 14px;
  margin: 4px 0;
  position: relative;
}

.smenu a:before {
  content: "";
  position: absolute;
  width: 6px;
  height: 100%;
  background: #3498db;
  left: 0;
  top: 0;
  transition: 0.3s;
  opacity: 0;
}

a:hover:before {
  opacity: 1;
}

.item:target .smenu {
  max-height: 100%;
}

.item {
  list-style-type: none
}
<div class="middle">
  <div class="menu">
    <li class="item" id="aboutus">
      <a href="#aboutus" class="btn">About Us</a>
      <div class="smenu">
        <a href="#">1</a>
        <a href="#">2</a>
        <a href="#">3</a>
        <a href="#">4</a>
        <a href="#">5</a>
        <a href="#">6</a>
      </div>
    </li>
    <li class="item" id="contactus">
      <a href="#contactus" class="btn">Contact Us</a>
      <div class="smenu">
        <a><i class="fas fa-phone"></i>000-000-000</a>
        <a><i class="fas fa-envelope-open"></i>Send Us An Email</a>
        <a>ABCDEFG</a>
        <a>P.O> Box 00000</a>
        <a>New York, New York 000000</a>
        <a><i class="fas fa-clock"></i>Monday - Friday: 9AM - 5PM ET</a>
        <a><i class="fas fa-window-close"></i>Saturday-Sunday: Closed</a>
        <a><i class="fas fa-desktop"></i>Online: 24/7</a>
      </div>
    </li>
    <li class="item" id="sitelinks">
      <a href="#sitelinks" class="btn">Site Links</a>
      <div class="smenu">
        <a href="#">1</a>
        <a href="#">2</a>
        <a href="#">3</a>
        <a href="#">4t</a>
        <a href="#">5</a>
        <a href="#">6</a>
      </div>
    </li>


  </div>
</div>

Answer №1

The Challenge

When it comes to opening an accordion panel, the key is for the panel with the li class="item" to be the current target (hash).

.item:target .smenu {
  max-height: 100%;
}

However, closing that panel is not as straightforward. Simply clicking on the item again won't work; you'll need to click on something else or turn to JavaScript. So, the answer to the question "Can I achieve this with CSS or do I need JavaScript?" leans towards requiring JavaScript.

Incorporating JavaScript

The good news is that the JavaScript solution is relatively simple. Encapsulated within a DOMContentLoaded function, here's how it would look if placed in its own .js file linked to your page:

document.addEventListener("DOMContentLoaded", function(event) {
    const items = document.querySelectorAll('.menu li.item');
    for (let item of items) {
        item.addEventListener('click', function(e) {
                if ('#'+e.currentTarget.id === location.hash) {
                    e.preventDefault();
                    window.location.hash = '';
                }
        });
    }
});

Understanding the Functionality

This code selects all menu items using the querySelectorAll method and loops through each one to attach a 'click' event listener. The logic inside the event listener checks if the clicked item matches the current hash value; if so, it prevents the default action and clears the hash.

With the hash value cleared, the CSS selector .item:target no longer applies, causing the previously open accordion panel to close.

Using a Stack Snippet

In a stack snippet scenario where the DOM readiness is handled automatically, we can simplify the script. Below is the modified version incorporating JavaScript into your existing snippet:

const items = document.querySelectorAll('.menu li.item');
    for (let item of items) {
        item.addEventListener('click', function(e) {
                                if ('#'+e.currentTarget.id === location.hash) {
                                    e.preventDefault();
                                    window.location.hash = '';
                                }
        });
    }
(CSS styles)
(HTML structure)

Furthermore, since the items variable isn't essential beyond looping, you can even further simplify the code using arrow functions:

document.addEventListener("DOMContentLoaded", function(event) {
    document.querySelectorAll('.menu li.item')
            .forEach(element =>
                  element.addEventListener('click',
                     e => {
                            if ('#'+e.currentTarget.id === location.hash) {
                                e.preventDefault();
                                window.location.hash = '';
                            }
                          })
                 )
});

The condition in the code could potentially be refactored into a predicate function, but that's left as an exercise for the reader.

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

What is the reason behind both paragraphs being displayed in a shade of blue in this snippet of code

.red p { color: red; } .blue p { color: blue; } <div class="blue"> <p> first </p> <div class="red"> <p> second </p> </div> </div> Initially, I expected the first paragraph to be bl ...

The hover effect generates a flickering sensation

I am having trouble displaying options on an image when hovering over it, as the displayed options keep flickering. $('a').hover(function(event) { var href = $(this).attr('href'); var top = $(this).next().css("bot ...

Loading necessary CSS when needed in React JS

I am currently in the process of converting a bootstrap template to react framework. My question is, is there a way for me to load stylesheets on demand? For instance, if I have 2 components and I import the same stylesheet separately in both components, ...

Displaying the "Ad Blocker Detected" image next to the advertisement

I am attempting to create a feature where if adblock is enabled, an image will display behind the ad banner on my website with the message "Please consider disabling adblock". Unfortunately, it is only showing up as a bordered box. Here is how the box ap ...

Adjustable / consistent box height

For hours, I've been attempting to make some divs the same height. Check out my JSfiddle here: https://jsfiddle.net/4cj5LbLs/15/ I experimented with using display: table; in the parent element and either display: table-cell; or display: table-colum ...

What method does Apple use to apply overflow:hidden to elements within position:fixed?

After conducting my own experiments and delving into the topic online, I discovered that elements using position: fixed do not adhere to the overflow: hidden property of their parent element. This also applies to children within the fixed positioned elemen ...

Is the ID selector the quickest method in jQuery and CSS?

Which is the optimal choice in jQuery/javascript for speed? $('#myID .myClass') or $('.myClass') What is the preferred option to utilize in CSS? #myID .myClass{} or .myClass{} In hindsight, I realize my explanation was insuffici ...

Don't forget to update the CSS stylesheet whenever templates are rendered in Flask

I have developed a Flask application using HTML and external (static) CSS stylesheets. One interesting feature of the application is a checkbox that uses JavaScript to toggle between two different CSS stylesheets - one for light mode and one for dark mode. ...

Customizing SwiperJS to display portion of slides on both ends

I need some assistance with my SwiperJS implementation to replace existing sliders on my website. The goal is to have variable-width slides, showing a landscape slide in the center with a glimpse of the preceding and following slides on each side. If it&ap ...

I am having trouble placing the button within the image

Essentially, my goal is to place the button within the image and ensure it remains in its position when transitioning to mobile mode or adjusting window size. This is the desired outcome: https://example.com/image https://example.com/button-image .img ...

Graphs vanish when they are displayed in concealed sections

Looking for a way to toggle between two charts (created with charts.js) by clicking a button? Initially, I had them in separate divs, one hidden and the other visible: <div id="one"> <canvas id="myChart1" width="400" height="400"></can ...

CSS - Issue with table cell height exceeding in Mozilla Firefox and Internet Explorer

Examining the code below <div style="border:solid 5px orange;width:400px;height:400px"> <div style="display:table;border:solid 1px red;width:100%;height:100%"> <div style="display:table-row;border:solid 1px blue"> <div sty ...

Having trouble with implementing the .addclass function in a dice roller project

I'm looking to have the element with id=die load initially, and then on a button click event labeled "click me," apply the corresponding CSS class such as 'die1,' 'die2,' and so forth. function roll() { var die = Math.floor(Ma ...

Looking for a way to make a button glide down to the bottom of a card in a

I need to ensure that the contact button in each card floats to the bottom, regardless of the amount of text within the card. This will create a uniform appearance with all cards having the contact button in the same position. Here is an example of how on ...

Using an inline-block within a positioned absolute element

I am curious about the behavior of inline-block elements inside absolutely positioned elements. To better explain, I have provided an example below. We can see in the code snippet why the .icon within the .tag is not displayed like the previous .icon (whic ...

Unexpected issue with accessing the jQuery class descendant

I am currently developing a photo gallery and I am trying to add some CSS styling to the first photo that is visible within my #wrapper div. Below is the code snippet I have been working on: $('.inside:first-of-type>div:nth-of-type(1)').css(& ...

JavaScript button for displaying and hiding all content in one click

I came across a tutorial on W3Schools that showed how to create collapsibles, and I thought it would be great to have buttons that could expand or collapse all at once. I've been trying to implement this feature using the code provided in the tutorial ...

The color of the progress bar in JS is not displaying properly

My work involves using jQuery to manipulate progress bars. The issue I am facing is defining standard colors that should be displayed on the progress bar based on the value received. Here is my code: var defaultSegmentColors = ['#FF6363', &ap ...

Engaging JavaScript Navigation

I am looking to create an interactive JavaScript menu or image map where users can press down, highlight, and hit enter on four different items to reveal hidden messages. However, I struggle with JavaScript and could really use some assistance. I have alr ...

Testing the Limits of CSS Hover

I'm having trouble figuring out how to implement this. When hovering, a yellow square/background should appear with an offset similar to the one in the image below. I am utilizing bootstrap for this project. Any assistance or guidance would be greatl ...