Get Even More Visitors To Your Blog, Upgrade To A Business Listing >>

Accessible* ANIMATED Accordion: in pure CSS??? No way! 😱

Posted on Oct 1 A few days ago I designed an Accordion in 1 minute, 5 minutes and 10 minutes that was far more accessible than most! (perfect? not quite, but certainly usable by assistive tech users and keyboard only users).But the thing that annoyed me was that I HAD to use JavaScript in order to make the animation work.And we all know how much people love CSS only things (don't get me started on people who build their sites using React or Vue and then want CSS only stuff...)Preword / note: Now, there is an important thing to say here. This pattern in this article is accessible...but not ideal.So if you must, for some strange reason, have an animated and CSS only accordion...then use this pattern. Otherwise, there are much better accordion patterns that expose additional information to assistive tech users (and I will be writing about a "perfect" accordion soon!).So this post is just to explore a pattern you may not have thought of or come across, that also highlights some interesting CSS techniques along the way!Caveat over, let's see what we have built.Try it with your keyboard! Tab between sections, use up and down arrow keys for scrolling the scrollable sections (or on mobile just drag!) and you can even open all the sections with the checkbox!Above all, have a poke around the CSS and HTML and see what you notice! It may looks simple at first, but there is a lot going on here!Here are the most interesting parts:So, let's look at each of those parts!This attribute is an interesting one, in fact, it is the whole reason this accordion works at all!It allows us to select something with CSS as long as it has the same ID as the hash at the end of the URL.OK, so that might not make sense immediately, so let me explain.Let's say we have a div with id="blue". We can create a CSS selector as follows:So when we load our page to it's default URL (e.g. example-site.com) that div will be red.But, if we update our URL to be example-site.com*#blue* then that div will turn blue!And there is a simple way to update the URL, we can use an anchor tag and set the href equal to that ID ().I have put that all together in a simple CodePen. Note that the JS is just to show the URL of the CodePen as you cannot see it. Pay attention to the end of the URL as you click on the links.We can take advantage of this to make our accordion exclusive!By assigning an ID to each of the accordion "sections", and then using an anchor () tag with that href, we now have a way of selecting just one section in CSS and not the others.Then, if we click on a link that belongs to another section, the selector will target that section instead.The relevant code from the demo (simplified) is as follows:And putting that all together you should now hopefully understand the :target property behaviour!There is another bonus to using fragment identifiers (the name for hashes at the end of URLs) that we update with an anchor element...it allows us to move the focus on the page!When we add #acc1 to the URL the page jumps to the section with the same ID. This comes in useful in this instance as we may have a scrollable area that we want people to be able to scroll up and down with the arrow keys. Moving focus there means that when the accordion opens, they can use the up and down arrow keys right away without having to Tab to the scrollable section. A nice quick UX win!If you have ever tried to animate the height of an item that isn't a fixed height...you will have experienced frustration and pain."Can't I just animate the height"...nope, that won't work?"What about the max-height animation?" - kind of works, but animation times will vary depending on the height of each item and you end up with strange delays (as the whole max-height is animated, even if you only use a small part of it).No, this used to be a problem that was only solvable properly with fixed heights or JavaScript.But, grid is a thing now...and grid can finally give us what we need!So what is the magic sauce for animated opening? First of all: grid-template-rows: min-content 0frWait...what does that mean?And that last part is why we need visibility: hidden on whatever item we want to expand / collapse. That hides the item visually, but it still takes up space in the DOM. So, as a last step, we have to do an overflow: hidden on it to make sure it collapses and margin: 0 and padding: 0 to make sure that the margins and padding do not take up space and it all works!Now, before I show you that it works, we need to do one last thing, we need to animate the expansion of the element!So the last thing we need to do is reverse the hiding and make the revealing item take up it's full height, then we can animate it!So obviously, visibility: hidden needs to become visibility: visible so we can show the revealing item.And then we need to change the parent so that we have: grid-template-rows: min-content 1frWith the change being 1fr (1 fraction) instead of 0fr. In this case that means "take up the remaining space". As our grid has no set height, this will become the total height of the element we want to reveal. Now, we finally have the parts to animate it!You see, even with the 0fr the height of the element was already calculated (which is different to that of max-height), so the browser knows what height to animate to...and this is what all of this jumping around with grids and fractions and template rows is for!We can now animate grid-template-rows!So to round us off, we need to apply a transition CSS property. In this example of 1 second.So by combining the visibility-hidden, template-rows and transition we have a working opening section that will work at any height!Check it out!There is one nuance here you may have noticed, :has.This is like saying "if the item inside :has appears inside this element, then select this item"We need this so we can change the grid-template-rows on the parent easily..grid-rows:has(p:target) effectively says "select the element with the class grid-rows if there is a

element inside that matches the :target selector (that we discussed earlier in this article).".This way we can toggle our grid-template-rows property when needed.phew, that was a lot, let's look at some hacks now to make it more fun!As this is a hack, I won't go into as much detail, but I had a problem.In the final demo, I restricted the height of the content section (so that the next accordion section is visible on screen, even on long content sections, for better UX).Now the problem is that I am animating the height. So as the section expands, there are parts that will overflow and trigger Scroll Bars. This would be fine, except that some sections in the demo accordion do not require scroll bars (as they are shorter than the max height I set).This results in some horrible UI / UX where the scroll bars momentarily appear while the section expands and then disappear afterwards, as can be seen in the following GIF.Ewwwww...hate it!Now, scroll bars are strange. If we start them off invisible (overflow-y: hidden) and then animate them to visible (overflow-y: auto), they disappear again once the animation is over.So, I hacked it!I set a very long animation duration and made them visible very early in the animation.Here we animate over 1000 seconds, but we change to the final state at 0.2% of the animation (2 seconds). So after 16min 40 seconds, this scroll bar will disappear, but I think that is acceptable here (as acceptable as possible with a hack at least lol).I am sure there is a way to achieve this, but sometimes you just have to ship to production with something that is 95% good enough!Now we get scroll bars that are hidden initially, and then appear once the expanding animation has completed.One final note: if someone did sit with the page open for 17 minutes and the scroll bars disappeared, they would reappear when they expand the section again and the section would still be scrollable. This is why I say this solution is "good enough" as that will be a rare occurrence and it self fixes on interaction!In my previous article about accordions I covered prefers-reduced-motion as well.It means that for people who need reduced motion on a page (people who may become dizzy or ill through movement due to vestibular disorders for example) have a way of indicating that they would like less animation to us.In the previous article I was using JavaScript to check for this media query. But as prefers-reduced-motion is designed for CSS primarily, it is waaaaay easier in CSS.So what we want to do is this:Now here is the "trick" to make this easy.set your animation durations as CSS properties.That way, we only need to toggle the animation duration in one place and we can update it in multiple places.Like this:By setting the animation to "0s" that effectively switches the animation off!By using calc you can actually create a toggle for animations across a whole site from a single CSS variable.I am not going to explain this fully as it is outside of the scope of this article, but the following CSS may help you understand how it works!Hopefully that little tip can give you some interesting ideas on how to allow user settings etc.Another media query tip / trick...and one that works well with this pattern for an accordion over the

and one I shared in my previous article.The @media print media query will apply styles only when someone prints the page (this includes "print to PDF" in case you are thinking nobody will ever print your page lol!)All we need to do is apply the "open state" CSS to every single .content section in our demoRead more about [media queries and the print media query on MDN (second item in "description" section)[https://developer.mozilla.org/en-US/docs/Web/CSS/@media#description]One last "win" for this pattern over
and is that we can open and close all sections using just a checkbox.We apply the exact same principles as those of out @media print CSS query, but this time attach it to the :checked state on a checkbox.And one last thing, this time we do not change the max-height as we don't want to remove the scrolling sections this time!So we have a checkbox input () with an ID oftota11y-open-close`.We position it as a sibling (on the same level in the DOM) as the
    that contains each of our accordion sections.This way we can use the tilde ~ operator to do some matching based on the :checked state (which is like a boolean, checked = true, not checked = false).So we check the state first, then if it is checked, we select the sibling ul, and any li within it to toggle our grid-template-rows (the li are the parent items we discussed earlier in each accordion section).We also need to update the content section (the child section discussed earlier). But this time notice we use the > operator?That is so that we only select matches that are directly related to each other in the DOM order (any li must be a direct child of the ul and the .content section must be a direct child of the li). This is just so that we don't "pollute" the .content section (as if we added a
    within our original content section that would also get selected otherwise. It is an edge case, but it just makes it more robust.I just mentioned "polluting" other sections. What I mean is that many people apply styles in CSS that are not specific enough. Due to how CSS "Cascades" (the "C" part of "CSS"), this can sometimes result in styles "escaping" and affecting elements they should not.To avoid this (so you can use this accordion in your own projects) I have scoped all of the selectors with .tota11y-accordion.That means the styles cannot escape the surrounding div with that class.That is a useful tip to remember, if you create a component, have a class on the outer most element that you include in all selectors. This will keep the styles scoped within that component and will make your CSS easier to maintain (even if it is a little more verbose!).Oh and just have a quick look at the aria attributes I used in the demo again and have a read around those, it never hurts to level up your accessibility knowledge! 💪🏼💗There was a lot in that and I hope you learned some new tips and tricks for CSS.As I said at the beginning, do not use this pattern in production unless you absolutely MUST avoid JavaScript. But also keep the pattern and techniques in mind as they may just save your bacon one day!If you enjoyed this article, consider leaving a 💗 (or more emojis if you are feeling generous!) as it really helps!And if you learned something new, then let me know in the comments, that would be amazing!See you in the next one all of you beautiful people (and fellow monsters)! 💗Templates let you quickly answer FAQs or store snippets for re-use.New dev? Experienced Dev? If you learned something in this article, please do let me know, I always love hearing when people stumble across something new, it makes writing the articles worth it! 💪🏼💗 Are you sure you want to hide this comment? It will become hidden in your post, but will still be visible via the comment's permalink. Hide child comments as well Confirm For further actions, you may consider blocking this person and/or reporting abuse потик - Oct 1 Akinnimi Stefan Emmanuel - Sep 30 commdao - Sep 29 raman04-byte - Sep 29 Once suspended, grahamthedev will not be able to comment or publish posts until their suspension is removed. Once unsuspended, grahamthedev will be able to comment and publish posts again. Once unpublished, all posts by grahamthedev will become hidden and only accessible to themselves. If grahamthedev is not suspended, they can still re-publish their posts from their dashboard. Note: Once unpublished, this post will become invisible to the public and only accessible to GrahamTheDev. They can still re-publish the post if they are not suspended. Thanks for keeping DEV Community safe. Here is what you can do to flag grahamthedev: grahamthedev consistently posts content that violates DEV Community's code of conduct because it is harassing, offensive or spammy. Unflagging grahamthedev will restore default visibility to their posts. DEV Community — A constructive and inclusive social network for software developers. With you every step of your journey. Built on Forem — the open source software that powers DEV and other inclusive communities.Made with love and Ruby on Rails. DEV Community © 2016 - 2023. We're a place where coders share, stay up-to-date and grow their careers.



This post first appeared on VedVyas Articles, please read the originial post: here

Share the post

Accessible* ANIMATED Accordion: in pure CSS??? No way! 😱

×

Subscribe to Vedvyas Articles

Get updates delivered right to your inbox!

Thank you for your subscription

×