Create a dropdown accordion FAQ page

Reading Time: 4 minutes 🕑

Last updated: September 27, 2022.

In this tutorial, we are going to cover how to create an interactive FAQ page with answers that can be expanded and collapsed by a user.

All we will use to create the dropdown FAQ is HTML, CSS and JavaScript.

The finished project looks like this:

Preview of completed project
Table of contents

The HTML markup

The HTML for this project is minimal:

<h1>Frequently asked questions</h1>
<div id="FAQs-container"></div>

No questions and answers appear in the markup.

This is because these will be inserted into the DOM inside a HTML template using JavaScript.

There a two advantages to structuring the workflow this way:

  • Data can be imported from an external server
  • The one HTML template in JavaScript sets the HTML for all questions and answers

The JavaScript

Populating questions and answers

The questions and answers are stored in an array of objects in JavaScript.

Each object has two properties: question and answer:

const faqData = [
    {
        question: "What is lorem ipsum?",
        answer: "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur."
    },
    {
        question: "Can I see a Game of Thrones ipsum?",
        answer: "Hodor. Hodor hodor, hodor. Hodor hodor hodor hodor hodor. Hodor. Hodor! Hodor hodor, hodor; hodor hodor hodor. Hodor. Hodor hodor; hodor hodor - hodor, hodor, hodor hodor. Hodor, hodor. Hodor. Hodor, hodor hodor hodor; hodor hodor; hodor hodor hodor! Hodor hodor HODOR! Hodor hodor... Hodor hodor hodor..."
    },
    {
        question: "Is a Trump ipsum possible?",
        answer: "Lorem Ipsum is the single greatest threat. We are not - we are not keeping up with other websites. Lorem Ipsum best not make any more threats to your website. It will be met with fire and fury like the world has never seen. Does everybody know that pig named Lorem Ipsum? An ‘extremely credible source’ has called my office and told me that Barack Obama’s placeholder text is a fraud."
    },
    {
        question: "How about an academic ipsum?",
        answer: "If one examines precultural libertarianism, one is faced with a choice: either accept rationalism or conclude that context is a product of the masses, given that Marx’s essay on precultural libertarianism is invalid. The subject is contextualised into a precapitalist dematerialism that includes culture as a reality."
    },
    {
        question: "Is a Breaking Bad ipsum also possible?",
        answer: "A business big enough that it could be listed on the NASDAQ goes belly up. Disappears! It ceases to exist without me. No, you clearly don't know who you're talking to, so let me clue you in. I am not in danger, Skyler. I AM the danger! A guy opens his door and gets shot and you think that of me? No. I am the one who knocks!"},
    {   
        question: "What does a hipster ipsum look like?",
        answer: "Lorem ipsum dolor amet mustache knausgaard +1, blue bottle waistcoat tbh semiotics artisan synth stumptown gastropub cornhole celiac swag. Brunch raclette vexillologist post-ironic glossier ennui XOXO mlkshk godard pour-over blog tumblr humblebrag. Blue bottle put a bird on it twee prism biodiesel brooklyn. Blue bottle ennui tbh succulents."
    },
];

The following code extracts the data and appends it to the DOM.

The map() method is used to iterate through each object in the array. For each iteration, a template string containing the content is appended to the DOM inside an <article> element:

const faqsContainer = document.getElementById('FAQs-container');

faqData.map((item) => {

    let faqItem = document.createElement('article');
    faqItem.classList.add('faq-item');

    let markup = `
            <div class="item-question">
                <span class="question-text">${item.question}</span>
                <span class="arrows-container">
                    <span class="expand">▼</span>
                    <span class="close">▲</span>
                </span>
            </div>
            <div class="item-answer">
                <span>${item.answer}</span>
            </div>
    `;
    
    faqItem.innerHTML = markup;
    faqsContainer.append(faqItem);
});

Toggling a 'show-answer' class

After appending the data to the DOM, you want to add an event listener to the arrows-container class element.

When a user clicks this, you want a 'show-answer' class to be added to the <article> element that contains the respective question and answer.

You can do this using the following code, which uses the currentTarget property on the event parameter to select the arrow container clicked by the user. And then then parentElement to traverse upwards:

const toggleButtons = document.querySelectorAll('.arrows-container');
    
toggleButtons.forEach(button => {
    button.addEventListener('click', function(e) {
        const faqItem = e.currentTarget.parentElement.parentElement;
        faqItem.classList.toggle("show-answer");
    });
});

The CSS

The trick for this project is in using the 'show-answer' class toggled by a user to expand and close question answers.

Before doing that, here’s the CSS that will make all answers hidden upon page load. It also hides the ‘close’ arrow, which should only be visible when an answer is open:


@import url('https://fonts.googleapis.com/css2?family=Open+Sans&display=swap');

body {
    background: whitesmoke;

    /* Flex to list articles vertically */
    display: flex;
    flex-direction: column;
    align-items: center;
    font-family: 'Open Sans', sans-serif;
}
h1 {
    color: #143d59;
    margin-bottom: 1rem;
    text-align: center;
}
.faq-item {
    width: 40vw;
    min-width: 350px;
    margin-top: 1rem;
}
.item-question {
    background: #143d59;
    color: whitesmoke;
    font-size: 1rem;
    padding: 1.2rem;
    display: flex;
    justify-content: space-between;
    align-items: center;
}
.question-text {
    display: inline-block;
}
.arrows-container {
    margin: 0.4rem;
}
.item-answer {
    display: none;
    color: whitesmoke;
    padding: 2rem;
}
.close {
    display: none;
}

Now for the ‘trick’: make the item answer and close arrow visible when they are children of a 'show-answer' element.

This way, the styles only apply when the 'show-answer' element is present on a parent <article> element.

You’ll also want to style the expand arrow to be non-visible when this is the case:

.show-answer .item-answer {
    display: block;
    background: #4b5f6d;
}
.show-answer .close {
    display: inline;
}
.show-answer .expand {
    display: none;
}

Now, when'show-answer' is toggled, it will have the effect of making the answer to a question visible and changing the arrow when clicked once.

And when it is clicked again, the question answer will no longe be visible and the expand arrow will reappear.

Summary

In this project, we have seen how to create an interactive FAQ section using HTML, CSS and vanilla JavaScript.

Interested in learning how to build more web projects? Visit our projects section for more free tutorials!