FAQ Accordion Component
Create collapsible FAQ sections with accessible toggles and smooth UI animations
Time to implement the project: ~ 4-7 hours
- HTML
- Semantic HTML
- CSS
- JavaScript
- DOM Events
- Accessibility
In this project, you will build an interactive FAQ accordion where users can expand and collapse questions to reveal answers. The task focuses on implementing a clean, predictable toggle behavior that works equally well with mouse, keyboard, and screen readers. Each question block must act as a control element that manages the visibility of its associated answer.
The accordion should include smooth open and close animations without relying on heavy libraries. State changes must be clearly reflected in the markup using attributes and class changes. The component should handle multiple questions gracefully, maintain layout stability during interactions, and prevent confusing UI behavior such as multiple answers opening unintentionally unless explicitly designed to do so.
Learning Focus and Project Intent
This project is designed to teach how interactive UI components are structured, controlled, and made accessible. You will learn how to connect user actions to DOM updates, manage open and closed states, and ensure that content visibility changes remain understandable to all users.
FAQ accordions are widely used in documentation, support pages, and product sites. Building one from scratch strengthens your understanding of real interaction logic, not just visual layout. These skills are considered essential for frontend developers working on content-heavy interfaces.
Required Knowledge Before Starting
To complete this task confidently, you should understand how HTML elements interact with JavaScript and how CSS transitions affect layout changes. The project assumes familiarity with basic accessibility concepts.
- HTML semantics and proper heading structure
- CSS transitions and height/opacity animations
- JavaScript event handling and state toggling
- ARIA attributes and keyboard navigation basics
- Using DevTools to inspect DOM and event listeners
Functional and Accessibility Requirements
A correct implementation prioritizes clarity, predictability, and accessibility over visual flair. The accordion must behave consistently across devices and input types. Each requirement reflects how UI components are evaluated in professional frontend code reviews.
| Requirement | Explanation |
| Clickable question headers | Clear interactive headers define where users initiate state changes. |
| Single-source state control | Centralized state logic prevents desynchronization between UI and DOM. |
| Smooth expand and collapse animation | Animations help users track content changes without visual confusion. |
| Keyboard operability | Tab and Enter/Space support ensures usability without a mouse. |
| ARIA-expanded state management | Assistive technologies rely on accurate state attributes. |
| Independent question behavior | Each FAQ item must toggle without breaking neighboring items. |
Implementation Advice for Better Results
Begin by defining the HTML structure clearly: question as a control, answer as a collapsible region. Implement functionality before animations to validate logic. When adding transitions, prefer height-based or max-height techniques that remain predictable across content lengths. Use class toggling rather than inline styles to keep behavior and presentation separated. A well-built accordion feels invisible to the user because it behaves exactly as expected.
- Bind click and keyboard events to the same handler to avoid duplicated logic
- Update ARIA attributes alongside visual state changes for accessibility consistency
- Test with long and short answers to validate animation robustness
- Avoid hard-coded heights that break when content changes
- Ensure focus remains logical when sections open or close
- Keep JavaScript minimal and readable to simplify debugging
Common FAQ Accordion Mistakes and How to Fix Them
1. Using clickable divs instead of real interactive elements
One of the biggest mistakes in FAQ accordion projects is making the question row clickable with a <div> or <p> element. It may
work with a mouse, but it usually fails for keyboard users and does not communicate proper interactive meaning to assistive technologies.
Problematic approach:
<div class="faq-question">
What is frontend development?
</div>
This element is not naturally focusable, does not respond to Enter or Space by default, and does not clearly describe itself as a control.
Better approach:
<button class="faq-question" type="button" aria-expanded="false" aria-controls="answer-1">
What is frontend development?
</button>
<div class="faq-answer" id="answer-1" hidden>
Frontend development focuses on building the user-facing part of websites and applications.
</div>
Pay attention to: Use a real <button> for every accordion trigger. Buttons are keyboard-accessible by default, communicate
interactivity correctly, and make the JavaScript logic simpler. If you use <details> and <summary>, they also provide built-in
disclosure behavior, but custom accordions should still use proper controls.
2. Updating the visual state but forgetting aria-expanded
Beginners often toggle only CSS classes, so the accordion visually opens and closes, but assistive technologies do not receive accurate state information. This creates a mismatch between what sighted users see and what screen reader users hear.
Incomplete JavaScript:
button.addEventListener('click', () => {
answer.classList.toggle('is-open');
});
This changes the layout visually, but the button still does not announce whether the related answer is expanded or collapsed.
Accessible state update:
button.addEventListener('click', () => {
const isOpen = button.getAttribute('aria-expanded') === 'true';
button.setAttribute('aria-expanded', String(!isOpen));
answer.hidden = isOpen;
});
Pay attention to: Every visual state change should have a matching semantic state change. If an answer is visible, the trigger should have
aria-expanded="true". If the answer is hidden, the trigger should return to aria-expanded="false".
3. Animating height: auto incorrectly
Smooth accordion animation is a common requirement, but many beginners try to animate directly from height: 0 to height: auto. CSS cannot
transition to or from auto reliably, so the animation either does not run or behaves inconsistently.
Problematic CSS:
.faq-answer {
height: 0;
overflow: hidden;
transition: height 0.3s ease;
}
.faq-answer.is-open {
height: auto;
}
This looks logical, but height: auto is not a numeric value that CSS can interpolate smoothly.
Simpler beginner-friendly solution:
.faq-answer {
max-height: 0;
overflow: hidden;
opacity: 0;
transition: max-height 0.3s ease, opacity 0.2s ease;
}
.faq-answer.is-open {
max-height: 300px;
opacity: 1;
}
More flexible JavaScript-assisted solution:
function openAnswer(answer) {
answer.hidden = false;
answer.style.maxHeight = answer.scrollHeight + 'px';
}
function closeAnswer(answer) {
answer.style.maxHeight = '0px';
answer.addEventListener('transitionend', () => {
answer.hidden = true;
}, { once: true });
}
Pay attention to: If FAQ answers can have very different lengths, avoid tiny fixed max-height values. Use scrollHeight when
you need a more reliable animation, and always test both short and long answers.
4. Closing and opening panels without a clear state strategy
Accordion behavior should be intentional. Some accordions allow multiple answers open at the same time, while others allow only one open item. A common beginner mistake is mixing both behaviors accidentally, which creates confusing UI logic and hard-to-debug code.
Unclear toggle logic:
buttons.forEach((button) => {
button.addEventListener('click', () => {
button.classList.toggle('active');
button.nextElementSibling.classList.toggle('open');
});
});
This works for simple toggling, but it does not define whether other answers should close. As the component grows, this ambiguity can create inconsistent behavior.
Clear one-open-at-a-time logic:
buttons.forEach((button) => {
button.addEventListener('click', () => {
const shouldOpen = button.getAttribute('aria-expanded') !== 'true';
buttons.forEach((item) => {
const panel = document.getElementById(item.getAttribute('aria-controls'));
item.setAttribute('aria-expanded', 'false');
panel.hidden = true;
});
if (shouldOpen) {
const panel = document.getElementById(button.getAttribute('aria-controls'));
button.setAttribute('aria-expanded', 'true');
panel.hidden = false;
}
});
});
Pay attention to: Decide the behavior before writing JavaScript. If the accordion is used for compact FAQ reading, one-open-at-a-time behavior can reduce visual clutter. If users need to compare several answers, allowing multiple open panels may be better.
5. Hiding content only with opacity or visibility
Another common issue is hiding the answer visually while leaving it available to keyboard users and screen readers. For example, using only
opacity: 0 makes content invisible, but it may still occupy space or remain reachable depending on the rest of the CSS.
Problematic CSS-only hiding:
.faq-answer {
opacity: 0;
}
.faq-answer.is-open {
opacity: 1;
}
This does not properly remove the hidden answer from the interaction flow.
Better approach with hidden attribute:
<button type="button" aria-expanded="false" aria-controls="answer-2">
Can I use only HTML and CSS?
</button>
<div id="answer-2" class="faq-answer" hidden>
Yes, simple accordions can be built with details and summary.
</div>
JavaScript update:
function togglePanel(button) {
const panel = document.getElementById(button.getAttribute('aria-controls'));
const isOpen = button.getAttribute('aria-expanded') === 'true';
button.setAttribute('aria-expanded', String(!isOpen));
panel.hidden = isOpen;
}
Pay attention to: Hidden content should be hidden both visually and semantically. Use the hidden attribute, proper ARIA states, or
carefully managed CSS classes that do not leave closed content accidentally accessible.
6. Ignoring focus styles and keyboard testing
FAQ accordions are often placed on support pages, documentation pages, and product pages where users may rely on keyboard navigation. If focus styles are removed or never designed, users can tab through the accordion without knowing which question is currently active.
Problematic reset:
button {
outline: none;
}
Removing outlines without replacing them creates an accessibility problem and makes the component harder to use.
Better focus styling:
.faq-question:focus-visible {
outline: 3px solid #2fc969;
outline-offset: 4px;
}
.faq-question:hover {
color: #14856e;
}
Pay attention to: Test the component without a mouse. Press Tab to move between questions, Enter or Space to open an answer, and Shift + Tab to move backward. If you cannot clearly see where focus is, the component is not finished.
By completing this project, you will gain practical experience building an accessible accordion component with controlled state, smooth animations, and reliable user interactions. This work strengthens your understanding of UI logic, accessibility standards, and DOM-driven behavior, preparing you for more advanced components such as tabs, modals, and interactive documentation systems used in production environments.
Reference FAQ Accordion Implementations
Beginner-friendly implementation:
itsale-o - FAQ Accordion
This repository is useful for beginners because it is a focused FAQ accordion project built with HTML, CSS, and JavaScript. The project is based on the Frontend Mentor FAQ accordion challenge, where users should be able to show and hide answers, navigate with keyboard alone, view responsive layouts, and see hover and focus states. These requirements match the practical goals of this project very closely.
What to study in the code:
- How the HTML structure separates questions from answers.
- How JavaScript connects click behavior to accordion state.
- How hover and focus states are handled for interactive elements.
- How the layout changes between desktop and mobile screen sizes.
- Whether ARIA attributes stay synchronized with visual open and closed states.
Use this repository as a practical baseline. After reviewing it, try improving your own version with clearer state handling, stronger focus styles, and more explicit accessibility attributes.
Accessibility-focused implementation:
CorinaMurg - FAQ Accordion
This repository is especially valuable because it pays attention to accessibility rather than treating the accordion as a purely visual component. The project notes the
use of semantic HTML, CSS custom properties, Flexbox, CSS Grid, mobile-first workflow, and JavaScript. It also discusses accessible practices such as
<details> and <summary>, keyboard navigation, and closing other questions to reduce clutter for users.
Pay attention to:
- How semantic HTML improves the default behavior of the accordion.
- How the project treats responsive layout as part of the component, not as an afterthought.
- How accessible disclosure behavior can be achieved with native HTML elements.
- How closing other open questions can help users focus on one answer at a time.
- How CSS files are organized and separated into different responsibility areas.
This is a strong reference when you want your FAQ accordion to be useful for real users, not just visually similar to a design screenshot.
Accessible pattern example:
Equalize Digital - Accessible Accordion Example
This CodePen is useful because it demonstrates an accessibility-first accordion pattern in a compact format. The example uses real buttons with
aria-expanded and aria-controls, connects each button to a specific content panel, and hides collapsed content with the
hidden attribute. It is a good reference when you want to understand the mechanics of an accessible accordion without studying a full project repository.
What to inspect carefully:
- How each accordion button controls exactly one content panel.
- How
aria-expandedchanges when a panel opens or closes. - How
hiddenis used to remove inactive content from the page flow. - How the JavaScript collapses other panels before opening the selected one.
- How CSS uses the ARIA state to change the visual indicator from plus to minus.
A useful exercise is to rebuild this pattern manually, then add your own animation layer without breaking the accessibility logic. The behavior should remain correct even after visual improvements.