Answering my own question in case anyone needs an answer!
Short answer: no, sections can only go 1 level. Shopify doesn’t support multi-level sections.
Long answer: You can’t associate sections to other sections. For this use case however, making a dropdown list of 2 options, I was able to pass the selected option from that dropdown into the FAQ to make it associated to the correct category. It’s an extra step and is important to note that this would be a nightmare for the merchant to manage if this approach was used for multiple products. Only 3 products are here and less the 30 FAQ’s, so the tradeoff was acceptable in this scenario.
So, [section 2] - name the category:
category Names
[text area] A
[text area] B
[text area] C
Section 2 - FAQ
assign to a category
[DROPDOWN LIST. Options: A, B C]
[FAQ Question]
[FAQ Answer]
And the loop would look for the category name in an if statement, and then return the code.
<div class="faqs" itemscope itemtype="https://schema.org/FAQPage">
{% for block in section.blocks %}
{% if block.type == "FAQHeading" %}
<h1 class="title faq--heading text-center">{{ block.settings.faqHeading }}</h1>
{% endif %}
{% endfor %}
<div data-tab-component>
<div class="faq__tabList" role="tablist" aria-label="Tabbed sections">
{% for block in section.blocks %}
{% if block.type == "faqCategories" %}
<button role="tab"
aria-selected="true"
aria-controls="tab1-content"
id="tab1"
class="tab__button">
{{ block.settings.faqProductType1 }}
</button>
<button role="tab"
aria-selected="false"
aria-controls="tab2-content"
id="tab2"
class="tab__button">
{{ block.settings.faqProductType2 }}
</button>
<button role="tab"
aria-selected="false"
aria-controls="tab3-content"
id="tab3"
class="tab__button">
{{ block.settings.faqProductType3 }}
</button>
{% endif %}
{% endfor %}
</div>
<section
class="faq__tabPanel"
id="tab1-content"
role="tabpanel"
aria-labelledby="tab1"
tabindex="0">
<div data-tab-component>
<div role="tablist" aria-label="Tabbed sections">
<dl class="faqAccordion">
{% for block in section.blocks %}
{%- assign type = block.settings.productType -%}
{%- if type contains "CategoryA" -%}
<dt
itemscope
itemprop="mainEntity"
itemtype="https://schema.org/Question">
<button
itemprop="name"
role="tab"
aria-selected="true"
aria-controls="faq{{ block.id }}-content"
id="faq{{ block.id }}"
class="accordion_heading faq-list">
{{ block.settings.faqQuestion }}
</button>
</dt>
<dd
itemscope
itemprop="acceptedAnswer"
itemtype="https://schema.org/Answer"
class="success_stories_data"
role="tabpanel"
id="faq{{ block.id }}-content"
aria-labelledby="faq{{ block.id }}"
tabindex="0"
aria-hidden="true">
<p itemprop="text">
{{ block.settings.faqAnswer }}
</p>
</dd>
{%- endif -%}
{% endfor %}
</dl>
</div>
</div>
</section>
<section id="tab2-content"
role="tabpanel"
aria-labelledby="tab2"
tabindex="0"
aria-hidden="true">
<div data-tab-component>
<div role="tablist" aria-label="Tabbed sections">
<dl class="faqAccordion">
{% for block in section.blocks %}
{%- assign type = block.settings.productType -%}
{%- if type contains "CategoryB" -%}
<dt
itemscope
itemprop="mainEntity"
itemtype="https://schema.org/Question">
<button
itemprop="name"
role="tab"
aria-selected="true"
aria-controls="faq{{ block.id }}-content"
id="faq{{ block.id }}"
class="accordion_heading faq-list">
{{ block.settings.faqQuestion }}
</button>
</dt>
<dd
itemscope
itemprop="acceptedAnswer"
itemtype="https://schema.org/Answer"
class="success_stories_data"
role="tabpanel"
id="faq{{ block.id }}-content"
aria-labelledby="faq{{ block.id }}"
tabindex="0"
aria-hidden="true">
<p itemprop="text">
{{ block.settings.faqAnswer }}
</p>
</dd>
{%- endif -%}
{% endfor %}
</dl>
</div>
</div>
</section>
<section id="tab3-content"
role="tabpanel"
aria-labelledby="tab3"
tabindex="0"
aria-hidden="true">
<div data-tab-component>
<div role="tablist" aria-label="Tabbed sections">
<dl class="faqAccordion">
{% for block in section.blocks %}
{%- assign type = block.settings.productType -%}
{%- if type contains "CategoryC" -%}
<dt
itemscope
itemprop="mainEntity"
itemtype="https://schema.org/Question">
<button
itemprop="name"
role="tab"
aria-selected="true"
aria-controls="faq{{ block.id }}-content"
id="faq{{ block.id }}"
class="accordion_heading faq-list">
{{ block.settings.faqQuestion }}
</button>
</dt>
<dd
itemscope
itemprop="acceptedAnswer"
itemtype="https://schema.org/Answer"
class="success_stories_data"
role="tabpanel"
id="faq{{ block.id }}-content"
aria-labelledby="faq{{ block.id }}"
tabindex="0"
aria-hidden="true">
<p itemprop="text">
{{ block.settings.faqAnswer }}
</p>
</dd>
{%- endif -%}
{% endfor %}
</dl>
</div>
</div>
</section>
</div>
</div>
{% schema %}
{
"blocks": [
{
"name": "FAQ Heading",
"type": "FAQHeading",
"limit": 1,
"settings": [
{
"type": "text",
"id": "faqHeading",
"label": "FAQ Title",
"default": "FAQs"
}
]
},
{
"name": "FAQ Categories",
"type": "faqCategories",
"settings": [
{
"type": "text",
"label": "faqProductType1",
"id": "faqProductType1",
"default": "Formula"
},
{
"type": "text",
"label": "faqProductType2",
"id": "faqProductType2",
"default": "Porridge"
},
{
"type": "text",
"label": "faqProductType3",
"id": "faqProductType3",
"default": "Snack Puffs"
}
]
},
{
"name": "FAQ Q and A",
"type": "faqQandA",
"settings": [
{
"type": "select",
"id": "productType",
"options": [
{ "value": "CategoryA", "label": "CategoryA"},
{ "value": "CategoryB", "label": "CategoryB"},
{ "value": "CategoryC", "label": "CategoryC"}
],
"label": "Select Product"
},
{
"type": "text",
"label": "Enter Question",
"id": "faqQuestion",
"default": "FAQ question content goes here"
},
{
"type": "textarea",
"label": "Enter Answer",
"id": "faqAnswer",
"default": "Lorem ipsum dolor amit icit"
}
]
}
]
}
{% endschema %}
I’m sure there’s a clever way to nest another loop so you would only have to write the output HTML for the tabs and accordion groups once which would make this more maintainable.
This is also a WCAG AA accessible accordion structure. Here’s the Vanilla JS.
{% javascript %}
/**
* @function Tabs()
*
* @param args {object} Settings for controlling the functionality of the component
* @returns bindEventListeners {function} Event listeners for the component
*/
function Tabs(args) {
// Scope-safe constructors
if (!(this instanceof Tabs)) {
return new Tabs();
}
/**
* Default component settings
*
* @param container {string} Classname for container of the entire component
* @param trigger {string} Element that toggles content
* @param content {string} Classname for the content
*/
var defaults = {
container: '[data-tab-component]',
trigger: '[role="tab"]',
content: '[role="tabpanel"]'
};
// If there are no settings overrides
var settings = (typeof args !== 'undefined') ? args : defaults;
/**
* @function toggle()
*
* Handles the displaying/hiding of content
*
* @returns null
*/
var toggle = function() {
var parent = this.closest(settings.container),
target = this.getAttribute('aria-controls'),
content = document.getElementById(target),
toggles = parent.querySelectorAll(settings.trigger),
all_content = parent.querySelectorAll(settings.content);
// Update visibility
for (var i = 0, len = toggles.length; i < len; i++) {
toggles[i].setAttribute('aria-selected', 'false');
all_content[i].setAttribute('aria-hidden', 'true');
}
this.setAttribute('aria-selected', 'true');
content.setAttribute('aria-hidden', 'false');
};
/**
* @function bindEventListeners()
*
* Attach event listeners
*
* @returns null
*/
var bindEventListeners = function() {
var trigger = document.querySelectorAll(settings.trigger);
//
// TODO
// Use event delgation to add event handlers
//
for (var i = 0, len = trigger.length; i < len; i++) {
trigger[i].addEventListener('click', function(event) {
toggle.call(this);
});
trigger[i].addEventListener('keydown', function(event) {
if (event.which == 13) {
toggle.call(this);
}
});
};
};
return bindEventListeners();
}
// Create an instance of component
window.onload = function() {
var tabs = new Tabs();
};
{% endjavascript %}