Building an Accessible Category Accordion Without Third-Party Libraries
Build an accessible, animated category accordion with native HTML5 details/summary, CSS transitions, and no JavaScript library.
This post contains affiliate links for tools I use in production. If you buy through them I earn a commission at no extra cost to you. Recommendations are based on my own experience.
Organizing blog posts by category does not need a third-party accordion library. The native <details> and <summary> elements handle disclosure behavior, keyboard support, and screen reader semantics for free, which means less JavaScript to ship and less surface area to maintain.
The Power of HTML5 details/summary
<details> and <summary> provide native accordion functionality with built-in accessibility:
<details> <summary>Category Name</summary> <div>Content goes here...</div></details>These elements give you:
- Native keyboard navigation (Enter and Space toggle the summary)
- Built-in ARIA state, exposed automatically as the
openattribute changes - Screen reader support with no extra
aria-*wiring - Default open and closed states via the
openattribute
Building the Category Accordion
Here is the component, written as an Astro island:
---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>Debugging this in production
The Tailwind group-open: variant depends on the parent details element carrying the group class and being an actual ancestor of the element you are styling; if a build step or a CMS-injected wrapper breaks that DOM structure, the chevron silently stops rotating with no console error. A session replay and error tracker such as LogRocket or Sentry shows the rendered DOM and CSS state at the moment a user reports the accordion as broken, and both have a free tier that covers a side project.
1. Accessibility first
<details> and <summary> provide native accessibility for free:
- Keyboard navigation with Enter and Space
- Correct ARIA roles and states, generated by the browser
- Screen reader announcements when the section opens or closes
2. Visual enhancements
A few CSS rules replace the default marker with a rotating chevron:
/* 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;}Note the escaped colon in .group-open\:rotate-180: Tailwind’s group-open: variant compiles to a class name with a literal colon, which needs escaping in raw CSS. When you write it as a Tailwind utility class in markup (group-open:rotate-180), Tailwind handles the escaping for you.
3. Post count badge
A circular badge shows how many posts sit 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
Tailwind’s dark: modifier covers the color scheme:
<div class="text-zinc-900 dark:text-zinc-100"> <!-- Content --></div>Using the Accordion
<CategoryAccordion category="WebDevelopment" posts={allPosts} isOpen={true}/>Benefits Over Third-Party Libraries
- Performance: zero additional JavaScript bundle.
- Accessibility: native elements come with screen reader support already correct.
- Maintainability: nothing to patch when a library ships a breaking change.
- Customization: full control over markup and styling.
- SEO: semantic HTML that crawlers parse without extra hints.
Browser Support
<details> and <summary> are supported in every browser still in active use: Chrome, Firefox, Safari, and Edge have all shipped support for over a decade. The ::-webkit-details-marker pseudo-element used to hide the default triangle is WebKit-specific; Firefox and Chromium-based browsers other than the original marker rendering may need a list-style: none fallback if you see a residual marker.
Conclusion
An accessible accordion does not require a dependency. Native HTML5 elements plus a small amount of CSS produce a component that is faster, more maintainable, and no less accessible than a library-based one.
If you are taking this into production, the next steps below cover auth, data and observability, and the newsletter is where the Astro SaaS boilerplate ships first.
Next steps: scaling to production
If you take this into production, these are the pieces I would add first.
- Clerk Clerk provides drop-in authentication and user management components. Hosted auth saves the login, session and org code you would otherwise maintain.
- Supabase Supabase is a hosted Postgres platform with authentication and storage built in. Postgres with row-level security, so the data layer is ready for multi-tenant apps.
- Sentry Sentry captures errors and performance traces from production applications. Errors and slow transactions from real users, with source maps, before customers report them.
Production-ready Astro + TanStack architecture
Get the architecture cheat sheet and join the waitlist for the Astro SaaS boilerplate.