← All Articles A Product of Kinsa Creative

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.