Smooth Accordion
An accordion component with physics height transition, rotating chevron indicators, and accessible keyboard toggles.
Live preview from the library's source
Install
From EasyUI
npx shadcn@latest add Surajmaurya1/easyui/smooth-accordionFrom this collection
npx shadcn@latest add https://allcomps.vercel.app/r/easyui--smooth-accordion.jsonCode
'use client';
import React, { useState } from 'react';
import { motion, AnimatePresence } from 'framer-motion';
import { ChevronDown } from 'lucide-react';
import { cn } from '../../lib/utils';
import { motionTransitions } from '../../lib/motion-tokens';
export interface AccordionItem {
id: string;
title: string;
subtitle?: string;
content: React.ReactNode;
}
export interface SmoothAccordionProps {
items: AccordionItem[];
allowMultiple?: boolean;
defaultOpen?: string[];
className?: string;
}
export const SmoothAccordion: React.FC<SmoothAccordionProps> = ({
items,
allowMultiple = false,
defaultOpen = [],
className,
}) => {
const [openIds, setOpenIds] = useState<string[]>(defaultOpen);
const toggle = (id: string) => {
if (allowMultiple) {
setOpenIds((prev) =>
prev.includes(id) ? prev.filter((item) => item !== id) : [...prev, id]
);
} else {
setOpenIds((prev) => (prev.includes(id) ? [] : [id]));
}
};
return (
<div className={cn('flex flex-col divide-y divide-[#1F1F1F] rounded-xl border border-[#1F1F1F] bg-[#0E0E0E] overflow-hidden', className)}>
{items.map((item) => {
const isOpen = openIds.includes(item.id);
return (
<div key={item.id} className="transition-colors">
<button
onClick={() => toggle(item.id)}
className="flex w-full items-center justify-between p-4 text-left font-medium text-[#FAFAFA] hover:bg-[#141414] transition-colors focus-ring cursor-pointer"
aria-expanded={isOpen}
>
<div>
<div className="text-sm font-medium text-[#FAFAFA]">{item.title}</div>
{item.subtitle && (
<div className="text-xs text-[#6B6B6B] mt-0.5">{item.subtitle}</div>
)}
</div>
<motion.div
animate={{ rotate: isOpen ? 180 : 0 }}
transition={motionTransitions.springSnappy}
className="text-[#525252] ml-2 shrink-0"
>
<ChevronDown className="w-4 h-4" />
</motion.div>
</button>
<AnimatePresence initial={false}>
{isOpen && (
<motion.div
key="content"
initial={{ height: 0, opacity: 0 }}
animate={{ height: 'auto', opacity: 1 }}
exit={{ height: 0, opacity: 0 }}
transition={motionTransitions.springGentle}
className="overflow-hidden"
>
<div className="px-4 pb-4 pt-1 text-xs sm:text-sm text-[#A1A1A1] leading-relaxed">
{item.content}
</div>
</motion.div>
)}
</AnimatePresence>
</div>
);
})}
</div>
);
};
Other accordions
The same kind of component from the other libraries in the collection.