Building an Accessible Category Accordion Without Third-Party Libraries
Learn how to create an accessible, animated category accordion using HTML5 details/summary and CSS transitions, perfect for organizing blog posts or any content.
When building my blog, I wanted a clean way to organize posts by categories. While there are many third-party accordion libraries available, I decided to build one from scratch using native HTML5 elements. This approach ensures better performance, accessibility, and maintainability.
The Power of HTML5 details/summary
The <details>
and <summary>
elements provide native accordion functionality with built-in accessibility features:
<details> <summary>Category Name</summary> <div>Content goes here...</div></details>
These elements give us:
- Native keyboard navigation
- Built-in ARIA attributes
- Screen reader support
- Default open/close functionality
Building Our Category Accordion
Here’s how we built our category accordion component:
---import type { CollectionEntry } from "astro:content";import PostList from "./PostList.astro";
type Props = { category: string; posts: CollectionEntry<"posts">[]; isOpen?: boolean;};
const { category, posts, isOpen = false } = Astro.props;
// Filter posts for this categoryconst categoryPosts = posts.filter((post) => post.data.categories.includes(category));---
<div class="border-b border-teal-900/20 dark:border-zinc-700"> <details class="group" open={isOpen}> <summary class="flex cursor-pointer list-none items-center justify-between py-4 text-xl font-medium text-zinc-900 dark:text-zinc-100" > <div class="flex items-center gap-x-3"> <span class="inline-flex h-8 w-8 items-center justify-center rounded-full bg-teal-100 text-sm font-medium text-teal-900 dark:bg-zinc-800 dark:text-zinc-200" > {categoryPosts.length} </span> <span>{category}</span> </div> <svg class="h-5 w-5 rotate-0 transform text-zinc-500 transition duration-300 ease-in-out group-open:rotate-180 dark:text-zinc-400" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" > <path stroke-linecap="round" stroke-linejoin="round" d="M19.5 8.25l-7.5 7.5-7.5-7.5" ></path> </svg> </summary> <div class="pb-6"> <PostList posts={categoryPosts} /> </div> </details></div>
<style> details summary::-webkit-details-marker { display: none; }</style>
Let’s break down the key features:
1. Accessibility First
The <details>
and <summary>
elements provide native accessibility:
- Keyboard navigation with Enter/Space
- Proper ARIA roles and states
- Screen reader announcements for open/close states
2. Visual Enhancements
We’ve added several visual improvements:
/* Remove default triangle marker */details summary::-webkit-details-marker { display: none;}
/* Custom arrow icon with smooth rotation */.group-open:rotate-180 { transform: rotate(180deg);}
.transition { transition-property: transform; transition-duration: 300ms; transition-timing-function: ease-in-out;}
3. Post Count Badge
A circular badge shows the number of posts in each category:
<span class="inline-flex h-8 w-8 items-center justify-center rounded-full bg-teal-100"> {categoryPosts.length}</span>
4. Dark Mode Support
We’ve included dark mode styles using Tailwind’s dark: modifier:
<div class="text-zinc-900 dark:text-zinc-100"> <!-- Content --></div>
Using the Accordion
To use the accordion, simply pass the required props:
<CategoryAccordion category="WebDevelopment" posts={allPosts} isOpen={true}/>
Benefits Over Third-Party Libraries
- Performance: No additional JavaScript bundle
- Accessibility: Native HTML5 elements provide better screen reader support
- Maintainability: No dependencies to update or manage
- Customization: Full control over styling and behavior
- SEO: Semantic HTML structure
Browser Support
The <details>
and <summary>
elements are supported in all modern browsers:
- Chrome 12+
- Firefox 49+
- Safari 6+
- Edge 79+
Conclusion
Building an accessible accordion doesn’t require complex libraries. By leveraging HTML5’s native elements and adding some thoughtful styling, we can create a robust, accessible component that’s perfect for organizing blog content.