Accordion

Accordions are used widely throughout our sites. Often, they are used as a ACF Flexible Section.

Advanced Custom Fields

If working with ACF, you will want to utilize the following fields:

Repeater Field: accordion

Fields:

  • Text: accordion_header
  • Text: accordion_label
  • WYSIWYG: accordion_content

Markup

HTML

<div class="Accordion">

    <button class="Accordion-header" type="button">Accordion Header</button>
    <div class="Accordion-content">
        <h2 class="Accordion-label">Accordion Heading</h2>
        <p>Here is the content of 1st tab <a href="#">link</a></p>
    </div> <!-- //.Accordion-content -->

    <button class="Accordion-header" type="button">Accordion Header</button>
    <div class="Accordion-content">
        <h2 class="Accordion-label">Accordion Heading</h2>
        <p>Here is the content of 2nd tab <a href="#">link</a></p>
    </div> <!-- //.Accordion-content -->

    <button class="Accordion-header" type="button">Accordion Header</button>
    <div class="Accordion-content">
        <h2 class="Accordion-label">Accordion Heading</h2>
        <p>Here is the content of 3rd tab <a href="#">link</a></p>
    </div> <!-- //.Accordion-content -->

    <button class="Accordion-header" type="button">Accordion Header</button>
    <div class="Accordion-content">
        <h2 class="Accordion-label">Accordion Heading</h2>
        <p>Here is the content of 4th tab <a href="#">link</a></p>
    </div> <!-- //.Accordion-content -->

</div> <!-- //.accordion -->

PHP

<?php if( have_rows( 'accordion' ) ): ?>
    
    <div class="Accordion">

        <?php while( have_rows( 'accordion' ) ): the_row(); ?>

            <button class="Accordion-header" type="button"><?php the_sub_field( 'accordion_header' ); ?></button>
            <div class="Accordion-content">
                <h2 class="Accordion-label"><?php the_sub_field( 'accordion_label' ); ?></h2>
                <?php the_sub_field( 'accordion_content' ); ?>
            </div> <!-- //.Accordion-content -->

        <?php endwhile; ?>

    </div> <!-- //.accordion -->

<?php endif; ?>

Twig

<div class="Accordion">

    {% for item in post.get_field('accordion') %}
        
        <button class="Accordion-header" type="button">{{item.accordion_header}}</button>
        <div class="Accordion-content">
            <h2 class="Accordion-label">{{item.accordion_label}}</h2>
            {{item.accordion_content}}
        </div> {# .Accordion-content #}
        
    {% endfor %}

</div>{# .Accordion #}

Styles

CSS

.Accordion-header {
    background: #eeeeee;
    border: 1px solid #ccc;
    cursor: pointer;
    font-size: 1.5em;
    margin-top: 10px;
    padding: 10px 15px;
    position: relative;
    text-align: left;
    width: 100%;
}

.Accordion-header:first-of-type {
    margin-top: 0;
}

.Accordion-header.is-active {
    border-bottom: none;
}

.Accordion-header:after {
    content: "+";
    position: absolute;
    right: 15px;
    transition: transform 0.2s ease-in-out;
    top: 8px;
}

.Accordion-header.is-active:after {
    transform: rotate(45deg);
}

.Accordion-content {
    box-sizing: border-box;
    border: 1px solid #ccc;
    border-top: none;
    display: none;
    padding: 10px 15px;
}

.Accordion-content.is-active {
    display: block;
}

Sass

.Accordion-header {
    background: #eeeeee;
    border: 1px solid #ccc;
    cursor: pointer;
    font-size: 1.5em;
    margin-top: 10px;
    padding: 10px 15px;
    position: relative;
    text-align: left;
    width: 100%;

    &:after {
        content: "+";
        position: absolute;
        right: 15px;
        transition: transform 0.2s ease-in-out;
        top: 8px;
    }

    &:first-of-type {
        margin-top: 0;
    }

    &.is-active {
        border-bottom: none;

        &:after {
            transform: rotate(45deg);
        }
    }
}

.Accordion-content {
    box-sizing: border-box;
    border: 1px solid #ccc;
    border-top: none;
    display: none;
    padding: 10px 15px;

    &.is-active {
        display: block;
    }
}

Javascript

Plugin

/**
 * Objectiv Accessible Accordion
 * 
 * Loosely based off of 10up's Accordion: https://10up.github.io/wp-component-library/component/accordion/index.html
 */
( function() {
    'use strict';

    // Let's make sure that we have an Objectiv object
    if ( 'object' !== typeof window.Objectiv) {
        window.Objectiv = {};
    }

    // Start creating the accordionobject
    window.Objectiv.accordion = function(options) {
        // Make sure that a target is passed into the options object
        if ('undefined' === typeof options.target)
            return false;
        
        // Let's set up our variables
        var accordion = document.querySelector(options.target),
            accordionContent = accordion.getElementsByClassName('Accordion-content'),
            accordionHeader = accordion.getElementsByClassName('Accordion-header');

        // If we don't have an accordion, jump out
        if (!accordion) return;

        // Loop through the headers
        Array.prototype.map.call(accordionHeader, function(value, index) {
            var index = index + 1,
                header = value;

            function accordionClickHandler() {
                var content = value.nextElementSibling,
                    contentLabel = content.getElementsByClassName('Accordion-label')[0];
                
                // Set our active classes
                header.classList.toggle('is-active');
                content.classList.toggle('is-active');

                // Focus on the label in the accordion content
                contentLabel.focus();

                // Let's now set all of our attributes if the accordion is active
                if (content.classList.contains('is-active')) {
                    header.setAttribute( 'aria-selected', 'true' );
					header.setAttribute( 'aria-expanded', 'true' );
					content.setAttribute( 'aria-hidden', 'false' );
                } else {
                    header.setAttribute( 'aria-selected', 'false' );
					header.setAttribute( 'aria-expanded', 'false' );
					content.setAttribute( 'aria-hidden', 'true' );
                }
                
            }

            header.addEventListener( 'click', accordionClickHandler );
        });

        // Set proper attributes for the content and content label
        Array.prototype.map.call(accordionContent, function(value, index) {
            var content = value;

            content.setAttribute( 'aria-hidden', 'true' );
			content.setAttribute( 'role', 'tabpanel' );
        });
    }
})();

Usage

Objectiv.accordion({
	target: '.Accordion', // ID (or class) of accordion container
});