Preview
Desktop
Mobile
Overview
About This Project
The Brief
Build a functional FAQ accordion from a Frontend Mentor design — a card with expandable questions and answers, switching between a plus and minus icon on toggle. The visual implementation was straightforward. The real brief I set for myself was to build it accessibly from the ground up, not as an afterthought.
My Approach
I started with the HTML structure before writing a single line of CSS or JS. The core pattern is a button element controlling a div panel, linked via aria-controls on the button and a matching id on the panel. aria-expanded carries the open/closed state and acts as the single source of truth for both assistive technology and the UI.
Rather than toggling icon visibility in JavaScript, I keyed the icon swap entirely off aria-expanded in CSS. This meant JavaScript only ever needed to touch one attribute — the visual layer followed automatically. The JS logic itself was kept deliberately simple: on click, collapse all panels, then reopen the clicked one if it was previously closed.
Challenges
Text wrapping: The first question wasn't wrapping at the same point as the design. I tried word-break and overflow-wrap — neither worked. After closer inspection I realised the icon was flex-compressing by a few pixels, giving the title just enough extra room to stay on one line. Setting flex-shrink: 0 on the icon fixed it immediately. The lesson: layout bugs aren't always where you're looking.
Understanding ARIA as a contract: The most important shift in thinking was realising that ARIA attributes aren't decorative labels — they're a contract with assistive technology. If aria-expanded="false" is set on a button, the panel it controls must actually be hidden. A mismatch between the ARIA state and the visual state means screen reader users get false information about the UI. Every implementation decision in this project flowed from that principle.
Icon visibility: Managing two icons — one visible, one hidden, swapping on toggle — initially seemed like a JS problem. Keeping both aria-hidden="true" regardless of which was visible was a deliberate choice: both icons are purely decorative, and the button text already communicates its purpose. Announcing icon changes to screen readers would have added noise without value.
Tools Used