Product-Page FAQ Accordion: Page-Backed Answers, Mobile 'View More' Reveal, Auto-Emitted FAQPage
A local extension of Palo Alto's Tabs section that hides every FAQ row past the third on mobile behind one reveal button, and emits FAQPage JSON-LD from the vendor's own block loop, reading each answer from the same Shopify page or rich-text field the accordion displays.
Palo Alto's Tabs section draws an FAQ accordion and emits no structured data, and on a phone a long FAQ set buries the rest of the product page. We kept the vendor's section and its collapsible runtime and added three things inside it: a three-item mobile reveal, its script, and a FAQPage block assembled in Liquid from the same answer expression the accordion renders, so both answers resolve from one source.
The Problem
A theme accordion is a rendering component, and a rendering component's answers are invisible to anything that doesn't run the page. Palo Alto's Tabs section takes a heading and a body per block, draws a collapsible row, and stops there. There is no structured data and no way for a retriever to know that a row is a question and the panel under it is the answer.
On Three Ships that mattered twice over. The FAQ sets on product pages are the highest-intent copy the brand writes — is this bundle suitable for sensitive skin, how long does an order take — and they were read only by the shoppers who scrolled to them. On a phone, a ten-row set stacked into a wall of taps that pushed everything after it off the screen.
The Constraint
The section is vendor code. Palo Alto 5.8.0 ships section-accordions.liquid under the schema name Tabs, with a block loop, a two-column split computed by modulo, and open-close behavior owned by the theme's compiled Collapsible runtime through data-collapsible-trigger, data-collapsible-container and data-collapsible-content. Whatever we added had to leave the block schema, the settings and those attributes untouched, so the runtime kept driving each row and a theme update stayed plausible.
The answers have two homes. A Text block carries its answer as rich text. A Page block points raw_content at a Shopify page whose body is the answer, so one page can serve many products. Structured data therefore had to be assembled in Liquid, at render time, from whichever source the block was using. And the section is mounted by 73 templates, not one, so anything we added had to behave on a collection page and a blog page as well as a PDP.
What We Built
Three additions, all inside the vendor's file, none touching what the vendor's runtime reads.
The answer expression. Every block resolves its body through one expression: pages[block.settings.raw_content].content, falling through default: to the block's own text rich-text setting. If the block points at a page, the page body is the answer; if not, the block's own rich text is. The visible accordion renders that expression as HTML inside the vendor's .rte panel. That line is the vendor's, and it's the line everything we added hangs off.
The FAQPage block. After the section's schema we appended a <script type="application/ld+json"> that loops section.blocks a second time and emits one Question per block: name from block.settings.title | json, and acceptedAnswer.text from the same answer expression passed through strip_html and then json. The structured answer is the visible answer with its tags removed: the same expression, resolving the same Shopify page or the same rich text.
The mobile reveal. In the block loop, every item after the third gets a faq-hidden-mobile class. A stylesheet rule hides those rows under 767px and shows a .faq-toggle-btn beneath the list; from 768px up the button is hidden and every row is in the flow. A small script finds the section by its own id and, on click, toggles faq-expanded on .accordion__content, flips the button's aria-expanded, sets aria-hidden on the rows it hides, swaps the button label between its view-more and view-less states, and — when collapsing — calls scrollIntoView({behavior:'smooth'}) on the accordion so the shopper lands back at the top of the list rather than somewhere below it.
None of the three interacts with the vendor's open-close. A row's own expand and collapse is still Collapsible's job, driven by the attributes the theme shipped; data-collapsible-single on the wrapper still means one open row at a time. Our reveal decides only whether a row is in the mobile flow, and the markup takes its answers from the expression the rows are rendered from.
Why This Way
Generating the JSON-LD from the same loop and the same expression as the visible accordion is the decision that matters. Every other route — a metafield of question-and-answer pairs, a separate structured-data section, an app that scrapes the DOM after load — creates a second copy of the answers that can drift from the first. Here there's one copy. A merchandiser who repoints a Page block at a different page changes the shopper's answer and the machine's answer in one edit, and a page body reused across twenty PDPs is one page to correct.
Doing the reveal as a class and one toggle, rather than per-row JavaScript, keeps it out of the runtime's way: Collapsible never sees a click it didn't expect.
We accepted two things in return. The section remains vendor code with our additions inside it, so a Palo Alto update will need those additions carried forward by hand. And the reveal threshold is a literal in the loop rather than a setting: three rows on mobile is a decision a developer made, not one a merchandiser can revisit in the theme editor. We took the smaller schema over the extra knob.
Why Not an App
FAQ apps and structured-data apps both exist, and both would have been the wrong shape here. An FAQ app keeps its questions in its own dashboard and renders them with a script after the page has loaded — which on this storefront means a third accordion system on a product page that already runs the vendor's and the brand's own content accordion, and answers that live somewhere Liquid can't read. A structured-data app emits FAQPage from its own copy of the questions, which is the drift problem restated with a subscription attached.
The section already had the answers, in Shopify, in a block loop. Emitting markup from that loop is a few dozen lines of Liquid and keeps one source of truth. The mobile reveal is smaller still. Neither justified a second script on every product page, and neither would have been able to read a Page block's body the way the theme already does.
Implementation Notes
- The answer expression —
pages[block.settings.raw_content].content, withdefault:falling to the block'stextsetting — is the vendor's. It appears verbatim in both loops, the vendor's and ours; the markup doesn't invent a second answer source. strip_htmlruns beforejson, so page HTML reaches the JSON string as text rather than as markup, andjsonhandles quote and newline escaping. A page body with headings, bold and line breaks becomes one flat answer string.- The reveal threshold is
forloop.index > 3in the block loop. The first three rows are always in the mobile flow; the class does nothing above 767px because the stylesheet rule is inside amax-widthmedia query. - The toggle script finds its section by the
Accordions--id, then early-returns if the button or the.accordion__contentwrapper is missing, so it never binds to another section's elements. scrollIntoViewfires only on collapse. Expanding leaves the viewport where the shopper tapped; collapsing brings the top of the list back into view instead of dropping them below it.- The vendor's per-row ARIA —
aria-controlsandaria-expandedon the trigger,role="region"andaria-labelledbyon the panel — is untouched. Our reveal addsaria-hiddenonly to the rows it hides, and only while they're hidden. data-collapsible-singleon the vendor's wrapper is what makes one row close when another opens. We did not add a second accordion controller to get it.
Edge Cases
- The
default:fallback is what lets Text and Page blocks share one loop: a Text block has noraw_content, so the page lookup resolves to nothing and its own rich text is used — in the row and in the Question alike. - A Page block whose
raw_contentno longer resolves falls throughdefault:to the block's own rich text, so the row and the Question move together rather than one of them going empty. - A rich-text answer with inline HTML renders it in the row and loses it in the markup; a bold phrase becomes plain text in
acceptedAnswer.text. - A phone rotated past 768px shows every row without a tap. The hiding is a stylesheet rule, so no resize listener is needed and the button disappears with the same media query.
- Reveal state is a class on the wrapper, not stored anywhere. A navigation or a section re-render starts collapsed again with the view-more label.
Platform Primitives Used
liquid-schema-settings— the vendor's Tabs schema is unchanged: layout, divider, heading, subheading, text, colors, width and padding. Our additions read the block loop andforloop.index, not settings.theme-blocks— two block types, Text and Page. The block loop is the single source for the accordion, the mobile reveal class and theFAQPagemarkup.
Where It Runs
Mounted by 73 templates on Three Ships: mostly product templates, plus the collection, page and blog templates the same Tabs section serves. It renders on the product detail page, on product listing pages and on the blog.
What This Demonstrates
- FAQ and self-service content — the primary capability: FAQ answers authored as theme blocks, or as Shopify pages a block points at, rendered inside the vendor's accordion and reused across many products.
- Structured data for AI answers and rich results —
FAQPageassembled in Liquid from the rendered blocks, kept for what answer engines can extract from a Question and Answer pair rather than for a SERP feature. - Premium theme extension and configuration at scale — three additions inside a licensed theme's section, with the vendor's schema, attributes and runtime left intact.
How We Know
One section file from the theme — the vendor's Tabs section, with our three additions inside it — plus seven client working records: six task-register entries and one roadmap entry. The task register tracks the mobile reveal from request through verification, and the client's roadmap records further product-page FAQ work as a planned item, which is why this page carries strong confidence rather than moderate. We also checked a live product page on 2026-09-07: it emits a FAQPage block with the same four questions its accordion shows, and the fourth row carries the mobile-hidden class.
Related Builds
- PDP content accordion that borrows the premium theme's collapsible contract — the same storefront's other accordion: our markup driving the vendor's runtime, with no structured data because its panels aren't questions.
- Metafield-driven two-level FAQ accordion grafted into a help-center app's shell — LUS Brands. A FAQ corpus moved into a page metafield so the theme could render it; that page builds the structure, this one emits the markup.
- Product card with award badges, per-variant content payload and per-card structured data — Nudestix. Structured data emitted per rendered component, the same principle at grid scale.
- Configurable PDP block system on the Palo Alto theme (Recharge widget placement, size charts, siblings, quick view) — the broader pattern on this storefront: extend the vendor's product page inside its own files.
The Buy-vs-Build Question
An FAQ app buys an accordion, a dashboard and a second home for your answers. Extending the theme's own section bought one source that renders for shoppers and serializes for machines, and cost a vendor file we maintain by hand across updates. Where the answers should live is the subject of FAQ help center: buy or build?; the markup half is in Structured data and schema markup: buy or build?
Provenance & Evidence
- Client: Three Ships — threeshipsbeauty.ca
- Surfaces: Product detail page, product listing page, blog
- Templates served: 73
- Complexity: Medium — roughly 550 lines, most of them the vendor's
- Attribution: Built on top of Palo Alto 5.8.0 by Presidio Creative, a paid premium theme the client licenses. The Tabs section, its schema, its two block types and its collapsible runtime are the vendor's. The mobile reveal, its script and the
FAQPageblock are our modification inside that section. - Status: Live, verified 2026-09-07
- Evidence: One section file from the theme, plus six task-register entries and one roadmap entry from the client's working records
- Confidence: Strong — code plus a surviving client-side record
- Primary capability: FAQ and self-service content
Want Your Product Answers Read by More Than Shoppers?
You dream it. We build it. If your product pages already answer the questions but nothing outside the browser can read them, Contact us today and we'll show you what one block loop can do for both audiences.