Accordions: 1, 5 and 10 minute versions...all accessible [quicka11y]
Tags: javascript, css, webdev, a11y
Published: 2023-09-26 13:43:24

I saw a post a few days ago that was a "CSS only accordion". 

Sadly the techniques used were not very accessible. Plus, they made things so very difficult for themselves!

However, I did at least get inspired by that post.

So if you liked that post, then this post will blow your mind, I am going to create:
- a HTML only accordion, 
- a pretty one with just HTML and CSS 
- a nice animated one using HTML, CSS and JS. 

And to make it more fun, we will do it in the "1 minute, 5 minute, 10 minute" format!

## The 1 minute accordion (HTML only)
Wow 1 minute isn't long to build an accordion now is it?

I mean, I have already wasted 15 seconds saying that. 

You know what though, I am not worried, accordions are super easy in HTML. 

Yes, I am still talking, I really do have time.

OK, enough stalling, here is the code:

``` html
<details>
  <summary>Accordion item 1</summary>
  Accordion 1 content
</details>
<details>
  <summary>Accordion item 2</summary>
  Accordion 2 content
</details>
<details>
  <summary>Accordion item 3</summary>
  Accordion 3 content
</details>
```

Phew, 12 seconds to spare!

### Wait, what? That is an accordion?
You bet your sweet behind it is! 

A fully functioning and pretty accessible accordion!

Wanna see it in action?

{% codepen https://codepen.io/GrahamTheDev/pen/zYyPdKQ %}

It is:
- **keyboard accessible:** you can <kbd>tab</kbd> through each accordion item and open and close it with the <kbd>Enter</kbd> key.
- **keyboard friendly:** it has built-in focus indicators, for free!
- **functional:** No JS and yet it can open and close (and exposes the state to assistive technology, making it more accessible!), even more free goodies! 😱

It is pretty amazing, except for one big problem...it is ugly AF. Ewwwww. Let's fix that!

## The 5 minute accordion (HTML and CSS)
Ok so we have our base HTML, what can we do with it?

Well luckily the `<summary>` and `<detail>` elements are quite easy to style!

The only thing that has changed in the HTML here is that we have added a `<div>` around all of the content that is hidden, to make it easier to position it.

Oh and I also added a single attribute "`open`" to the second item, to show another trick that the `<details>` element has! A single attribute to set the open state!

After a few minutes of styling, we have a (in my opinion) attractive accordion with just HTML and CSS.

The only thing that is unusual here is:

``` css
details > summary::marker,
details > summary::-webkit-details-marker {
  display: none;
}
```

Which allows us to hide the default open and close marker so we can create a custom one using an SVG background image (in this instance).

{% codepen https://codepen.io/GrahamTheDev/pen/MWZOvoL %}


Looking good...yes you, also the accordion isn't looking bad either! 

Shall we make it all fancy now?

## The 10 minute accordion (HTML, CSS and JS)
The pure HTML and CSS accordion is great, but it would be nice if we had a subtle animation when we open and close each `<details>` element that makes up the accordion.

Luckily with a little JS magic we can achieve this. 

Here is the JS I came up with (and partially stole! Hey, I only had 5 minutes to add JS functionality...give me a break!).

The code below includes some explanations at key points:

``` js
class Accordion {
  constructor(el) {
    this.el = el;
    this.summary = el.querySelector('summary');
    this.content = el.querySelector('.content');
    this.animation = null;
    this.isClosing = false;
    this.isExpanding = false;
    this.summary.addEventListener('click', (e) => this.onClick(e));
  }

  onClick(e) {
    e.preventDefault();
    this.el.style.overflow = 'hidden';
    if (this.isClosing || !this.el.open) {
      this.open();
    } else if (this.isExpanding || this.el.open) {
      this.shrink();
    }
  }

  shrink() {
    this.isClosing = true;
    //we need to grab the height of the summary and the overall element before we shrink it.
    const startHeight = `${this.el.offsetHeight}px`;
    const endHeight = `${this.summary.offsetHeight}px`;
    //stop previous animations.
    if (this.animation) {
      this.animation.cancel();
    }
    
    //start the new animation, starting at full height and reducing down to the minimum
    this.animation = this.el.animate({
      height: [startHeight, endHeight]
    }, {
      duration: 400,
      easing: 'ease-out'
    });
    
    
    this.animation.onfinish = () => this.onAnimationFinish(false);
    this.animation.oncancel = () => this.isClosing = false;
  }

  open() {
    //important step, we grab the current height and set it, this could be mid animation so we need to set it each time.
    this.el.style.height = `${this.el.offsetHeight}px`;
    this.el.open = true;
    window.requestAnimationFrame(() => this.expand());
  }

  expand() {
    this.isExpanding = true;
    const startHeight = `${this.el.offsetHeight}px`;
    const endHeight = `${this.summary.offsetHeight + this.content.offsetHeight}px`;
    
    if (this.animation) {
      this.animation.cancel();
    }
    
    this.animation = this.el.animate({
      height: [startHeight, endHeight]
    }, {
      duration: 400,
      easing: 'ease-out'
    });
    this.animation.onfinish = () => this.onAnimationFinish(true);
    this.animation.oncancel = () => this.isExpanding = false;
  }

  onAnimationFinish(open) {
    this.el.open = open;
    this.animation = null;
    this.isClosing = false;
    this.isExpanding = false;
    this.el.style.height = this.el.style.overflow = '';
  }
}

document.querySelectorAll('details').forEach((el) => {
  new Accordion(el);
});

```

And putting it all together we get this beautiful accordion:

{% codepen https://codepen.io/GrahamTheDev/pen/QWzmgeN %}

And there you have it, a 1 minute, 5 minute and 10 minute accordion. But we are not done yet!

## Summary on `<summary>`
Yes...I had to call this section that, I am sorry (not sorry!) 😂.

As you can see, in just a few short minutes we can create a beautiful accordion. 

I will admit, the CSS only one actually took about 7 minutes and the JS took about 15 minutes...but I am slow at coding OK?

Either way though, we created an accessible and easy to maintain accordion.

And no, we are still not done yet...let's level it up a bit further! 💪🏼

## Bonus Copy and Paste Component version
As an added bonus for making it this far, here is our accordion, restructured as a component you can reuse on your site. (this is the super secret "1 hour" version! 😂)

I even added an optional parameter that allows you to make it so that when you open one section the other sections close!

Additionally I added one more important feature: we test for `prefers-reduced-motion`.

### `prefers-reduced-motion`
In the previous example we had an animation on open and close.

Now, the problem with animations is that for certain people they can be unsettling and or cause them to feel nauseous and or dizzy.

So the browser provides us with a media query we can use to check if that person has a preference for reduced motion.

By querying that media query in our JS we can activate or remove the `eventListeners` that are responsible for the animation and respect their preferences / needs.

You can read more about [`prefers-reduced-motion` on MDN](https://developer.mozilla.org/en-US/docs/Web/CSS/@media/prefers-reduced-motion)

So with that in mind, let's check out how to use the component and the options!

### How to use the component!

All you need to do is:
- Grab the HTML and replace the `<summary>` contents and the contents inside the `<div class="contents">` section with your own! (Make sure to grab the outer `<div>` with class `tota11y-accordion` or it won't work!)  
- Include the CSS and JS!
- Add any options that you want to set (see section after CodePen).

{% codepen https://codepen.io/GrahamTheDev/pen/yLGKzPZ %}

### Configurable Options
There are a couple of options you can change in the JS

``` js

// create a new Tota11yAccordion, passing the wrapper element and then the options.
new Tota11yAccordion(el,{
      // close other items in the accordion when one is opened.
      closeOthers: true, // default false
     
      // set the duration of the animation
      duration: 800, // default 400

      // set the animation easing
      easing: 'ease-in' // default ease-out
  });
```

and there are a few styling options that I have added as variables on the CSS `:root` element, so you can do some styling there without having to dig through everything.

As a final point of interest, I have prefixed all of the CSS with `.tota11y-accordion` so that the styles will not bleed into the rest of your page.

## Final Thoughts!
As you can see, the `<details>` and `<summary>` elements are really powerful and also straight forward to use and style.

And, most importantly, they are **accessible**. 

Easier to create than other accordions, beautiful and inclusive components? That is the beauty of accessibility first thinking! 🚀

### Speaking of Accessibility
Have you heard of my business [totally.dev](https://tota11y.dev)?

If not, do you own a SaaS or manage a team of developers and want to learn how to make your applications accessible? 

Do you want to learn how to unlock a new audience of over 1 billion people with disabilities who want to use your product?

If that sounds interesting then check out [totally.dev](https://tota11y.dev)!

We offer Accessibility as a Service (yes, it's abbreviation says "AaaS"...yes we lean into it in the marketing! 😂) so that you can take your product and team from accessibility zero to accessibility hero!

Please do check out my AaaS...and let me know if you like it! (see...we lean into it hard lol).

### Are you a developer wanting to learn about accessibility?
I am running live streams on accessibility, coupled with some tutoring for those who are interested. If you want to take part, send me a DM on Twitter, my account is [GrahamTheDev](https://twitter.com/GrahamTheDev), I would love to have you as part of the #A11y100 💪🏼💗!