<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%);
}
This Pen doesn't use any external CSS resources.
This Pen doesn't use any external JavaScript resources.