Learn how to create a modern FAQ accordion for websites using HTML, CSS, and JavaScript.
Table of Contents
If you’re building a website, having a clean and interactive FAQ (Frequently Asked Questions) section is important. It helps users find answers quickly and keeps your page organized.
In this blog, you’ll learn how to build a modern FAQ accordion using only HTML, CSS, and JavaScript. This design is easy to make and works well on all devices. We’ll also keep the code clean and beginner-friendly.
Prerequisites
Before we start, make sure you know the basics of:
- HTML (for structure)
- CSS (for styling)
- JavaScript (for interactivity)
- A text editor like VS Code
- A browser to test the output
Source Code
Step 1 (HTML Code):
To get started, we first need to create a basic HTML file. In this file, we will include the main structure for our FAQ section. Let's break down the HTML code step by step:
1. DOCTYPE & HTML Setup
<!DOCTYPE html>
<html lang="en">
- Declares the document as HTML5.
- lang="en" specifies the language as English.
2. Head Section
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Card-Based FAQ (Accordion)</title>
- charset="UTF-8" supports all common characters.
- viewport ensures responsive scaling on mobile devices.
- title appears in the browser tab.
3. Fonts & CSS
<link href="https://fonts.googleapis.com/css2?family=Be+Vietnam+Pro:wght@400;500;600;700&display=swap" rel="stylesheet">
<link rel="stylesheet" href="styles.css">
- Loads a Google Font: Be Vietnam Pro.
- Links an external CSS file styles.css where the layout and animation styles live.
4. Aurora Background Shapes
<div class="aurora-bg">
<div class="aurora-shape shape1"></div>
<div class="aurora-shape shape2"></div>
<div class="aurora-shape shape3"></div>
</div>
- Decorative background shapes for a glowing/gradient animated effect.
- Styled in CSS with animation or blur effects.
5. Main Container
<div class="faq-container" id="faq-container">
- Holds the entire FAQ content.
- Used for layout and JavaScript selection.
6. FAQ List View
<div class="faq-list-view" id="faq-list">
- Displays a list of FAQ cards.
- This is the main interactive list users first see.
Header of FAQ List
<div class="faq-main-header">
<h1>Frequently Asked Questions (FAQ)</h1>
<p>Find answers to your questions and get the help you need.</p>
</div>
- A heading and subtext for the FAQ section.
FAQ Cards
Each .faq-card is one FAQ item with:
- An icon (faq-card-icon)
- A title + description (faq-card-content)
- A right-arrow icon (faq-card-action-icon)
Example:
<div class="faq-card" data-faq-id="1" style="animation-delay: 0.4s;">
<div class="faq-card-icon">...svg icon...</div>
<div class="faq-card-content">
<h3>Premium Package</h3>
<p>Learn about what's included in our top-tier plan.</p>
</div>
<div class="faq-card-action-icon">...svg icon...</div>
</div>
- data-faq-id="1" uniquely identifies the FAQ for JS interaction.
- animation-delay gives staggered card entrance effect.
- Clicking on this will trigger an answer to show.
There are 3 such FAQ cards in your snippet.
7. FAQ Answer View
<div class="faq-answer-view" id="faq-answer-view">
- Hidden by default; shown when a question is clicked.
- Shows the full answer of the selected FAQ.
Header With Back Button
<div class="faq-answer-header">
<button class="faq-back-button" id="faq-back-button">...svg...</button>
<h3 id="answer-title"></h3>
</div>
- The back button allows users to return to the FAQ list.
- answer-title gets filled dynamically via JavaScript.
Body of Answer
<div class="faq-answer-body" id="answer-body"></div>
- Filled with JavaScript with the detailed answer content.
8. JavaScript File
<script src="script.js"></script>
- Loads the logic from script.js:
- Handles animations
- Toggles between list view and answer view
- Controls back button functionality
Step 2 (CSS Code):
Now, make your FAQ section look clean and modern with CSS.
1. :root — CSS Variables
:root {
--bg-color: #0d1117;
--card-bg: rgba(255, 255, 255, 0.05);
--card-border: rgba(255, 255, 255, 0.1);
--card-hover-bg: rgba(255, 255, 255, 0.1);
--text-primary: #f0f6fc;
--text-secondary: #8b949e;
--accent-color: #58a6ff;
--glow-color-1: #58a6ff;
--glow-color-2: #3081f7;
}
This block defines custom properties (variables) that make it easy to update the theme. For example:
- --bg-color = background color of the whole page.
- --card-bg = card background with slight transparency.
- --accent-color = primary highlight color (used for hover effects, buttons, icons).
2. body — Global Styling
body {
font-family: 'Be Vietnam Pro', sans-serif;
background-color: var(--bg-color);
color: var(--text-primary);
display: flex;
justify-content: center;
align-items: center;
min-height: 100vh;
margin: 0;
padding: 2rem;
overflow: hidden;
position: relative;
}
Sets:
- Full-screen dark background.
- Centered content.
- Responsive padding.
- Prevents overflow (e.g., scrollbars).
- Uses custom font.
- Sets text color globally.
3. Aurora Background Effects
.aurora-bg {
position: fixed;
width: 100%;
height: 100%;
z-index: -1;
}
.aurora-shape {
position: absolute;
border-radius: 50%;
filter: blur(100px);
opacity: 0.3;
}
- Adds animated glowing blobs in the background.
- Uses blur + transparency for a glowing effect.
Individual Shapes
.shape1 { background-color: var(--accent-color); animation: move-aurora 15s; }
.shape2 { background-color: #e81cff; animation: move-aurora 20s reverse; }
.shape3 { background-color: #42e2b8; animation: move-aurora 18s; }
- Different colors, sizes, and positions.
- Animated with @keyframes move-aurora.
4. FAQ Container
.faq-container {
max-width: 1000px;
height: 700px;
perspective: 2000px;
}
- Holds the entire FAQ block.
- Adds a 3D-like perspective for transitions (like flipping/rotating).
5. FAQ List View
.faq-list-view {
transition: opacity 0.5s, transform 0.5s;
display: flex;
flex-direction: column;
}
- This is the default view, showing all question cards.
Header
.faq-main-header {
animation: header-entry 0.8s;
opacity: 0;
transform: translateY(20px);
}
@keyframes header-entry {
to {
opacity: 1;
transform: translateY(0);
}
}
- Animates in the heading when loaded.
6. FAQ Cards
.faq-card {
background-color: var(--card-bg);
border-radius: 18px;
padding: 32px;
margin-bottom: 20px;
opacity: 0;
transform: translateY(30px);
animation: card-entry 0.6s forwards;
}
- Cards fade and slide into view.
- Have glassmorphic blur (backdrop-filter).
- Hover effects:
.faq-card:hover { transform: translateY(-8px); background-color: var(--card-hover-bg); box-shadow: 0 16px 40px rgba(0, 0, 0, 0.2); } - Slight lift-up effect.
- Background glow appears using ::before.
Icon, Text, Arrow
.faq-card-icon { background: rgba(88, 166, 255, 0.1); border-radius: 50%; }
.faq-card-content h3 { color: var(--text-primary); }
.faq-card-content p { color: var(--text-secondary); }
.faq-card-action-icon:hover { color: var(--accent-color); transform: translateX(5px); }
7. FAQ Answer View
.faq-answer-view {
background: var(--bg-color);
opacity: 0;
visibility: hidden;
transform: rotateY(90deg) scale(0.8);
transition: transform 0.6s, opacity 0.6s, visibility 0.6s;
}
- Hidden by default.
- Flips into view when activated (handled by JS).
Back Button
.faq-back-button:hover {
background-color: rgba(255, 255, 255, 0.1);
}
- Simple hover glow.
- Clicking this returns to the FAQ list view.
8. Accordion Styles
.accordion-item {
border-bottom: 1px solid var(--card-border);
}
.accordion-question {
display: flex;
justify-content: space-between;
cursor: pointer;
}
.accordion-answer {
max-height: 0;
overflow: hidden;
transition: max-height 0.4s ease-out;
}
.accordion-item.active .accordion-answer {
max-height: 300px; /* set dynamically or with JS */
}
- Expand/collapse mechanism.
- When .accordion-item has .active, the answer opens.
9. View State Toggle Classes
.is-answer-visible .faq-list-view {
opacity: 0;
pointer-events: none;
}
.is-answer-visible .faq-answer-view {
opacity: 1;
visibility: visible;
transform: rotateY(0) scale(1);
}
- JS adds this class to show the answer view.
- Helps transition between views.
10. Responsive (Mobile) Styles
@media (max-width: 768px) {
body {
align-items: flex-start;
}
.faq-container {
max-height: 90vh;
}
.faq-main-header h1 {
font-size: 2rem;
}
}
- Reduces padding, font size, and height for small screens.
- Makes layout more mobile-friendly.
:root {
--bg-color: #0d1117;
--card-bg: rgba(255, 255, 255, 0.05);
--card-border: rgba(255, 255, 255, 0.1);
--card-hover-bg: rgba(255, 255, 255, 0.1);
--text-primary: #f0f6fc;
--text-secondary: #8b949e;
--accent-color: #58a6ff;
--glow-color-1: #58a6ff;
--glow-color-2: #3081f7;
}
body {
font-family: 'Be Vietnam Pro', sans-serif;
background-color: var(--bg-color);
color: var(--text-primary);
display: flex;
justify-content: center;
align-items: center;
min-height: 100vh;
margin: 0;
padding: 2rem;
box-sizing: border-box;
overflow: hidden;
position: relative;
}
/* --- Animated Aurora Background --- */
.aurora-bg {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
overflow: hidden;
z-index: -1;
}
.aurora-shape {
position: absolute;
border-radius: 50%;
filter: blur(100px);
opacity: 0.3;
}
.shape1 {
width: 400px;
height: 400px;
background-color: var(--accent-color);
top: -150px;
left: -150px;
animation: move-aurora 15s infinite alternate ease-in-out;
}
.shape2 {
width: 300px;
height: 300px;
background-color: #e81cff;
bottom: -100px;
right: -100px;
animation: move-aurora 20s infinite alternate-reverse ease-in-out;
}
.shape3 {
width: 250px;
height: 250px;
background-color: #42e2b8;
bottom: 50%;
right: 40%;
animation: move-aurora 18s infinite alternate ease-in-out;
}
@keyframes move-aurora {
from {
transform: translate(0, 0) rotate(0deg);
}
to {
transform: translate(200px, 100px) rotate(180deg);
}
}
.faq-container {
width: 100%;
max-width: 1000px;
height: 700px;
position: relative;
perspective: 2000px;
}
.faq-list-view {
position: absolute;
width: 100%;
height: 100%;
transition: opacity 0.5s ease, transform 0.5s ease;
transform: scale(1);
opacity: 1;
display: flex;
flex-direction: column;
}
.faq-main-header {
text-align: center;
margin-bottom: 40px;
animation: header-entry 0.8s 0.2s cubic-bezier(0.25, 1, 0.5, 1) forwards;
opacity: 0;
transform: translateY(20px);
}
.faq-main-header h1 {
font-size: 3rem;
font-weight: 700;
margin: 0;
letter-spacing: -1px;
}
.faq-main-header p {
font-size: 1.1rem;
color: var(--text-secondary);
margin-top: 8px;
}
@keyframes header-entry {
to {
opacity: 1;
transform: translateY(0);
}
}
.faq-card {
position: relative;
background-color: var(--card-bg);
backdrop-filter: blur(20px);
-webkit-backdrop-filter: blur(20px);
border-radius: 18px;
padding: 32px;
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1);
border: 1px solid var(--card-border);
cursor: pointer;
display: flex;
align-items: center;
margin-bottom: 20px;
opacity: 0;
transform: translateY(30px);
animation: card-entry 0.6s cubic-bezier(0.25, 1, 0.5, 1) forwards;
transition: transform 0.3s ease, background-color 0.3s ease,
box-shadow 0.3s ease;
}
@keyframes card-entry {
to {
opacity: 1;
transform: translateY(0);
}
}
.faq-card::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
border-radius: 18px;
border: 2px solid transparent;
background: linear-gradient(90deg, var(--glow-color-1), var(--glow-color-2))
border-box;
-webkit-mask: linear-gradient(#fff 0 0) padding-box, linear-gradient(#fff 0 0);
-webkit-mask-composite: destination-out;
mask-composite: exclude;
opacity: 0;
transition: opacity 0.4s ease;
}
.faq-card:hover {
transform: translateY(-8px);
background-color: var(--card-hover-bg);
box-shadow: 0 16px 40px rgba(0, 0, 0, 0.2);
}
.faq-card:hover::before {
opacity: 1;
}
.faq-card-icon {
flex-shrink: 0;
width: 48px;
height: 48px;
background-color: rgba(88, 166, 255, 0.1);
color: var(--accent-color);
border-radius: 50%;
display: flex;
justify-content: center;
align-items: center;
margin-right: 24px;
}
.faq-card-icon svg {
width: 24px;
height: 24px;
}
.faq-card-content {
flex-grow: 1;
}
.faq-card-content h3 {
margin: 0 0 4px;
font-size: 1.25rem;
font-weight: 600;
color: var(--text-primary);
}
.faq-card-content p {
margin: 0;
color: var(--text-secondary);
font-size: 0.95rem;
}
.faq-card-action-icon {
flex-shrink: 0;
margin-left: 24px;
transition: transform 0.3s ease, color 0.3s ease;
color: var(--text-secondary);
}
.faq-card-action-icon svg {
width: 24px;
height: 24px;
}
.faq-card:hover .faq-card-action-icon {
transform: translateX(5px);
color: var(--accent-color);
}
.faq-answer-view {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: var(--bg-color);
border-radius: 16px;
box-shadow: 0 20px 50px rgba(0, 0, 0, 0.2);
display: flex;
flex-direction: column;
overflow: hidden;
visibility: hidden;
opacity: 0;
transform: rotateY(90deg) scale(0.8);
transition: transform 0.6s cubic-bezier(0.25, 1, 0.5, 1), opacity 0.6s ease,
visibility 0.6s;
border: 1px solid var(--card-border);
}
.faq-answer-header {
padding: 24px 32px;
display: flex;
align-items: center;
border-bottom: 1px solid var(--card-border);
flex-shrink: 0;
}
.faq-back-button {
background: none;
border: none;
cursor: pointer;
padding: 8px;
margin-right: 16px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
transition: background-color 0.2s ease;
}
.faq-back-button:hover {
background-color: rgba(255, 255, 255, 0.1);
}
.faq-back-button svg {
width: 24px;
height: 24px;
color: var(--text-secondary);
}
.faq-answer-header h3 {
margin: 0;
font-size: 1.25rem;
font-weight: 600;
}
.faq-answer-body {
padding: 16px 32px;
overflow-y: auto;
flex-grow: 1;
}
/* --- Accordion Styles --- */
.accordion-item {
border-bottom: 1px solid var(--card-border);
}
.accordion-item:last-child {
border-bottom: none;
}
.accordion-question {
display: flex;
justify-content: space-between;
align-items: center;
padding: 20px 0;
cursor: pointer;
}
.accordion-question h4 {
margin: 0;
font-size: 1.05rem;
font-weight: 500;
color: var(--text-primary);
}
.accordion-icon {
width: 20px;
height: 20px;
flex-shrink: 0;
margin-left: 16px;
transition: transform 0.3s ease;
color: var(--text-secondary);
}
.accordion-answer {
max-height: 0;
overflow: hidden;
transition: max-height 0.4s ease-out;
}
.accordion-answer p {
font-size: 1rem;
line-height: 1.6;
color: var(--text-secondary);
padding-bottom: 20px;
margin: 0;
}
.accordion-item.active .accordion-icon {
transform: rotate(180deg);
}
.accordion-item.active .accordion-question h4 {
color: var(--accent-color);
}
/* Class to manage view state */
.is-answer-visible .faq-list-view {
opacity: 0;
transform: scale(0.95);
pointer-events: none;
}
.is-answer-visible .faq-answer-view {
opacity: 1;
visibility: visible;
transform: rotateY(0) scale(1);
}
@media (max-width: 768px) {
body {
padding: 1rem;
align-items: flex-start;
}
.faq-container {
height: auto;
max-height: 90vh;
perspective: none;
}
.faq-main-header h1 {
font-size: 2rem;
}
.faq-card {
padding: 24px;
}
.faq-answer-view {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
border-radius: 0;
z-index: 1000;
}
} Step 3 (JavaScript Code):
Now we will use JavaScript to make the accordion interactive. When a user clicks on a question, the answer will smoothly slide open or close. Below is a step-by-step explanation of the JavaScript code:
1. Waits for the DOM to Load
document.addEventListener('DOMContentLoaded', function () {
- Ensures all HTML content is fully loaded before the script runs.
2. Grabs Key DOM Elements
const faqContainer = document.getElementById('faq-container');
const questionCards = document.querySelectorAll('.faq-card');
const backButton = document.getElementById('faq-back-button');
const answerTitleEl = document.getElementById('answer-title');
const answerBodyEl = document.getElementById('answer-body');
These variables are used to reference different parts of the FAQ UI, such as:
- The main container
- The clickable FAQ cards
- The back button
- The dynamic FAQ answer title and body area
3. Stores FAQ Content
const faqData = {
1: { title: 'Premium Package', items: [...] },
2: { title: 'Subscription & Billing', items: [...] },
3: { title: 'Free Trial Information', items: [...] }
};
- This is a JavaScript object (like a mini database) that stores all the FAQ content for each card.
- Each key (1, 2, 3) represents a card with its own title and multiple Q&A items.
4. Accordion Functionality
function setupAccordionListeners() {
const accordionItems = answerBodyEl.querySelectorAll('.accordion-item');
- This function adds click listeners to each question in the accordion.
- Clicking a question will:
- Expand the answer (max-height is set to scroll height)
- Collapse any other open question
- Toggle the .active class to rotate the icon and highlight the question
5. When a FAQ Card is Clicked
questionCards.forEach((card) => {
card.addEventListener('click', () => {
const faqId = card.dataset.faqId;
const data = faqData[faqId];
- Reads the data-faq-id attribute from the clicked card.
- Finds the corresponding content from faqData.
- Populates the title and dynamically creates all Q&A HTML using template literals.
- Inserts the HTML into the page:
answerBodyEl.innerHTML = accordionHTML; - Then runs
setupAccordionListeners()to make new questions clickable. - Finally, shows the answer view:
faqContainer.classList.add('is-answer-visible');
6. Back Button Logic
backButton.addEventListener('click', () => {
faqContainer.classList.remove('is-answer-visible');
});
- Hides the answer view and returns to the list of FAQ categories.
document.addEventListener('DOMContentLoaded', function () {
const faqContainer = document.getElementById('faq-container');
const questionCards = document.querySelectorAll('.faq-card');
const backButton = document.getElementById('faq-back-button');
const answerTitleEl = document.getElementById('answer-title');
const answerBodyEl = document.getElementById('answer-body');
const faqData = {
1: {
title: 'Premium Package',
items: [
{
question: 'What are the key features of the Premium Package?',
answer:
'The Premium Package includes 24/7 priority support, access to all advanced features, unlimited projects, and exclusive access to our beta programs.',
},
{
question: 'Do I get a dedicated account manager?',
answer:
'Yes, a dedicated account manager is included to help you get the most out of our platform and achieve your goals.',
},
{
question: 'Is there a limit on team members?',
answer:
'Our Premium Package allows for unlimited team members, so your entire organization can collaborate seamlessly.',
},
],
},
2: {
title: 'Subscription & Billing',
items: [
{
question: 'How do I cancel my subscription?',
answer:
"You can cancel your subscription at any time from your account settings. Navigate to the 'Billing' section and click on the 'Cancel Subscription' button. Your access will continue until the end of the current billing period.",
},
{
question: 'Can I change my plan later?',
answer:
'Absolutely. You can upgrade or downgrade your plan at any time from your account dashboard. Changes will be prorated automatically, so you only pay for what you use.',
},
],
},
3: {
title: 'Free Trial Information',
items: [
{
question: 'Is there a free trial available?',
answer:
'Yes, we offer a comprehensive 14-day free trial with no credit card required to get started. The trial gives you full access to all our premium features.',
},
{
question: 'What happens after my free trial ends?',
answer:
'After your trial ends, you will be prompted to choose a plan to continue using the service. Your data and projects will be saved and waiting for you.',
},
],
},
};
function setupAccordionListeners() {
const accordionItems = answerBodyEl.querySelectorAll('.accordion-item');
accordionItems.forEach((item) => {
const question = item.querySelector('.accordion-question');
question.addEventListener('click', () => {
const currentlyActive = answerBodyEl.querySelector(
'.accordion-item.active'
);
if (currentlyActive && currentlyActive !== item) {
currentlyActive.classList.remove('active');
currentlyActive.querySelector(
'.accordion-answer'
).style.maxHeight = 0;
}
item.classList.toggle('active');
const answer = item.querySelector('.accordion-answer');
if (item.classList.contains('active')) {
answer.style.maxHeight = answer.scrollHeight + 'px';
} else {
answer.style.maxHeight = 0;
}
});
});
}
questionCards.forEach((card) => {
card.addEventListener('click', () => {
const faqId = card.dataset.faqId;
const data = faqData[faqId];
if (data) {
answerTitleEl.textContent = data.title;
// Build accordion HTML
let accordionHTML = '';
data.items.forEach((item) => {
accordionHTML += `
<div class="accordion-item">
<div class="accordion-question">
<h4>${item.question}</h4>
<svg class="accordion-icon" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" d="m19.5 8.25-7.5 7.5-7.5-7.5" />
</svg>
</div>
<div class="accordion-answer">
<p>${item.answer}</p>
</div>
</div>
`;
});
answerBodyEl.innerHTML = accordionHTML;
// Add listeners to the new accordion
setupAccordionListeners();
faqContainer.classList.add('is-answer-visible');
}
});
});
backButton.addEventListener('click', () => {
faqContainer.classList.remove('is-answer-visible');
});
});Final Output:
Build a Professional Article Details Page from Scratch with HTML, CSS, and Vanilla JavaScriptConclusion:
Creating a modern and user-friendly FAQ accordion with HTML, CSS, and JavaScript is simple and powerful. It helps improve your website’s user experience by keeping things clean and easy to navigate.
With just a few lines of code, you can make your site look more professional and user-friendly. This FAQ style works perfectly for portfolios, business sites, blogs, and e-commerce websites.
Keep your code minimal, and remember to test on different devices for the best results.
That’s a wrap!
I hope you enjoyed this post. Now, with these examples, you can create your own amazing page.
Did you like it? Let me know in the comments below 🔥 and you can support me by buying me a coffee
And don’t forget to sign up to our email newsletter so you can get useful content like this sent right to your inbox!
Thanks!
Faraz 😊

