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:
<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?
It is:
- keyboard accessible: you can tab through each accordion item and open and close it with the Enter 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:
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).
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:
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:
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
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 classtota11y-accordion
or it won't work!) - Include the CSS and JS!
- Add any options that you want to set (see section after CodePen).
Configurable Options
There are a couple of options you can change in the 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?
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!
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, I would love to have you as part of the #A11y100 πͺπΌπ!
Top comments (6)
And this will only get better when mixed with the View Transitions API
for animations and the exclusive accordion feature (if it gets added).
Taking common UI elements and getting browsers makers to implement versions will really be a huge accessibility win, provided browsers make styling easy.
"provided browsers make styling easy."...yes, this is the key and where so many things go wrong!
I must have misunderstood the View Transitions API as I thought that was page to page transitions! I better go have another read! ππ
It was originally named the "Page Transition API" but was later renamed, and the scope expanded, to include all changes in the DOM.
πππΌπ
One other option that would be nice is to have a method: openAll for the accordion. This would be useful for printing etc.
Bonus points to set a click event to call openAll when a button in the dom looks like:
(arguably there should be a closeAll and the button could toggle but ...).
The button idea is patterned on the popover implementation.
Also did you creatively acquire (because stole is such an ugly word 8-) the animation from css-tricks.com/how-to-animate-the-...?
No I didn't steal it from there, but it looks like the code I stole from stole from there lol.
The open all and close all option via button is an interesting idea! π