Accessible Accordion Content with HTML Details and Summary Elements
Markup
Each accordion item should get wrapped in <details>
with the accordion tab contained in <summary>
e.g.
Title of Item
Accordion content.
Title of Additional Item
Accordion content for additional item.
Title of Third Item
Accordion content for third item.
To hide the default arrows. In CSS:
summary::marker,
summary::-webkit-details-marker {
display: none;
}
To use a custom caret in place of the default arrows, in CSS:
summary {
display: flex; /* allows us to set up the accordion title on the left and the caret on the right and to vertically center the two items */
align-items: center; /* vertically center the carat with the title */
column-gap: 1rem; /* space between the title and the caret */
justify-content: space-between; /* force the title to the left and the caret to the right */
}
/* The Caret */
summary:after {
background: url('path/to/caret.svg') center center no-repeat;
background-size: 1rem 1rem;
content: '';
display: block;
flex-shrink: 0; /* don't let a long title shrink this down */
height: 1rem;
width: 1rem;
}
/* Assuming the Caret is pointed down by default, rotate to an up position when the accordion is open */
details[open] > summary:after {
rotate: 180deg;
}
To only have one open accordion at a time, which is to say, to close all the other accordions when one is clicked, add the same name
attribute to each <details>
element. This is known as an exclusive accordion.
Title of Item
Accordion content.
Title of Additional Item
Accordion content for additional item.
Title of Third Item
Accordion content for third item.
When an accordion tab below a very long open accordion tab opens, the collapsing action of the long tab can pull the now open accordion tab below it "off" the top of the page, hiding it from view. This can be addressed in JavaScript by scrolling to the top of the open accordion tab when it is open.
const scrollOpenSummaryIntoView = () {
document.querySelectorAll("details[name]").forEach(($details) => {
$details.addEventListener("toggle", (e) => {
if ($details.open) {
$details.scrollIntoView({ behavior: "smooth", block: "start" });
}
});
});
}
if ( document.readyState === 'loading' ) {
// Loading hasn't finished yet
document.addEventListener( 'DOMContentLoaded', scrollOpenSummaryIntoView );
} else {
// `DOMContentLoaded` has already fired
scrollOpenSummaryIntoView();
}
If the site has a fixed header, the CSS attribute scroll-margin-top
can be applied to the details
elements to account for it. The value should be the height of the fixed header.
details { scroll-margin-top: 130px; }
Feedback?
Email us at enquiries@kinsa.cc.