<main>
  <h1>CSS-Only Animated Accordion</h1>
  <section class="accordion" id="overview">
    <h1 class="title"><a href="#overview">Overview</a></h1>
    <div class="content">
      <div class="wrapper">
        <p>
          This CodePen demonstrates how we can animate the opening and closing
          states of an accordion with fancy reveal animation using only CSS. This
          concept is suitable for creating <strong>FAQ sections</strong>,
          <strong>Table of Contents</strong>, and more.
        </p>
      </div>
    </div>
  </section>

  <section class="accordion" id="how-does-it-work">
    <h1 class="title"><a href="#how-does-it-work">How does it work?</a></h1>
    <div class="content">
      <div class="wrapper">
        <p>
          We cannot transition height or any CSS property from zero to auto. If we
          want to smoothly expand a collapsed accordion, we could set a
          <code>max-height</code> and transition the <code>height</code> property
          to a large value when we expand the section. However,
          <a href="https://css-tricks.com/using-css-transitions-auto-dimensions/#aa-there-are-two-crucial-downsides-to-this" target="_blank">it may not properly work</a>
          if the content is larger than the <code>max-height</code>.
        </p>
        <p>
          So, here we create a CSS grid with a grid item. Then we transition the
          <code>grid-template-rows</code> property from <code>0fr</code> to
          <code>1fr</code> and the grid item transitions to its content height.
        </p>
        <p>
          I used CSS <code>clip-path</code> and <code>mix-blend-mode</code> to
          animate the background and text color of the accordion. Each accordion
          has two pseudo-elements. We know that <code>opacity</code>,
          <code>clip-path</code> and, <code>visibility</code> are animatable CSS
          properties. So when the accordion is selected using the
          <code>:target</code> pseudo-class, we transition the clip-path circle
          radius to 200% of the
          <code>::before</code>
          pseudo-element. When the accordion is not selected, we do the same with
          the
          <code>::after</code> but this time with a delay. This creates an
          illusion as it appears to clip from inside.
        </p>
        <p>
          Since the accordion's title is an anchor element, users can also
          navigate through the items with their keyboard.
        </p>
      </div>
    </div>
  </section>

  <section class="accordion" id="inspiration">
    <h1 class="title"><a href="#inspiration">Inspiration</a></h1>
    <div class="content">
      <div class="wrapper">
        <p>
          In a recent video, Kevin showed us a way to animate height from zero to
          auto value. Seeing that, I couldn't resist experimenting with the idea.
          Then I came up with this CSS-only accordion component and added the
          reveal animation too!
        </p>
        <p>
          I would highly recommend watching
          <a href="https://youtu.be/B_n4YONte5A" target="_blank">his video</a>,
          where he beautifully explains the technique and points out how he
          discovered it. Also, read
          <a href="https://keithjgrant.com/posts/2023/04/transitioning-to-height-auto/" target="_blank">Keith J. Grant's post</a>
          and
          <a href="https://nemzes.net/posts/animating-height-auto/" target="_blank">Nelson Menezes's post</a>
          on the same topic.
        </p>
      </div>
    </div>
  </section>
</main>
:root {
  --size-header: 2.25rem;
  --size-accordion-title: 1.25rem;
  --size-accordion-content: 1rem;
  --animation-speed: 100;
  --slide-ease: cubic-bezier(0.86, 0, 0.07, 1);
  --slide-duration: calc(400ms * 100 / var(--animation-speed));
  --slide-delay: calc(450ms * 100 / var(--animation-speed));
  --circle-duration: calc(900ms * 100 / var(--animation-speed));
}

*,
*::before,
*::after {
  position: relative;
  left: 0;
  top: 0;
  box-sizing: border-box;
}

a,
p,
h1 {
  margin: 0;
}

html {
  height: 100%;
}

body {
  background-color: hsl(4, 91%, 60%);
  font-family: Nunito, Arial, Helvetica, sans-serif;
  font-weight: 600;
  margin: 0;
  display: grid;
  place-items: center;
  padding: 2rem 2rem;
  min-height: 100%;
}

html,
body {
  scroll-behavior: smooth;
  scroll-padding-top: 1rem;
}

main > h1 {
  font-size: var(--size-header);
  margin-bottom: 1.25rem;
  color: #fff;
}

::selection {
  background-color: rgba(0, 0, 0, 0.4);
}

.accordion {
  --circle-x: 1.8rem;
  --circle-y: 0;
  --circle-r: 200%;
  --circle-bg: #fff;
  color: #000;

  background-color: var(--circle-bg);
  max-width: 56ch;
  margin-bottom: 1rem;
  border-radius: min(8px, 0.5rem);

  display: grid;
  grid-template-rows: 0fr 0fr;
  transition-timing-function: var(--slide-ease);
  transition-duration: 200ms, 200ms, var(--slide-duration);
  transition-property: opacity, box-shadow, grid-template-rows;
  transition-delay: 0ms, 0ms, var(--slide-delay);
  box-shadow: 0 0 0 2px rgba(0, 0, 0, 0.1);
  opacity: 0.9;
}

.accordion:not(:target):hover {
  box-shadow: 0 0 0 2px rgba(0, 0, 0, 0.5);
}

.accordion:not(:target):active {
  opacity: 1;
  box-shadow: 0 4px 7px 0 rgba(0, 0, 0, 0.3);
}

.accordion,
.content {
  overflow: hidden;
}

.accordion:target {
  --d: 90deg;
  grid-template-rows: 0fr 1fr;
  transition: grid-template-rows var(--slide-ease) var(--slide-duration)
    var(--slide-delay);
}

.wrapper {
  padding-block: 0 1.05rem;
  padding-inline: 1.25rem;
}

.content {
  font-size: var(--size-accordion-content);
  line-height: 140%;
}

.content p {
  margin-bottom: 1rem;
}

.content a {
  color: currentColor;
  font-weight: 800;
  text-decoration: underline;
}

main :last-child,
.content :last-child {
  margin-bottom: 0;
}

.title a {
  padding: 1rem 1.25rem;
  font-size: var(--size-accordion-title);
  font-weight: 800;
  color: currentColor;
  text-decoration: none;
  display: flex;
  flex-direction: row;
  place-items: center;
}

.title a::before {
  --chevron-icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 320 512'%3E%3C!--! Font Awesome Pro 6.4.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2023 Fonticons, Inc. --%3E%3Cpath fill='white' d='M310.6 233.4c12.5 12.5 12.5 32.8 0 45.3l-192 192c-12.5 12.5-32.8 12.5-45.3 0s-12.5-32.8 0-45.3L242.7 256 73.4 86.6c-12.5-12.5-12.5-32.8 0-45.3s32.8-12.5 45.3 0l192 192z'/%3E%3C/svg%3E");
  content: "";
  left: 0;
  top: 0;
  width: 0.65rem;
  aspect-ratio: 320 / 512;
  display: inline-block;
  margin-right: 0.75rem;
  transform: rotate(var(--d, 0deg));
  transition: transform var(--slide-ease) var(--slide-duration)
    var(--slide-delay);
  mask-image: var(--chevron-icon);
  mask-size: 100% 100%;
  -webkit-mask-image: var(--chevron-icon);
  -webkit-mask-size: 100% 100%;
  background-color: currentColor;
}

.accordion::before,
.accordion::after {
  content: "";
  position: absolute;
  width: 100%;
  height: 100%;
  background-color: var(--circle-bg);
  mix-blend-mode: difference;
  transform-style: preserve-3d;
  transition-timing-function: ease;
  transition-property: opacity, clip-path, visibility;
  pointer-events: none;
  clip-path: circle(var(--r) at var(--circle-x) var(--circle-y));
  border-radius: inherit;
  z-index: 4;
}

.accordion::before {
  --r: 0%;
  transition-delay: var(--circle-duration), var(--circle-duration), 0ms;
  transition-duration: 0ms, var(--circle-duration), 0ms;
  opacity: 0;
}

.accordion:target::before {
  --r: var(--circle-r);
  transition-delay: 0ms, 0ms, 0ms;
  transition-duration: 0ms, var(--circle-duration), 0ms;
  opacity: 1;
}

.accordion::after {
  --r: var(--circle-r);
  transition-delay: 0ms, 0ms, var(--circle-duration);
  transition-duration: 0ms, var(--circle-duration), 0ms;
  visibility: hidden;
  opacity: 1;
}

.accordion:target:after {
  --r: 0%;
  transition-delay: 0ms, 0ms, 0ms;
  transition-duration: 0ms, 0ms, 0ms;
  visibility: visible;
  opacity: 0;
}

.title a:focus-visible {
  background-color: hsl(0, 100%, 90%);
  outline: none;
}

.accordion:target .title a:focus-visible {
  background-color: hsl(183, 100%, 93%);
}

External CSS

This Pen doesn't use any external CSS resources.

External JavaScript

This Pen doesn't use any external JavaScript resources.