Now that weʼve seen how semantic HTML alone produces a
fully-responsive web page, where do we go next? Our plain HTML page is ugly as sin, so weʼre
gonna need some CSS to spiff it up. But today letʼs keep the CSS
simple. Weʼll return to good CSS practices soon in a follow-up
Progressive enhancement starts with something that is already functional and usable. Then it
In this essay weʼll take a simple <details>
element, which will work in any browser, and make it into an accordion.
The <details> element with its <summary> is already an accordion. But it doesnʼt look like one.
This is already a workable accordion component. But most accordions have
more than one expandable element. So letʼs add a
few more elements:
We have also adjusted the CSS a bit to make it work with multiple
We added the .xx-accordion-group properties. And moved
the outer border from the individual elements to the
group. We also changed the border color to match the <summary>
We removed the top border from the content block. Then we replaced it
with a bottom border on the <summary>
element. We also changed the color to the background color of the <details> elements. This makes it appear as a horizontal rule between the
We donʼt want that bottom border on the last element in the accordion group. So we used .xx-accordion:last-child summary to remove that one bottom border.
Rats! The accordion elements open and close instantly when you click on
the summary. It would be nice if we animated the open and close.
We bet that your first thought will be that we can do this with CSS
animations. We thought so, too. But it turns out that it is fiendishly
difficult to make that work. By that we mean that we couldnʼt.
CSS should always be your first choice for effects, but
CSS transitions for the loss
Naturally, the first thing we tried was a CSS transition on the height of the content block. But this only works if you know
the height of the block in advance. It wonʼt work
(for us, anyway) with height set to auto or fit-content.
OK, fine! Instead we set the open height to a CSS property with a
fallback to auto:
var(--xx-accordion-height, auto). That oughta do it.
Now we need to set that property for each accordion element
The first thing we want to do is write a function that
grabs any and all accordion elements on the page. Then weʼll add an
to each of them. And we want our function to run once as soon as the DOM has finished loading.
And our modified CSS:
And it does work. Beautifully … but intermittently. Try it. And then it stops working at all. And it never animates the close. Why
Because when the <details> element toggles closed,
it sets its “open” value to
false. And thatʼs that.
OK, fine. Time to show it whoʼs boss.
Kicking butt and taking names
First, letʼs delete the CSS we added. Forget using
a transition! Weʼll do this the old-fashioned way.
First issue: when the <details> element toggles shut,
it closes the accordion abruptly. We need to prevent that. To do so, we
will seize control.
Instead of handling the toggle event, letʼs capture
the click on the <summary>
element. Then we will prevent the click from bubbling up.
We still need to know the height of the open accordion, but we donʼt need it in our CSS. So letʼs use a property
instead. Weʼll call it xxOpenHeight. (Do remember to
replace xx with your own namespace.)
Check out the enhanced example and open the browser console. Note how clicking on the a summary
prints the height of that accordionʼs content block to the console.
Weʼre off to a good start.
Now letʼs complete the toggleAccordion function to
animate the accordion elements. Weʼll explain this
code line by line below.
We need to prevent the element from toggling. We can
do this with event.preventDefault() on line #2 above. Now
we are responsible for opening and closing the accordion
Next, we use the event.target to get the <details> (accordion) element, our content block, and the xxOpenHeight. Lines #4 to #7.
Now we have two possibilities. Either the accordion is open and we want to close it, or vice versa. So we check if the accordion
is open on line #9.
We need the maxHeight property of the content blockʼs style attribute. We will use it to control the height
of the block. So if the accordion is open, then we must make sure
that maxHeight it is set to xxOpenHeight.
See line #10.
Now we will use setTimeout recursively to reduce the maxHeightbit by bit. Weʼll call it every few milliseconds
until we have shut the accordion. We create our shutAccordion function on lines #12 to #26.
Hmm … maxHeight is a string. Urk. So we will convert it to a number with parseInt to decrement it. Then weʼll recreate
the string. We get the integer on line #13. If our maxHeight is still greater than zero, we subtract 25 from it on lines #16 to
#18. Then we call our shutAccordion function recursively.
We set the timeout to 10 milliseconds. Thatʼs a nice frame rate.
When the maxHeight reaches zero, we set accordion.open to false (line #25) and weʼre good to go.
Now that weʼve defined our shutAccordion recursive
function, we need to call it to start the recursion
(animation). We do so on line #28.
But what if the accordion is closed? Then we set the maxHeight
to zero on line #33 to be sure. This also covers
the initial case.
We define our openAccordion recursive function on lines
#35 to #45.
Again, we begin by parsing the height from maxHeight on
line #36. And we immediately set the accordion to “open” on line #38. This has no visual effect yet because the height of
the content block is currently zero.
Now, on lines #40 to #44, if the maxHeight is still
less than the measured openHeight, we add 15 pixels to
the maxHeight. Then we call the function again using setTimeout
to delay for 10 milliseconds. When the content block is fully open,
this conditional will fail and the recursion will stop.
And, as with the shutAccordion function above, we have
to call the function to start the animation. We do
this on line #47.
Not that difficult!
Putting it all together
Try the final example above. You might note an issue with the content of
the content block becoming visible instantly. This
happens even though the height is zero.
That is because we havenʼt hidden the overflow. We can do that by
adding overflow-y: hidden
to the .sb-accordion-content CSS.
Once we do that, then our accordion should animate flawlessly. Now
still works. It just doesnʼt animate. But we can live with that,
element. But that just means that the accordion is always open.
Again, we can live with that.
Thatʼs progressive enhancement for you. Our code is simple and
responsive and works for everyone. And if you have CSS
and JS enabled, then you get a nicer experience.
Links to related pages
Get notified form
Carbon emissions for this page
Cleaner than 99% of pages tested
on first visit; then on return visits
Scan this code to open this page on another device.