This code emulates the accordion layout here, and uses Bootstrap 5.

See the Pen
Accordion with categories – Bootstrap 5
by Laura Sage (@ThePixelPixie)
on CodePen.

   <span style="display: inline-block; width: 0px; overflow: hidden; line-height: 0;" data-mce-type="bookmark" class="mce_SELRES_start"></span><div class="faq-container container content mb-3">
      <div class="row gx-5 justify-content-center">
         <div class="col-12 p-5 content">
            <section class="cd-faq js-cd-faq">
               <ul class="cd-faq__categories m-0 p-0">
                  <?php $terms = get_terms( array("taxonomy" => "category") );
                  foreach ($terms as $term) :
                     $posts = get_posts(["post_type" => "faq", "cat" => $term->term_id, "post_status" => "publish"]);
                  ?>
                  <li><a class="cd-faq__category cd-faq__category-selected truncate" href="#<?php echo $term->slug; ?>"><?php echo $term->name; ?></a></li>
                     <?php wp_reset_postdata();
                  endforeach; ?>
               </ul> <!-- cd-faq__categories -->

               <div class="cd-faq__items">
                  <?php $terms2 = get_terms( array("taxonomy" => "category") );
                     foreach ($terms2 as $term2) : 
                     $posts2 = get_posts(["post_type" => "faq", "cat" => $term2->term_id, "post_status" => "publish"]);
                  ?>
                  <ul id="<?php echo $term2->slug; ?>" class="cd-faq__group m-0 p-0">
                     <li class="cd-faq__title">
                        <h2><?php echo $term2->name; ?></h2>
                     </li>
                     <?php foreach($posts2 as $post2) :
                        $content = apply_filters('the_content',$post2->post_content);
                     ?>
                     <li class="cd-faq__item">
                        <a class="cd-faq__trigger" href="#0"><span><?php echo $post2->post_title; ?></span></a>
                        <div class="cd-faq__content">
                           <div class="text-component">
                              <?php echo $content; ?>
                           </div>
                        </div>
                     </li>
                  <?php endforeach; ?>
                  </ul>
               <?php wp_reset_postdata();
                  endforeach; ?>
               </div>

               <a href="#0" class="cd-faq__close-panel text-replace">Close</a>
               <div class="cd-faq__overlay" aria-hidden="true"></div>
            </section>
         </div>
      </div>
   </div><span style="display: inline-block; width: 0px; overflow: hidden; line-height: 0;" data-mce-type="bookmark" class="mce_SELRES_end"></span>

Here’s the CSS you need to add:

@import "https://fonts.googleapis.com/css?family=Open+Sans:400,300,600,700";

body {
  font-size: 15px;
  background-color: #f2f2f2;
}

ul.cd-faq__categories,
ul.cd-faq__group {
  list-style: none;
  border: 0;
}

.cd-faq {
  border: 0;
  display: block;
  box-shadow: 0 1px 2px rgba(0, 0, 0, 0.085), 0 1px 8px rgba(0, 0, 0, 0.1);
}
.cd-faq::before {
  content: "mobile";
  display: none;
}
.cd-faq a {
  text-decoration: none;
  font-weight: 300;
  transition: all .15s ease-in;
}

.cd-faq a:hover {
  color: rgba(255,255,255,.7);
  font-weight: 600;
}

.cd-faq .cd-faq__trigger:hover {
  color: #8f3985;
}
@media (min-width: 768px) {
  .cd-faq {
    position: relative;
    box-shadow: none;
    display: -ms-flexbox;
    display: flex;
  }
  .cd-faq::before {
    content: "desktop";
  }
}
@media (min-width: 768px) {
  .cd-faq__categories {
    position: -webkit-sticky;
    position: sticky;
    -ms-flex-item-align: start;
    align-self: flex-start;
    -ms-flex-negative: 0;
    flex-shrink: 0;
    top: 20px;
    width: 20%;
    box-shadow: 0 1px 2px rgba(0, 0, 0, 0.085), 0 1px 8px rgba(0, 0, 0, 0.1);
    margin-top: 1.25rem;
  }
}
@media (min-width: 992px) {
  .cd-faq__categories {
    width: 200px;
  }
}
.cd-faq__category {
  position: relative;
  display: block;
  height: 50px;
  line-height: 50px;
  padding: 0 2rem 0 1.05rem;
  color: #fff;
  background-color: #4e545a;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  border-bottom: 1px solid #565c63;
}
.cd-faq__category::before,
.cd-faq__category::after {
  content: "";
  position: absolute;
  top: 50%;
  right: 16px;
  display: inline-block;
  height: 1px;
  width: 10px;
  background-color: #7e868f;
}
.cd-faq__category::after {
  -webkit-transform: rotate(90deg);
  -ms-transform: rotate(90deg);
  transform: rotate(90deg);
}
li:last-child .cd-faq__category {
  border-bottom: none;
}
@media (min-width: 768px) {
  .cd-faq__category {
    font-weight: 600;
    padding: 0 1.25rem;
    transition: background 0.2s;
  }
  .cd-faq__category::before,
  .cd-faq__category::after {
    display: none;
  }
  .cd-faq__category:hover {
    background: #565c63;
    color: #fff;
  }
}
@media (min-width: 992px) {
  .cd-faq__category::before {
    display: block;
    top: 0;
    right: auto;
    left: 0;
    height: 100%;
    width: 3px;
    background-color: #8f3985;
    opacity: 0;
    transition: opacity 0.2s;
  }
}
@media (min-width: 992px) {
  .cd-faq__category-selected {
    background: #3f4348;
  }
  .cd-faq__category-selected:hover {
    background: #3f4348;
  }
  .cd-faq__category-selected::before {
    opacity: 1;
  }
}
.cd-faq__items {
  position: fixed;
  z-index: 1;
  height: 100%;
  width: 90%;
  top: 0;
  right: 0;
  background: #fff;
  padding: 0 1.25rem 1.25rem;
  overflow: auto;
  -webkit-overflow-scrolling: touch;
  -webkit-backface-visibility: hidden;
  backface-visibility: hidden;
  -webkit-transform: translateZ(0) translateX(100%);
  transform: translateZ(0) translateX(100%);
  transition: -webkit-transform 0.3s;
  transition: transform 0.3s;
  transition: transform 0.3s, -webkit-transform 0.3s;
}
@media (min-width: 768px) {
  .cd-faq__items {
    position: static;
    height: auto;
    width: auto;
    -ms-flex-positive: 1;
    flex-grow: 1;
    overflow: visible;
    -webkit-transform: translateX(0);
    -ms-transform: translateX(0);
    transform: translateX(0);
    padding: 0 0 0 0.75rem;
    background: 0 0;
  }
}
.cd-faq__items--slide-in {
  -webkit-transform: translateX(0);
  -ms-transform: translateX(0);
  transform: translateX(0);
}
html:not(.js) .cd-faq__items {
  position: static;
  height: auto;
  width: 100%;
  -webkit-transform: translateX(0);
  -ms-transform: translateX(0);
  transform: translateX(0);
}
.cd-faq__group {
  display: none;
}
@media (min-width: 768px) {
  .cd-faq__group {
    display: block;
    padding-top: 1px;
  }
}
html:not(.js) .cd-faq__group,
.cd-faq__group--selected {
  display: block;
}
.cd-faq__title {
  margin: 1.25rem 0;
}
.cd-faq__title h2 {
  text-transform: uppercase;
  font-weight: 700;
  color: #3f4348;
}
@media (min-width: 768px) {
  .cd-faq__title {
    margin-bottom: 0.75rem;
  }
}
@media (min-width: 768px) {
  .cd-faq__item {
    background: #fff;
    margin-bottom: 0.25em;
    box-shadow: 0 1px 2px rgba(0, 0, 0, 0.08);
    transition: box-shadow 0.2s;
  }
  .cd-faq__item:hover {
    box-shadow: undefined;
  }
  @media (min-width: 768px) {
    .cd-faq__item:hover {
      box-shadow: 0 1px 10px #6b7d8e;
    }
  }
  .cd-faq__item:last-of-type {
    margin-bottom: 0;
  }
}
.cd-faq__trigger {
  display: block;
  position: relative;
  margin: 1.25rem 0 0.5rem;
  color: #8f3985;
}
@media (min-width: 768px) {
  .cd-faq__trigger {
    font-weight: 300;
    margin: 0;
    padding: 0.75rem 2rem 0.75rem 0.75rem;
  }
  .cd-faq__trigger::before,
  .cd-faq__trigger::after {
    content: "";
    position: absolute;
    right: 24px;
    top: 50%;
    height: 2px;
    width: 13px;
    background: #8f3985;
    -webkit-backface-visibility: hidden;
    backface-visibility: hidden;
    transition: -webkit-transform 0.2s;
    transition: transform 0.2s;
    transition: transform 0.2s, -webkit-transform 0.2s;
  }
  .cd-faq__trigger::before {
    -webkit-transform: rotate(45deg);
    -ms-transform: rotate(45deg);
    transform: rotate(45deg);
    right: 32px;
  }
  .cd-faq__trigger::after {
    -webkit-transform: rotate(-45deg);
    -ms-transform: rotate(-45deg);
    transform: rotate(-45deg);
  }
  .cd-faq__item-visible .cd-faq__trigger::before {
    -webkit-transform: rotate(-45deg);
    -ms-transform: rotate(-45deg);
    transform: rotate(-45deg);
  }
  .cd-faq__item-visible .cd-faq__trigger::after {
    -webkit-transform: rotate(45deg);
    -ms-transform: rotate(45deg);
    transform: rotate(45deg);
  }
}
.cd-faq__content p {
  color: #6b7d8e;
}
@media (min-width: 768px) {
  .cd-faq__content {
    display: none;
    padding: 0 0.75em;
    overflow: hidden;
  }
  .cd-faq__content .text-component {
    padding-bottom: 1.25rem;
  }
}
.cd-faq__content--visible {
  display: block;
}
@media (min-width: 768px) {
  html:not(.js) .cd-faq__content {
    display: block;
  }
}
.cd-faq__close-panel {
  position: fixed;
  z-index: 2;
  display: block;
  top: 5px;
  right: -40px;
  height: 40px;
  width: 40px;
  -webkit-transform: translateZ(0);
  transform: translateZ(0);
  -webkit-backface-visibility: hidden;
  backface-visibility: hidden;
  transition: right 0.3s;
  color: #fff;
}
.cd-faq__close-panel::before,
.cd-faq__close-panel::after {
  content: '';
  position: absolute;
  top: 16px;
  left: 12px;
  display: inline-block;
  height: 3px;
  width: 18px;
  background: #6b7d8e;
}
.cd-faq__close-panel::before {
  -webkit-transform: rotate(45deg);
  -ms-transform: rotate(45deg);
  transform: rotate(45deg);
}
.cd-faq__close-panel::after {
  -webkit-transform: rotate(-45deg);
  -ms-transform: rotate(-45deg);
  transform: rotate(-45deg);
}
@media (min-width: 768px) {
  .cd-faq__close-panel {
    display: none;
  }
}
.cd-faq__close-panel--move-left {
  right: 1.25rem;
  transition-delay: 0.3s;
}
.cd-faq__overlay {
  position: fixed;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  background-color: #4e545a;
  visibility: hidden;
  opacity: 0;
  transition: opacity 0.3s, visibility 0.3s;
}
@media (min-width: 768px) {
  .cd-faq__overlay {
    display: none;
  }
}
.cd-faq__overlay--is-visible {
  visibility: visible;
  opacity: 1;
}
.cd-header {
  height: 180px;
  background-color: #8f3985;
}
@media (min-width: 992px) {
  .cd-header {
    height: 240px;
  }
}
.cd-header h1 {
  font-size: 1.728rem;
  font-weight: 300;
  color: #fff;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}
@media (min-width: 992px) {
  .cd-header {
    height: 240px;
  }
}
.cd-article-link {
  color: #586626;
}

And finally, the js you need to add at the bottom of the file, before

// Utility function
function Util() {}

/* 
	class manipulation functions
*/
Util.hasClass = function (el, className) {
  if (el.classList) return el.classList.contains(className);
  else
    return !!el.className.match(new RegExp("(\\s|^)" + className + "(\\s|$)"));
};

Util.addClass = function (el, className) {
  var classList = className.split(" ");
  if (el.classList) el.classList.add(classList[0]);
  else if (!Util.hasClass(el, classList[0])) el.className += " " + classList[0];
  if (classList.length > 1) Util.addClass(el, classList.slice(1).join(" "));
};

Util.removeClass = function (el, className) {
  var classList = className.split(" ");
  if (el.classList) el.classList.remove(classList[0]);
  else if (Util.hasClass(el, classList[0])) {
    var reg = new RegExp("(\\s|^)" + classList[0] + "(\\s|$)");
    el.className = el.className.replace(reg, " ");
  }
  if (classList.length > 1) Util.removeClass(el, classList.slice(1).join(" "));
};

Util.toggleClass = function (el, className, bool) {
  if (bool) Util.addClass(el, className);
  else Util.removeClass(el, className);
};

Util.setAttributes = function (el, attrs) {
  for (var key in attrs) {
    el.setAttribute(key, attrs[key]);
  }
};

/* 
  DOM manipulation
*/
Util.getChildrenByClassName = function (el, className) {
  var children = el.children,
    childrenByClass = [];
  for (var i = 0; i < el.children.length; i++) {
    if (Util.hasClass(el.children[i], className))
      childrenByClass.push(el.children[i]);
  }
  return childrenByClass;
};

/* 
	Animate height of an element
*/
Util.setHeight = function (start, to, element, duration, cb) {
  var change = to - start,
    currentTime = null;

  var animateHeight = function (timestamp) {
    if (!currentTime) currentTime = timestamp;
    var progress = timestamp - currentTime;
    var val = parseInt((progress / duration) * change + start);
    // console.log(val);
    element.setAttribute("style", "height:" + val + "px;");
    if (progress < duration) {
      window.requestAnimationFrame(animateHeight);
    } else {
      cb();
    }
  };

  //set the height of the element before starting animation -> fix bug on Safari
  element.setAttribute("style", "height:" + start + "px;");
  window.requestAnimationFrame(animateHeight);
};

/* 
	Smooth Scroll
*/

Util.scrollTo = function (final, duration, cb) {
  var start = window.scrollY || document.documentElement.scrollTop,
    currentTime = null;

  var animateScroll = function (timestamp) {
    if (!currentTime) currentTime = timestamp;
    var progress = timestamp - currentTime;
    if (progress > duration) progress = duration;
    var val = Math.easeInOutQuad(progress, start, final - start, duration);
    window.scrollTo(0, val);
    if (progress < duration) {
      window.requestAnimationFrame(animateScroll);
    } else {
      cb && cb();
    }
  };

  window.requestAnimationFrame(animateScroll);
};

/* 
  Focus utility classes
*/

//Move focus to an element
Util.moveFocus = function (element) {
  if (!element) element = document.getElementsByTagName("body")[0];
  element.focus();
  if (document.activeElement !== element) {
    element.setAttribute("tabindex", "-1");
    element.focus();
  }
};

/* 
  Misc
*/

Util.getIndexInArray = function (array, el) {
  return Array.prototype.indexOf.call(array, el);
};

Util.cssSupports = function (property, value) {
  if ("CSS" in window) {
    return CSS.supports(property, value);
  } else {
    var jsProperty = property.replace(/-([a-z])/g, function (g) {
      return g[1].toUpperCase();
    });
    return jsProperty in document.body.style;
  }
};

/* 
	Polyfills
*/
//Closest() method
if (!Element.prototype.matches) {
  Element.prototype.matches =
    Element.prototype.msMatchesSelector ||
    Element.prototype.webkitMatchesSelector;
}

if (!Element.prototype.closest) {
  Element.prototype.closest = function (s) {
    var el = this;
    if (!document.documentElement.contains(el)) return null;
    do {
      if (el.matches(s)) return el;
      el = el.parentElement || el.parentNode;
    } while (el !== null && el.nodeType === 1);
    return null;
  };
}

//Custom Event() constructor
if (typeof window.CustomEvent !== "function") {
  function CustomEvent(event, params) {
    params = params || { bubbles: false, cancelable: false, detail: undefined };
    var evt = document.createEvent("CustomEvent");
    evt.initCustomEvent(
      event,
      params.bubbles,
      params.cancelable,
      params.detail
    );
    return evt;
  }

  CustomEvent.prototype = window.Event.prototype;

  window.CustomEvent = CustomEvent;
}

/* 
	Animation curves
*/
Math.easeInOutQuad = function (t, b, c, d) {
  t /= d / 2;
  if (t < 1) return (c / 2) * t * t + b;
  t--;
  return (-c / 2) * (t * (t - 2) - 1) + b;
};

(function () {
  // FAQ Template - by CodyHouse.co
  var FaqTemplate = function (element) {
    this.element = element;
    this.sections = this.element.getElementsByClassName("cd-faq__group");
    this.triggers = this.element.getElementsByClassName("cd-faq__trigger");
    this.faqContainer = this.element.getElementsByClassName("cd-faq__items")[0];
    this.faqsCategoriesContainer = this.element.getElementsByClassName(
      "cd-faq__categories"
    )[0];
    this.faqsCategories = this.faqsCategoriesContainer.getElementsByClassName(
      "cd-faq__category"
    );
    this.faqOverlay = this.element.getElementsByClassName("cd-faq__overlay")[0];
    this.faqClose = this.element.getElementsByClassName(
      "cd-faq__close-panel"
    )[0];
    this.scrolling = false;
    initFaqEvents(this);
  };

  function initFaqEvents(faqs) {
    // click on a faq category
    faqs.faqsCategoriesContainer.addEventListener("click", function (event) {
      var category = event.target.closest(".cd-faq__category");
      if (!category) return;
      var mq = getMq(faqs),
        selectedCategory = category.getAttribute("href").replace("#", "");
      if (mq == "mobile") {
        // on mobile, open faq panel
        event.preventDefault();
        faqs.faqContainer.scrollTop = 0;
        Util.addClass(faqs.faqContainer, "cd-faq__items--slide-in");
        Util.addClass(faqs.faqClose, "cd-faq__close-panel--move-left");
        Util.addClass(faqs.faqOverlay, "cd-faq__overlay--is-visible");
        var selectedSection = faqs.faqContainer.getElementsByClassName(
          "cd-faq__group--selected"
        );
        if (selectedSection.length > 0) {
          Util.removeClass(selectedSection[0], "cd-faq__group--selected");
        }
        Util.addClass(
          document.getElementById(selectedCategory),
          "cd-faq__group--selected"
        );
      } else {
        // on desktop, scroll to section
        if (!window.requestAnimationFrame) return;
        event.preventDefault();
        var windowScrollTop =
          window.scrollY || document.documentElement.scrollTop;
        Util.scrollTo(
          document.getElementById(selectedCategory).getBoundingClientRect()
            .top +
            windowScrollTop +
            2,
          200
        );
      }
    });

    // on mobile -> close faq panel
    faqs.faqOverlay.addEventListener("click", function (event) {
      closeFaqPanel(faqs);
    });
    faqs.faqClose.addEventListener("click", function (event) {
      event.preventDefault();
      closeFaqPanel(faqs);
    });

    // on desktop -> toggle faq content visibility when clicking on the trigger element
    faqs.faqContainer.addEventListener("click", function (event) {
      if (getMq(faqs) != "desktop") return;
      var trigger = event.target.closest(".cd-faq__trigger");
      if (!trigger) return;
      event.preventDefault();
      var content = trigger.nextElementSibling,
        parent = trigger.closest("li"),
        bool = Util.hasClass(parent, "cd-faq__item-visible");

      Util.toggleClass(parent, "cd-faq__item-visible", !bool);

      //store initial and final height - animate faq content height
      Util.addClass(content, "cd-faq__content--visible");
      var initHeight = bool ? content.offsetHeight : 0,
        finalHeight = bool ? 0 : content.offsetHeight;

      if (window.requestAnimationFrame) {
        Util.setHeight(initHeight, finalHeight, content, 200, function () {
          heighAnimationCb(content, bool);
        });
      } else {
        heighAnimationCb(content, bool);
      }
    });

    if (window.requestAnimationFrame) {
      // on scroll -> update selected category
      window.addEventListener("scroll", function () {
        if (getMq(faqs) != "desktop" || faqs.scrolling) return;
        faqs.scrolling = true;
        window.requestAnimationFrame(updateCategory.bind(faqs));
      });
    }
  }

  function closeFaqPanel(faqs) {
    Util.removeClass(faqs.faqContainer, "cd-faq__items--slide-in");
    Util.removeClass(faqs.faqClose, "cd-faq__close-panel--move-left");
    Util.removeClass(faqs.faqOverlay, "cd-faq__overlay--is-visible");
  }

  function getMq(faqs) {
    //get MQ value ('desktop' or 'mobile')
    return window
      .getComputedStyle(faqs.element, "::before")
      .getPropertyValue("content")
      .replace(/'|"/g, "");
  }

  function updateCategory() {
    // update selected category -> show green rectangle to the left of the category
    var selected = false;
    for (var i = 0; i < this.sections.length; i++) {
      var top = this.sections[i].getBoundingClientRect().top,
        bool = top <= 0 && -1 * top < this.sections[i].offsetHeight;
      Util.toggleClass(
        this.faqsCategories[i],
        "cd-faq__category-selected",
        bool
      );
      if (bool) selected = true;
    }
    if (!selected)
      Util.addClass(this.faqsCategories[0], "cd-faq__category-selected");
    this.scrolling = false;
  }

  function heighAnimationCb(content, bool) {
    content.removeAttribute("style");
    if (bool) Util.removeClass(content, "cd-faq__content--visible");
  }

  var faqTemplate = document.getElementsByClassName("js-cd-faq"),
    faqArray = [];
  if (faqTemplate.length > 0) {
    for (var i = 0; i < faqTemplate.length; i++) {
      faqArray.push(new FaqTemplate(faqTemplate[i]));
    }
  }
})();
thePixelPixie.com - WordPress boutique

Subscribe and receive my FREE eBook


4 MISTAKES You’re Making on Your Website that are Costing You MONEY!

  • This field is for validation purposes and should be left unchanged.

The Author

Save

Ready to chat about how ThePixelPixie can help?