Authoring FAQs
This page covers writing FAQs and exactly how they behave on the product page.
Add FAQs to a product
Section titled “Add FAQs to a product”- Open the product in the editor.
- In the Product data panel, open the FAQs tab.
- Click Add question to add a row. Type the question in the top field and the answer in the textarea below it.
- Repeat for each question. Use the × button on a row to remove it.
- Update the product.
Questions appear on the storefront in the order you list them, there is no drag-to-reorder; arrange them by editing the rows. A row is saved only when both the question and answer are filled in. Blank rows (or rows with only one field) are dropped on save, so you can leave a half-typed row and it simply will not persist. If every row is empty, the FAQ meta is removed from the product entirely.
The repeater works without JavaScript: existing rows render server-side and stay editable, and “Add question” clones a hidden template row when JS is available.
HTML allowed in answers
Section titled “HTML allowed in answers”Questions are plain text. Answers accept basic HTML, filtered with wp_kses_post, the same safe subset WordPress allows in post content: links, lists, <strong>/<em>, blockquotes and similar. Scripts and other unsafe markup are stripped, twice: once when you save, and again when the answer is output. On the storefront the answer is also passed through wpautop, so plain line breaks become paragraphs.
What renders, and where
Section titled “What renders, and where”When the product page loads, Respondo reads the product’s FAQ items and:
- registers a FAQs product-information tab (priority 25, between Description and Reviews, or your custom tab title),
- renders an accordion inside that tab,
- enqueues the FAQ CSS and JS, but only on single product pages, and only when the product has at least one usable FAQ.
A product with no FAQs gets no tab and loads nothing. Every saved row renders, in the order you listed it: there is no cap on the number of items and repeated questions are not merged, so the accordion shows exactly what the FAQs tab shows.
Accordion behaviour
Section titled “Accordion behaviour”Each FAQ is an <h3> containing a <button> that controls a region panel:
- Clicking the button (or pressing Space/Enter while it is focused, native to
<button>) toggles the panel.aria-expandedis kept in sync and the panel uses thehiddenattribute when closed. - Each panel is a
role="region"labelled by its button viaaria-labelledby, so screen-reader users can navigate to it and hear which question it belongs to. - The focused button shows a visible focus-visible ring.
- Panels animate open/closed with a height transition; under
prefers-reduced-motionthey snap instead.
Each question toggles independently, opening one does not close the others. Tab moves between the question buttons in document order; there is no arrow-key roving within the group (these are independent disclosure buttons, not a radio group).
With JavaScript disabled the markup is still valid: the answer text is reachable and the page is not broken, panels are simply collapsed.
Verify it
Section titled “Verify it”- Open a product with FAQs and click the FAQs tab, the accordion should render.
- Tab to a question and press Enter; it should expand and the button’s
aria-expandedshould readtrue. - Open a product with no FAQs and confirm there is no FAQs tab and no extra CSS/JS request.
Extending the output (developers)
Section titled “Extending the output (developers)”There is no shortcode and no theme template file to copy: the markup is rendered inline by the plugin. To restyle the accordion, override the --answers-* CSS custom properties from your theme (Configuration) or target the .answers-faq* classes.
For code, the free plugin fires these hooks (Respondo Pro builds its voting and categories on them):
answers/faq_items(filter), the list ofFaqItemobjects about to render for a product, with the product ID as the second argument. Add, remove or reorder items here.answers/faq_after_answer(action), fires after each answer is rendered, with the product ID, the zero-based index and theFaqItem.answers/faq_repeater_after_answer(action), fires after the answer field of each row in the admin repeater, with the row index, the row data, the field name base and whether the row is the hidden clone template.answers/faq_repeater_sanitize_row(filter), the sanitisedquestion/answerpair before it is saved, with the raw submitted row as the second argument. Return something that is not an array to drop the row.answers/booted(action), fires once with the plugin instance after Respondo starts.