Accordion CSS Animation

A pure-CSS grid-template-rows accordion with no dependencies. Toggling an item animates the content panel open or closed using a CSS grid row transition. An optional data attribute controls whether sibling items collapse automatically when a new one opens.

accordioncssno dependenciesanimationdata attributes

Code

HTML
html
<div data-accordion-close-siblings="true" data-accordion-css-init="" class="accordion-css">
  <ul class="accordion-css__list">
    <li data-accordion-status="not-active" class="accordion-css__item">
      <div data-hover="" data-accordion-toggle="" class="accordion-css__item-top">
        <h3 class="accordion-css__item-h3">This is just a simple CSS Accordion</h3>
        <div class="accordion-css__item-icon">
          <svg class="accordion-css__item-icon-svg" xmlns="http://www.w3.org/2000/svg" width="100%" viewbox="0 0 36 36" fill="none"><path d="M28.5 22.5L18 12L7.5 22.5" stroke="currentColor" stroke-width="3" stroke-miterlimit="10"></path></svg>
        </div>
      </div>
      <div class="accordion-css__item-bottom">
        <div class="accordion-css__item-bottom-wrap">
          <div class="accordion-css__item-bottom-content">
            <p class="accordion-css__item-p">Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.</p>
          </div>
        </div>
      </div>
    </li>
    <li data-accordion-status="not-active" class="accordion-css__item">
      <div data-hover="" data-accordion-toggle="" class="accordion-css__item-top">
        <h3 class="accordion-css__item-h3">Choose to close the siblings: true/false</h3>
        <div class="accordion-css__item-icon">
          <svg class="accordion-css__item-icon-svg" xmlns="http://www.w3.org/2000/svg" width="100%" viewbox="0 0 36 36" fill="none"><path d="M28.5 22.5L18 12L7.5 22.5" stroke="currentColor" stroke-width="3" stroke-miterlimit="10"></path></svg>
        </div>
      </div>
      <div class="accordion-css__item-bottom">
        <div class="accordion-css__item-bottom-wrap">
          <div class="accordion-css__item-bottom-content">
            <p class="accordion-css__item-p">Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.</p>
          </div>
        </div>
      </div>
    </li>
    <li data-accordion-status="not-active" class="accordion-css__item">
      <div data-hover="" data-accordion-toggle="" class="accordion-css__item-top">
        <h3 class="accordion-css__item-h3">Super easy, try it for yourself!</h3>
        <div class="accordion-css__item-icon">
          <svg class="accordion-css__item-icon-svg" xmlns="http://www.w3.org/2000/svg" width="100%" viewbox="0 0 36 36" fill="none"><path d="M28.5 22.5L18 12L7.5 22.5" stroke="currentColor" stroke-width="3" stroke-miterlimit="10"></path></svg>
        </div>
      </div>
      <div class="accordion-css__item-bottom">
        <div class="accordion-css__item-bottom-wrap">
          <div class="accordion-css__item-bottom-content">
            <p class="accordion-css__item-p">Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.</p>
          </div>
        </div>
      </div>
    </li>
  </ul>
</div>
CSS
css
.accordion-css {
  max-width: 29em;
  position: relative;
}

.accordion-css__list {
  grid-column-gap: .5em;
  grid-row-gap: .5em;
  flex-flow: column;
  margin-top: 0;
  margin-bottom: 0;
  padding-left: 0;
  display: flex;
  position: static;
}

.accordion-css__item {
  background-color: #efeeec;
  border-radius: .5em;
  list-style: none;
}

.accordion-css__item-top {
  grid-column-gap: 1em;
  grid-row-gap: 1em;
  cursor: pointer;
  justify-content: space-between;
  align-items: center;
  padding: 1em 1em 1em 1.5em;
  display: flex;
}

.accordion-css__item-bottom {
  transition: grid-template-rows 0.6s cubic-bezier(0.625, 0.05, 0, 1);
  grid-template-rows: 0fr;
  display: grid;
  position: relative;
  overflow: hidden;
}

[data-accordion-status="active"] .accordion-css__item-bottom {
   grid-template-rows: 1fr;
}

.accordion-css__item-bottom-wrap {
  flex-flow: column;
  height: 100000%;
  display: flex;
  position: relative;
  overflow: hidden;
}

.accordion-css__item-bottom-content {
  padding-bottom: 1.5em;
  padding-left: 1.5em;
  padding-right: 1.5em;
}

.accordion-css__item-h3 {
  margin-top: 0;
  margin-bottom: 0;
  font-size: 1.125em;
  font-weight: 500;
  line-height: 1.3;
}

.accordion-css__item-icon {
  transition: transform 0.6s cubic-bezier(0.625, 0.05, 0, 1);
  background-color: #d4cee5;
  border-radius: 50%;
  flex-shrink: 0;
  justify-content: center;
  align-items: center;
  width: 2em;
  height: 2em;
  display: flex;
  transform: rotate(180deg);
}

[data-accordion-status="active"] .accordion-css__item-icon {
   transform: rotate(0.001deg);
}

.accordion-css__item-icon-svg {
  width: 1em;
}

.accordion-css__item-p {
  color: #2c2c2c;
  margin-bottom: 0;
  font-size: .875em;
  line-height: 1.5;
}
JavaScript
javascript
function initAccordionCSS() {
  document.querySelectorAll('[data-accordion-css-init]').forEach((accordion) => {
    const closeSiblings = accordion.getAttribute('data-accordion-close-siblings') === 'true';

    accordion.addEventListener('click', (event) => {
      const toggle = event.target.closest('[data-accordion-toggle]');
      if (!toggle) return; // Exit if the clicked element is not a toggle

      const singleAccordion = toggle.closest('[data-accordion-status]');
      if (!singleAccordion) return; // Exit if no accordion container is found

      const isActive = singleAccordion.getAttribute('data-accordion-status') === 'active';
      singleAccordion.setAttribute('data-accordion-status', isActive ? 'not-active' : 'active');

      // When [data-accordion-close-siblings="true"]
      if (closeSiblings && !isActive) {
        accordion.querySelectorAll('[data-accordion-status="active"]').forEach((sibling) => {
          if (sibling !== singleAccordion) sibling.setAttribute('data-accordion-status', 'not-active');
        });
      }
    });
  });
}

// Initialize Accordion CSS
document.addEventListener('DOMContentLoaded', () => {
  initAccordionCSS();
});

Attributes

NameTypeDefaultDescription
data-accordion-css-init""Applied to the accordion root wrapper. Presence triggers script initialisation; one listener is attached per accordion instance.
data-accordion-close-siblings"true" | "false""true"Controls whether opening an item automatically closes all other open items in the same accordion. Set to "false" to allow multiple items open simultaneously.
data-accordion-status"not-active" | "active""not-active"Applied to each accordion item. Toggled between "active" and "not-active" on click; CSS targets this attribute to animate the content panel and rotate the icon.
data-accordion-toggle""Applied to the clickable header element inside each item. The script uses closest('[data-accordion-toggle]') to confirm the click target before processing.

Notes

  • No external dependencies — pure vanilla JS and CSS.
  • The open/close animation uses CSS grid-template-rows transitioning from 0fr to 1fr — no JS height calculations required.
  • The icon rotation is handled entirely by CSS targeting [data-accordion-status="active"].
  • Multiple independent accordion instances on the same page are supported; each [data-accordion-css-init] element gets its own scoped listener.

Guide

Close siblings [true/false]

You can set [data-accordion-close-siblings="false"] to prevent the accordion from closing sibling items when toggling an item.