The bit

The code

Template

<div class="">
  <button data-expandable aria-expanded="false" aria-controls="expandable-content-1" class="flex gap-3 itemx-center">
    <span>Toggle me please</span>
    <span class="font-bold plus transition-all">+</span>
  </button>
  <div class="mt-3" id="expandable-content-1" aria-hidden="true" data-expandable-content>
    <div class="overflow-hidden">
      <p>
        Id eu nisl nunc mi. Proin nibh nisl condimentum id.
        Nunc aliquet bibendum enim facilisis <a href="#" class="underline">gravida</a> neque convallis a cras. Magna fringilla urna porttitor rhoncus dolor purus non.
      </p>
    </div>
  </div>
</div>

Script

document.addEventListener('click', (event) => {
  const target = event.target as HTMLElement;
  const toggle = target.closest('[data-expandable]') as HTMLButtonElement;
  if (!toggle) return;
  
  const contentID = toggle.getAttribute('aria-controls') ?? 'xxx';
  const contentElement = document.getElementById(contentID);
  if (!contentElement) {
    console.warn(
      'No target content found for this toggle. Maybe you forget to add the aria-controls attribute or the target content element is missing',
      toggle,
    );
    return;
  }

  if (!contentElement.style.getPropertyValue('--duration')) {
    contentElement.style.setProperty('--duration', `${0.3 + contentElement.scrollHeight / 1000}s`);
  }

  const isExpanded = 'true' === toggle.getAttribute('aria-expanded');

  if (isExpanded) {
    toggle.setAttribute('aria-expanded', 'false');
    contentElement.setAttribute('aria-hidden', 'true');
    contentElement.setAttribute('inert', 'true');
  } else {
    toggle.setAttribute('aria-expanded', 'true');
    contentElement.setAttribute('aria-hidden', 'false');
    contentElement.removeAttribute('inert');
  }
});

Styles

[data-expandable][aria-expanded="true"] {
  .plus {
    transform: rotate(135deg);
  }
}
[data-expandable-content] {
  display: grid;
  grid-template-rows: 1fr;
  transition-delay: var(--delay, 0s);
  transition-duration: var(--duration, 0.3s);
  transition-property: 'display';

  &:is([aria-hidden='true']) {
    grid-template-rows: 0fr;
  }
}