Building an Accessible Category Accordion Without Third-Party Libraries

| December 28, 2024

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 category
const 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

  1. Performance: No additional JavaScript bundle
  2. Accessibility: Native HTML5 elements provide better screen reader support
  3. Maintainability: No dependencies to update or manage
  4. Customization: Full control over styling and behavior
  5. 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.