accordion.html
<div style="display: grid; gap: 1rem">

    <p-accordion name="example" class="accordion">
        <summary class="accordion__summary">
            <h3 class="accordion__heading">
                Why do we need to use Vue for an accordion element?
            </h3>
            <div class="accordion__iconWrapper">
                <svg class="accordion__icon icon icon--chevron-right" aria-hidden="true" role="presentation">
                    <use href="/main-icons-sprite.svg#chevron-right" />
                </svg>
            </div>
        </summary>
        <div class="accordion__content">
            <p>
                Technically you don't – the functionality in Pronto does not require <code>p-accordion</code> to be
                used.
            </p>
            <p>
                However, without JS, the height of the collapsed details element only allows for one row of
                summary text, and it's not possible to animate closing the details element.
            </p>
        </div>
    </p-accordion>

    <p-accordion name="example" class="warning">
        <summary class="accordion__summary">
            <h3 class="accordion__heading">
                Why do we need a FAQ style module?
            </h3>
            <div class="accordion__iconWrapper">
                <svg class="accordion__icon icon icon--chevron-right" aria-hidden="true" role="presentation">
                    <use href="/main-icons-sprite.svg#chevron-right" />
                </svg>
            </div>
        </summary>
        <div class="accordion__content">
            <p>
                Donec mattis consequat libero at fermentum. Phasellus ultricies ultrices felis eu dapibus. Donec nec pellentesque eros. Proin cursus, felis eu sollicitudin sodales, mi tellus posuere risus, id vehicula enim nisi id risus. Praesent ultrices, eros tempor tincidunt vehicula, sem orci imperdiet ante, iaculis egestas arcu dolor pretium dui. Nullam egestas cursus diam quis ultrices. Praesent mollis ligula vel lorem gravida, at commodo nibh posuere. Maecenas id mollis metus, sit amet ultricies urna. Pellentesque congue elementum massa, a tristique sem feugiat ac.
            </p>
        </div>
    </p-accordion>

    <p-accordion name="example" class="accordion">
        <summary class="accordion__summary">
            <h3 class="accordion__heading">
                Is this considered a frequently asked question?
            </h3>
            <div class="accordion__iconWrapper">
                <svg class="accordion__icon icon icon--chevron-right" aria-hidden="true" role="presentation">
                    <use href="/main-icons-sprite.svg#chevron-right" />
                </svg>
            </div>
        </summary>
        <div class="accordion__content">
            <p>
                Donec mattis consequat libero at fermentum. Phasellus ultricies ultrices felis eu dapibus. Donec nec pellentesque eros. Proin cursus, felis eu sollicitudin sodales, mi tellus posuere risus, id vehicula enim nisi id risus. Praesent ultrices, eros tempor tincidunt vehicula, sem orci imperdiet ante, iaculis egestas arcu dolor pretium dui. Nullam egestas cursus diam quis ultrices. Praesent mollis ligula vel lorem gravida, at commodo nibh posuere. Maecenas id mollis metus, sit amet ultricies urna. Pellentesque congue elementum massa, a tristique sem feugiat ac.
            </p>
        </div>
    </p-accordion>

    <p-accordion name="example" class="accordion">
        <summary class="accordion__summary">
            <h3 class="accordion__heading">
                Can this be used in more general situations?
            </h3>
            <div class="accordion__iconWrapper">
                <svg class="accordion__icon icon icon--chevron-right" aria-hidden="true" role="presentation">
                    <use href="/main-icons-sprite.svg#chevron-right" />
                </svg>
            </div>
        </summary>
        <div class="accordion__content">
            <p>
                Donec mattis consequat libero at fermentum. Phasellus ultricies ultrices felis eu dapibus. Donec nec pellentesque eros. Proin cursus, felis eu sollicitudin sodales, mi tellus posuere risus, id vehicula enim nisi id risus. Praesent ultrices, eros tempor tincidunt vehicula, sem orci imperdiet ante, iaculis egestas arcu dolor pretium dui. Nullam egestas cursus diam quis ultrices. Praesent mollis ligula vel lorem gravida, at commodo nibh posuere. Maecenas id mollis metus, sit amet ultricies urna. Pellentesque congue elementum massa, a tristique sem feugiat ac.
            </p>
        </div>
    </p-accordion>

    <p-accordion name="example" class="accordion">
        <summary class="accordion__summary">
            <h3 class="accordion__heading">
                Why use a different color for interactions vs. navigation?
            </h3>
            <div class="accordion__iconWrapper">
                <svg class="accordion__icon icon icon--chevron-right" aria-hidden="true" role="presentation">
                    <use href="/main-icons-sprite.svg#chevron-right" />
                </svg>
            </div>
        </summary>
        <div class="accordion__content">
            <p>
                Donec mattis consequat libero at fermentum. Phasellus ultricies ultrices felis eu dapibus. Donec nec pellentesque eros. Proin cursus, felis eu sollicitudin sodales, mi tellus posuere risus, id vehicula enim nisi id risus. Praesent ultrices, eros tempor tincidunt vehicula, sem orci imperdiet ante, iaculis egestas arcu dolor pretium dui. Nullam egestas cursus diam quis ultrices. Praesent mollis ligula vel lorem gravida, at commodo nibh posuere. Maecenas id mollis metus, sit amet ultricies urna. Pellentesque congue elementum massa, a tristique sem feugiat ac.
            </p>
        </div>
    </p-accordion>
</div>
index.scss
@use "@imarc/pronto/resources/styles/imported" as pronto;

/**
 * @uses PAccordion
 */
.accordion {
    $b: &;

    --accordion-height-closed: calc(2rem + var(--root-font-size-h3));
    background: white;
    border-radius: .5rem;
    box-shadow: var(--root-box-shadow-low);
    height: var(--accordion-height-closed);
    overflow: hidden;
    transition: height var(--root-duration-moderate) var(--root-ease-out);

    &__summary {
        align-items: center;
        border-radius: .5rem;
        cursor: pointer;
        display: grid;
        grid-auto-flow: column;
        justify-items: start;
        list-style: none;
        outline-offset: -2px;
        padding: .5rem;
        user-select: none;
    }
    
    &__heading {
        margin: 0;
        padding: .25rem 1rem;
    }
    
    &__iconWrapper {
        background: var(--accent-color);
        display: grid;
        justify-self: end;
        padding: .5rem;
    }
    
    &__icon {
        --icon-color: var(--accent-color-contrast);
        aspect-ratio: 1;
        height: var(--root-font-size-h3);
        transition: transform var(--root-duration-moderate) var(--root-ease-out);
    }
    
    &__content {
        padding: 0 1.5rem 1.5rem;
    }
    
    &[open]:not(&.-closing) {
        height: fit-content;
        box-shadow: var(--root-box-shadow-high);
        
        #{$b}__iconWrapper {
            background: var(--color-gray-200);
        }
        
        #{$b}__icon {
            --icon-color: #000;
            transform: rotate(90deg);
        }
    }
}
PAccordion.vue
<script setup>
import { onMounted, useTemplateRef } from 'vue'

/**
 * PAccordion is a vue component for the <details> element. It uses the native <details> element
 * state to determine whether it's open or closed, and merely handles updating CSS properties
 * to allow the element to use transitions between dynamic closed and [open] heights.
 * 
 * @param {number} duration - Duration of the animation in milliseconds.
 *                            Default: computed transition-duration of the details element.
 * @param {string} summary - selector for the summary element.
 *                           Default: 'summary:first-of-type'
 */

const props = defineProps({
  duration: { type: Number },
  summary: { type: String, default: 'summary:first-of-type' }
})
const details = useTemplateRef('details')

onMounted(() => {
  details.value.style.setProperty('--accordion-height-closed', 'auto')
})

const handleToggle = event => {
  const el = event.currentTarget
  const duration = props.duration || parseFloat(getComputedStyle(el).transitionDuration) * 1000 || 0
  const summary = el.querySelector(props.summary)

  if (!summary) {
    return
  }

  const summaryHeight = summary?.clientHeight

  if (!summary.contains(event.target)) {
    return
  }

  event.preventDefault()

  if (el.open) {
    el.classList.add('-closing')
    el.style.setProperty('--accordion-height-closed', `${summaryHeight}px`)

    setTimeout(() => {
      el.open = false
      el.classList.remove('-closing')
      el.style.setProperty('--accordion-height-closed', 'auto')
    }, duration)

  } else {
    el.style.transitionDuration = '0s'
    el.style.setProperty('--accordion-height-closed', `${summaryHeight}px`)

    requestAnimationFrame(() => {
      el.style.transitionDuration = ''
      el.open = true
    })
  }
}
</script>

<template>
  <details ref="details" class="accordion" @click="handleToggle">
    <slot />
  </details>
</template>