Accordion
Preview
This is amazing. Saved me hours and hours, and ended up with a much better site in the end.
I wish I’d found this years ago. I’ve used this plugin to create detailed, dynamic loops in very complex sites, saving hours on hours of custom programming. It can draw from WordPress loops, ACF fields, you name it. It’s truly fantastic. But what prompted this review was this morning’s revelation,
At a time when many web sites are implemented on templates (wpbakery, Divi, Elementor) that have huge restrictions, this plugin allows you to bypass these restrictions. This plugin deserves your support. In combination with ACF, it gives us a lot of possibilities.
Thanks for this! It allowed us to replace a huge chunk of code that was tangled up in a Pro theme when we redid a website (in an effort to simplify it). We used the plugin to pull a title, a preview (featured image), a download URL, and a PDF
I was looking since days for a way to show and sort lists of custom fields and only with this plugin I could finally manage to create those kind of lists.
<Set label>Accordion</Set> <!-- Replace with an accessibility label for the accordion -->
<Set query=accordion_query post_type=post count=5 /> <!-- Replace with loop query parameters to fetch the accordion content -->
<Set template=accordion_title><Field title /></Set> <!-- Replace with field name for the accordion section titles -->
<Set template=accordion_content><Field excerpt auto=true words=50 /></Set> <!-- Replace with field name for the accordion section content -->
<Set id_prefix><Format case=pascal><Get label /></Format></Set>
<div class="accordion" aria-label="{Get label}" role="tablist">
<Loop query=accordion_query>
<div class="accordion__section {If count value=1}active{/If}">
<button class="accordion__button"
role="tab"
aria-expanded="{If count value=1}true{Else /}false{/If}"
aria-controls="{Get id_prefix}Section{Get loop=count}"
id="{Get id_prefix}Tab{Get loop=count}"><Get template=accordion_title /></button>
<div class="accordion__panel"
tabindex="0"
role="tabpanel"
id="{Get id_prefix}Section{Get loop=count}"
aria-labelledby="{Get id_prefix}Tab{Get loop=count}"
tag-attributes="{If count value=1}tabindex='0'{Else /}hidden tabindex='-1'{/If}">
<div class="accordion__panel__inner">
<Get template=accordion_content />
</div>
</div>
</div>
</Loop>
</div>
$border-color: hsl(243, 100%, 93%);
$title-color: hsl(247, 75%, 5%);
$title-color-hover: hsl(243, 68%, 48%);
$font-family: Arial, Helvetica, sans-serif;
$title-size: 1.2rem;
.accordion {
&__section {
border-bottom: 2px dashed $border-color;
&.active { padding-bottom: 1.5em; }
}
&__section .accordion__button {
width: 100%;
background-color: transparent;
box-shadow: unset;
border: unset;
border-radius: unset;
text-align: left;
color: inherit;
display: flex;
justify-content: flex-start;
padding: 1em 0;
font-family: $font-family;
font-size: $title-size;
color: $title-color;
font-weight: 600;
transition-property: color rotate;
transition-timing-function: ease-in-out;
transition-duration: 0.2s;
&:hover, &:focus {
color: $title-color-hover;
}
&::after {
content: "";
display: inline-block;
border-width: 0 0 2px 2px;
border-style: solid;
rotate: -45deg;
width: .625em;
height: .625em;
vertical-align: middle;
margin-left: auto;
}
&[aria-expanded="true"]::after {
rotate: -225deg;
}
}
&__panel {
display: grid;
grid-template-rows: 0fr;
transition-property: grid-template-rows background-color;
transition-timing-function: ease-in-out;
transition-duration: 0.2s;
&__inner {
overflow: hidden;
padding-left: 2em;
padding-right: 2em;
> :is(p, ul, ol) {
&:first-child, &:empty {
margin-block-start: 0;
}
&:last-child, &:empty {
margin-block-end: 0;
}
li p {
margin: 0;
}
}
}
&:not([hidden]) {
grid-template-rows: 1fr;
}
}
}
function initializeAccordion(accordionElement) {
const accordionSections = accordionElement.querySelectorAll('.accordion__section');
const accordionButtons = accordionElement.querySelectorAll('.accordion__button');
const accordionPanels = accordionElement.querySelectorAll('.accordion__panel');
let focusedAccordionButton = null;
accordionButtons.forEach((button, index) => {
button.addEventListener("click", () => {
toggleAccordionPanel(index);
});
button.addEventListener("keydown", (e) => {
handleAccordionButtonKeydown(e, index);
});
button.addEventListener("focus", () => {
focusedAccordionButton = button;
});
button.addEventListener("blur", () => {
focusedAccordionButton = null;
});
});
document.addEventListener("keydown", handleDocumentKeydown);
accordionPanels.forEach((panel) => {
panel.addEventListener("keydown", (e) => {
if (e.key === "ArrowUp" || e.key === "ArrowDown") {
e.preventDefault();
}
});
});
function toggleAccordionPanel(index) {
const expanded = accordionButtons[index].getAttribute("aria-expanded") === "true";
accordionButtons.forEach((button) => {
button.setAttribute("aria-expanded", "false");
});
accordionPanels.forEach((panel) => {
panel.setAttribute("hidden", "true");
panel.setAttribute("tabindex", "-1");
});
accordionSections.forEach((section) => {
section.classList.remove("active");
});
accordionButtons[index].setAttribute("aria-expanded", String(!expanded));
if (!expanded) {
accordionPanels[index].removeAttribute("hidden");
accordionSections[index].classList.add("active");
accordionPanels[index].setAttribute("tabindex", "0");
} else {
accordionPanels[index].setAttribute("hidden", "true");
accordionSections[index].classList.remove("active");
}
if (!expanded) {
accordionButtons[index].focus();
}
}
function handleAccordionButtonKeydown(e, index) {
switch (e.key) {
case "ArrowDown":
e.preventDefault();
if (index < accordionButtons.length - 1) {
accordionButtons[index + 1].focus();
} else {
accordionButtons[0].focus();
}
break;
case "ArrowUp":
e.preventDefault();
if (index > 0) {
accordionButtons[index - 1].focus();
} else {
accordionButtons[accordionButtons.length - 1].focus();
}
break;
case " ":
e.preventDefault();
toggleAccordionPanel(index);
break;
}
}
function handleDocumentKeydown(e) {
if (e.key === "Tab" && focusedAccordionButton && e.shiftKey) {
accordionPanels.forEach((panel) => {
panel.setAttribute("tabindex", "-1");
});
accordionPanels[Array.from(accordionButtons).indexOf(focusedAccordionButton)].setAttribute("tabindex", "0");
}
}
}
document.addEventListener("DOMContentLoaded", () => {
const accordions = document.querySelectorAll('.accordion');
accordions.forEach((accordion) => {
initializeAccordion(accordion);
});
});