<div class="accordion" data-accordion-container data-accordion-config-single="true">
    <article class="accordion__panel" data-accordion-panel>
        <h3 class="accordion__header">
            <button class="accordion__button h4" data-accordion-button aria-expanded="false">
        Test Heading
        <svg class="accordion__arrow" viewBox="0 0 407.437 407.437" role="presentation" aria-hidden="true">
          <polygon points="386.258,91.567 203.718,273.512 21.179,91.567 0,112.815 203.718,315.87 407.437,112.815 "/>
        </svg>
      </button>
        </h3>
        <div class="accordion__window" data-accordion-window>
            <div class="accordion__content" data-accordion-content>
                <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vestibulum sollicitudin suscipit laoreet. Ut tristique nulla tempus commodo pharetra. Nam auctor, urna id consequat vulputate, ipsum ante pulvinar dolor, sed rhoncus neque enim eu
                    ipsum. Mauris viverra interdum mattis. Integer nec molestie tellus. Phasellus nec semper nibh, eget sagittis neque. <a href="https://www.w3.org/TR/wai-aria-practices/examples/accordion/accordion.html" target="_blank" rel="noopener">Read more about Accordions</a></p>
            </div>
        </div>
    </article>
    <article class="accordion__panel" data-accordion-panel>
        <h3 class="accordion__header">
            <button class="accordion__button h4" data-accordion-button aria-expanded="false">
        Test Heading II
        <svg class="accordion__arrow" viewBox="0 0 407.437 407.437" role="presentation" aria-hidden="true">
          <polygon points="386.258,91.567 203.718,273.512 21.179,91.567 0,112.815 203.718,315.87 407.437,112.815 "/>
        </svg>
      </button>
        </h3>
        <div class="accordion__window" data-accordion-window>
            <div class="accordion__content" data-accordion-content>
                <p>Lorem ipsum dolor</p>
            </div>
        </div>
    </article>
    <article class="accordion__panel" data-accordion-panel>
        <h3 class="accordion__header">
            <button class="accordion__button h4" data-accordion-button aria-expanded="false">
        Test Heading III
        <svg class="accordion__arrow" viewBox="0 0 407.437 407.437" role="presentation" aria-hidden="true">
          <polygon points="386.258,91.567 203.718,273.512 21.179,91.567 0,112.815 203.718,315.87 407.437,112.815 "/>
        </svg>
      </button>
        </h3>
        <div class="accordion__window" data-accordion-window>
            <div class="accordion__content" data-accordion-content>
                <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vestibulum sollicitudin suscipit laoreet. Ut tristique nulla tempus commodo pharetra. Nam auctor, urna id consequat vulputate, ipsum ante pulvinar dolor, sed rhoncus neque enim eu
                    ipsum. Mauris viverra interdum mattis. Integer nec molestie tellus. Phasellus nec semper nibh, eget sagittis neque.</p>
            </div>
        </div>
    </article>
</div>
<div class="accordion" data-accordion-container data-accordion-config-single="{{ config.single }}">
  {% for item in panels %}
  <article class="accordion__panel" data-accordion-panel>
    <h3 class="accordion__header">
      <button class="accordion__button h4" data-accordion-button aria-expanded="false">
        {{ item.heading }}
        <svg class="accordion__arrow" viewBox="0 0 407.437 407.437" role="presentation" aria-hidden="true">
          <polygon points="386.258,91.567 203.718,273.512 21.179,91.567 0,112.815 203.718,315.87 407.437,112.815 "/>
        </svg>
      </button>
    </h3>
    <div class="accordion__window" data-accordion-window>
      <div class="accordion__content" data-accordion-content>
        {{ item.content }}
      </div>
    </div>
  </article>
  {% endfor %}
</div>
{
  "config": {
    "single": true
  },
  "panels": [
    {
      "heading": "Test Heading",
      "content": "<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vestibulum sollicitudin suscipit laoreet. Ut tristique nulla tempus commodo pharetra. Nam auctor, urna id consequat vulputate, ipsum ante pulvinar dolor, sed rhoncus neque enim eu ipsum. Mauris viverra interdum mattis. Integer nec molestie tellus. Phasellus nec semper nibh, eget sagittis neque. <a href=\"https://www.w3.org/TR/wai-aria-practices/examples/accordion/accordion.html\" target=\"_blank\" rel=\"noopener\">Read more about Accordions</a></p>"
    },
    {
      "heading": "Test Heading II",
      "content": "<p>Lorem ipsum dolor</p>"
    },
    {
      "heading": "Test Heading III",
      "content": "<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vestibulum sollicitudin suscipit laoreet. Ut tristique nulla tempus commodo pharetra. Nam auctor, urna id consequat vulputate, ipsum ante pulvinar dolor, sed rhoncus neque enim eu ipsum. Mauris viverra interdum mattis. Integer nec molestie tellus. Phasellus nec semper nibh, eget sagittis neque.</p>"
    }
  ]
}
  • Content:
    /*
     * accordion styles
     *
     *****************************************************************************/
    
    .accordion {
        margin: 5px; // allows room to display full border when focused / Safari 
    
      &__panel + &__panel {
        border-top: 1px solid color(neutral, lighter);
      }
    
      &__header {
        margin: 0;
      }
    
      &__button {
        z-index: 0;
        position: relative;
        display: flex;
        align-items: center;
        justify-content: space-between;
        margin: 0;
        border: 0;
        padding: 20px 14px;
        width: 100%;
        background-color: color(neutral, lightest);
        text-align: left;
        appearance: none;
    
        &:focus { z-index: 1; } // allows the full focus state border to be visible 
      }
    
      &__arrow {
        width: 14px;
        height: 14px;
        transition: .2s ease-in-out;
    
        [aria-expanded='true'] & {
          transform: rotate(180deg);
        }
      }
    
      &__window {
        margin: 0;
        height: 0;
        overflow: hidden;
        visibility: hidden;
        transition: .2s ease-in-out;
      }
    
      &__content {
        padding: 20px 14px;
      }
    
      :nth-child(1) {
        margin-top: 0;
      }
    
      :last-child {
        margin-bottom: 0;
      }
    }
    
  • URL: /components/raw/accordion/_accordion.scss
  • Filesystem Path: components/accordion/_accordion.scss
  • Size: 1.1 KB
  • Content:
    // Polyfills
    if (window.Element && !Element.prototype.closest) {
      Element.prototype.closest =
      function(s) {
        var matches = (this.document || this.ownerDocument).querySelectorAll(s),
          i,
          el = this;
        do {
          i = matches.length;
          while (--i >= 0 && matches.item(i) !== el) {};
        } while ((i < 0) && (el = el.parentElement));
    
        return el;
      };
    }
    
    if (!Array.prototype.forEach) {
        Array.prototype.forEach = function (callback, thisArg) {
            thisArg = thisArg || window;
            for (var i = 0; i < this.length; i++) {
                callback.call(thisArg, this[i], i, this);
            }
        };
    }
    
    if (window.NodeList && !NodeList.prototype.forEach) {
      NodeList.prototype.forEach = Array.prototype.forEach;
    }
    
    // Accordion Functionality
    const buttons = document.querySelectorAll('[data-accordion-button]');
    
    for (let i = 0; i < buttons.length; i++) {
    
      buttons[i].addEventListener('click', (e) => {
        const toggle = (e.target.getAttribute('aria-expanded') === 'true') ? false : true;
        const root = buttons[i].closest('[data-accordion-container]');
        const panel = buttons[i].closest('[data-accordion-panel]');
        const panelSiblings = getSiblings(panel);
        const window = panel.querySelector('[data-accordion-window]');
        const content = panel.querySelector('[data-accordion-content]');
    
        buttons[i].setAttribute('aria-expanded', toggle);
        buttons[i].setAttribute('tabindex', '0');
        toggle ? 
          window.setAttribute('style', `height: ${content.offsetHeight}px; visibility: visible;`) : 
          window.setAttribute('style', 'height: 0; visibility: hidden;');
    
        if (root.getAttribute('data-accordion-config-single') === 'true') {
          panelSiblings.forEach((sibling) => {
            const siblingButton = sibling.querySelector('[data-accordion-button]');
            const siblingWindow = sibling.querySelector('[data-accordion-window]');
      
            siblingButton.setAttribute('aria-expanded', 'false');
            siblingWindow.setAttribute('style', 'height: 0; visibility: hidden;');
          });
        }
      });
    
      // Arrow key controls
      buttons[i].addEventListener('keydown', (e) => {
    
        if (e.keyCode === 38) {
          if (i === 0) {
            buttons[buttons.length - 1].focus();
          } else {
            buttons[i - 1].focus();
          }
        }
    
        if (e.keyCode === 40) {
          if (i === buttons.length - 1) {
            buttons[0].focus();
          } else {
            buttons[i + 1].focus();
          }
        }
      });
    }
    
    function getSiblings(element) {
      let siblings = [];
      let sibling = element.parentNode.firstChild;
    
      while (sibling) {
        if (sibling.nodeType === 1 && sibling !== element) {
                siblings.push(sibling);
        }
        sibling = sibling.nextSibling
      }
    
      return siblings;
    };
    
  • URL: /components/raw/accordion/accordion.js
  • Filesystem Path: components/accordion/accordion.js
  • Size: 2.7 KB

Usage

Accordions deliver large amounts of content in a small space through progressive disclosure, and work especially well whenever vertical space is at premium. All accordion panels are closed by default – expanding when the user clicks one of the accordion section titles.

This accordion comes with a configuration attribute, data-accordion-config-single that enables you to decide whether you want the accordion panel siblings to close or remain opened when selecting another panel. The example above is set to true.

Labelling Expectations

  • Each accordion header title is contained in an element with role="button".
  • If the accordion panel is visible, the header button element should have aria-expanded set to true. If the panel is not visible, aria-expanded is set to false.

Focus Expectations

  • Headers should have visible keyboard focus state
  • All keyboard interactions relate to when headers are focused

Keyboard Expectations

  • Tab = Move to next focusable element
  • Enter or Space = Expand/Collapse Panel
  • Shift + Tab = Move to previous focusable element
  • = Cycle headers when header focused
  • Control + Option = Navigate through accordion content with screen reader

Screen Reader Expectations

When interacting with accordions, screen readers should announce the following information:

  • Headers
  • Button (or other actionable element)
  • Current state of Button - expanded or collapsed
  • Panel content will be read if navigated to

Tab Order Expectations

When navigating through an accordion, the following tab order is expected:

  1. The next tab keypress will advance to the next Header text.
  2. When expanded and focused, all focusable elements inside the Accordion Panel are included in the tab order.