This Pro tutorial provides the steps to implement an accordion using Alpine.js in WordPress that pulls the values of sub fields of a Meta Box group for a dynamic FAQ accordion.
Step 1
Install Meta Box and Meta Box AIO plugins.
Create a new field group having a group-type of field with two sub fields or import this.
In this example, faq group field having question text-type sub field and answer wysiwyg-type field.

Set the field group to appear on your desired post type, in this example: page.
Step 2
Edit your Page(s) and populate the field group.

Step 3
Let’s load Alpine.js and its Collapse plugin.
Edit the Template that applies to your singular post type (all Pages in this example) or if you want to set this up on a specific Page, that Page with Bricks.
If using Bricks, go to Settings → PAGE SETTINGS → CUSTOM CODE and paste the following under “Header Scripts”:
<!-- Alpine Plugins -->
<script defer src="https://unpkg.com/@alpinejs/collapse@3.x.x/dist/cdn.min.js"></script>
<!-- Alpine Core -->
<script src="//unpkg.com/alpinejs" defer></script>
Paste this in Custom CSS:
.faqs {
display: flex;
flex-direction: column;
gap: 16px;
max-width: 800px;
margin: 0 auto;
}
.faq {
background-color: #ffffff;
box-shadow: rgba(0, 0, 0, 0) 0px 0px 0px 0px, rgba(0, 0, 0, 0) 0px 0px 0px 0px, rgba(0, 0, 0, 0.1) 0px 1px 3px 0px, rgba(0, 0, 0, 0.1) 0px 1px 2px -1px;
border-radius: 0.5rem;
}
.faq__question {
font-size: 1.4em;
}
.faq__button {
padding: 2rem 2.5rem;
font-weight: 700;
width: 100%;
background-color: transparent;
display: flex;
justify-content: space-between;
}
body.bricks-is-frontend .faq__button:focus {
outline: none;
}
.faq__answer {
padding: 0 2.5rem 2rem 2.5rem;
}
.faq__answer ul {
padding-left: 20px;
}
Add a Code element where you’d like to show the accordion. Paste this code:
<?php
// Avoid Undefined Function Error when Meta Box is not active.
if ( ! function_exists( 'rwmb_meta' ) ) {
function rwmb_meta( $key, $args = '', $post_id = null ) {
return false;
}
}
// Get the value of faq field and store it in a variable.
$faq = rwmb_meta( 'faq' );
if ( ! empty( $faq ) ) {
// loop counter
$index = 1; ?>
<div x-data="{ active: 1 }" class="faqs">
<?php foreach ( $faq as $faq_row ) {
$question = isset( $faq_row['question'] ) ? $faq_row['question'] : '';
$answer = isset( $faq_row['answer'] ) ? $faq_row['answer'] : ''; ?>
<div x-data="{
id: <?php echo $index; ?>,
get expanded() {
return this.active === this.id
},
set expanded(value) {
this.active = value ? this.id : null
},
}" role="region" class="faq">
<h2 class="faq__question">
<button
x-on:click="expanded = !expanded"
:aria-expanded="expanded"
class="faq__button"
>
<span><?php echo $question; ?></span>
<span x-show="expanded" aria-hidden="true" <?php echo 1 !== $index ? 'style="display: none;"' : ''; ?>>−</span>
<span x-show="!expanded" aria-hidden="true" <?php echo 1 === $index ? 'style="display: none;"' : ''; ?>>+</span>
</button>
</h2>
<div x-show="expanded" x-collapse <?php echo 1 !== $index ? 'style="display: none;"' : ''; ?>>
<div class="faq__answer"><?php echo $answer; ?></div>
</div>
</div>
<?php $index++; } ?>
</div>
<?php }
?>
Toggle Execute code on.
If you want to control/change the transition duration, replace
x-collapse
with
x-collapse.duration.1000ms
and change 1000 as needed.
Explanation of the code
In
<div x-data="{ active: 1 }" class="faqs">
we are setting the first row to be active on page load.
We have defined a loop counter (which is set to +1 at the end of the loop) to set id property of each for loop item i.e., the accordion row (question + answer). So the first row has id of 1, the second has 2 and so on.
When expanded is referenced, its value is obtained from the boolean returned by the get expanded() function.
:aria-expanded="expanded"
For the first row, the value of active is 1 since that’s what we are initializing for the div that has faqs class. And the value of expanded is true on page load.
Hence Alpine.js adds aria-expanded attribute and sets its value to true for the first row’s div. Its value becomes false when a different row is active and that happens via
x-on:click="expanded = !expanded"
which tells Alpine to toggle the value of expanded when a row’s button is clicked.
To prevent FOUC (Flash Of Unstyled Content), it is better to set display: none to elements that are/get hidden using JS via CSS. That is done using code like:
<?php echo 1 !== $index ? 'style="display: none;"' : ''; ?>