FluxUI Pro is live - modern UI, powerful animations, zero hassle.
Components
Accordion

Accordion

Ultra-modern accordion component with smooth animations, multiple variants, and customizable collapsible content sections.

Accordion Variants

Default Variant

What is FluxUI?

โ“

How do I get started?

๐Ÿš€

Bordered Variant

What is FluxUI?

โ“

How do I get started?

๐Ÿš€

Filled Variant

What is FluxUI?

โ“

How do I get started?

๐Ÿš€

Minimal Variant

What is FluxUI?

โ“

How do I get started?

๐Ÿš€

Accordion Sizes

Small Size

What is FluxUI?

โ“

How do I get started?

๐Ÿš€

Medium Size (Default)

What is FluxUI?

โ“

How do I get started?

๐Ÿš€

Large Size

What is FluxUI?

โ“

How do I get started?

๐Ÿš€

Multiple Selection Mode

What is FluxUI?

โ“

How do I get started?

๐Ÿš€

Is it free to use?

๐Ÿ’ฐ

Does it support dark mode?

๐ŸŒ™

Accordion with Icons

Lightning Fast Performance

Fully Customizable

TypeScript Support

๐Ÿ”ท

Accessibility First

โ™ฟ

Complex Content (Settings Panel)

Account Settings

Notification Preferences

Privacy & Security

FAQ Section

Frequently Asked Questions

Find answers to common questions about FluxUI

โ“

What is FluxUI?

๐Ÿš€

How do I get started?

๐Ÿ’ฐ

Is it free to use?

๐ŸŒ™

Does it support dark mode?

Product Features Showcase

Why Choose FluxUI?

Lightning Fast Performance

Fully Customizable

TypeScript Support

๐Ÿ”ท

Accessibility First

โ™ฟ

Getting Started Guide

Installation

๐Ÿ“ฆ

Basic Setup

โš™๏ธ

Customization

๐ŸŽจ

Keyboard Navigation

Try these keyboard interactions:

  • Tab: Move focus between accordion headers
  • Enter/Space: Expand or collapse the focused item
  • Arrow Up/Down: Navigate between accordion items
  • Home/End: Jump to first/last item

What is FluxUI?

โ“

How do I get started?

๐Ÿš€

Is it free to use?

๐Ÿ’ฐ

Custom Animation Duration

Fast Animation (200ms)

What is FluxUI?

โ“

How do I get started?

๐Ÿš€

Slow Animation (600ms)

What is FluxUI?

โ“

How do I get started?

๐Ÿš€

Installation

1

Install the packages

npm i motion clsx tailwind-merge lucide-react
2

Add util file

lib/util.ts
import { ClassValue, clsx } from "clsx"; import { twMerge } from "tailwind-merge"; export function cn(...inputs: ClassValue[]) { return twMerge(clsx(inputs)); }
3

Copy and paste the following code into your project

accordion.tsx
"use client"; import { motion, AnimatePresence } from "motion/react"; import React, { useState, useEffect } from "react"; import { cn } from "@/lib/utils"; import { ChevronDown, ChevronRight, Plus, Minus } from "lucide-react"; interface AccordionItem { key: string; title: string; content: string | React.ReactNode; icon?: string | React.ReactNode; disabled?: boolean; className?: string; } interface AccordionProps { items: AccordionItem[]; variant?: "default" | "bordered" | "filled" | "minimal"; size?: "sm" | "md" | "lg"; allowMultiple?: boolean; defaultOpen?: string | string[]; iconPosition?: "left" | "right"; animationDuration?: number; disabled?: boolean; onChange?: (openItems: string[]) => void; className?: string; } const Accordion: React.FC<AccordionProps> = ({ items, variant = "default", size = "md", allowMultiple = false, defaultOpen, iconPosition = "right", animationDuration = 300, disabled = false, onChange, className, }) => { const [openItems, setOpenItems] = useState<Set<string>>(new Set()); // Initialize default open items useEffect(() => { if (defaultOpen) { const defaultItems = Array.isArray(defaultOpen) ? defaultOpen : [defaultOpen]; setOpenItems(new Set(defaultItems)); } }, [defaultOpen]); // Handle item toggle const handleToggle = (itemKey: string) => { if (disabled) return; const item = items.find(i => i.key === itemKey); if (item?.disabled) return; setOpenItems(prev => { const newSet = new Set(prev); if (allowMultiple) { if (newSet.has(itemKey)) { newSet.delete(itemKey); } else { newSet.add(itemKey); } } else { if (newSet.has(itemKey)) { newSet.clear(); } else { newSet.clear(); newSet.add(itemKey); } } const openArray = Array.from(newSet); onChange?.(openArray); return newSet; }); }; // Size classes const sizeClasses = { sm: { container: "text-sm", header: "px-4 py-3", content: "px-4 pb-3", icon: "w-4 h-4", }, md: { container: "text-base", header: "px-6 py-4", content: "px-6 pb-4", icon: "w-5 h-5", }, lg: { container: "text-lg", header: "px-8 py-5", content: "px-8 pb-5", icon: "w-6 h-6", }, }; // Variant classes const variantClasses = { default: { container: "divide-y divide-gray-200 dark:divide-gray-700", item: "bg-white dark:bg-gray-800", header: "hover:bg-gray-50 dark:hover:bg-gray-700 cursor-pointer transition-colors duration-200", content: "bg-gray-50/50 dark:bg-gray-700/50", }, bordered: { container: "space-y-2", item: "bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-lg overflow-hidden", header: "hover:bg-gray-50 dark:hover:bg-gray-700 cursor-pointer transition-colors duration-200", content: "border-t border-gray-200 dark:border-gray-700 bg-gray-50/50 dark:bg-gray-700/50", }, filled: { container: "space-y-2", item: "bg-gray-100 dark:bg-gray-800 rounded-lg overflow-hidden", header: "hover:bg-gray-200 dark:hover:bg-gray-700 cursor-pointer transition-colors duration-200", content: "bg-white dark:bg-gray-900", }, minimal: { container: "divide-y divide-gray-100 dark:divide-gray-800", item: "bg-transparent", header: "hover:bg-gray-50 dark:hover:bg-gray-800 cursor-pointer transition-colors duration-200 rounded-lg", content: "bg-transparent", }, }; const currentSize = sizeClasses[size]; const currentVariant = variantClasses[variant]; return ( <div className={cn("w-full", currentSize.container, className)}> <div className={currentVariant.container}> {items.map((item, index) => { const isOpen = openItems.has(item.key); const isDisabled = disabled || item.disabled; return ( <motion.div key={item.key} className={cn(currentVariant.item, item.className)} initial={{ opacity: 0, y: 10 }} animate={{ opacity: 1, y: 0 }} transition={{ duration: 0.3, delay: index * 0.1 }} > {/* Header */} <motion.div className={cn( "flex items-center justify-between", currentSize.header, currentVariant.header, isDisabled && "opacity-50 cursor-not-allowed" )} onClick={() => handleToggle(item.key)} whileHover={!isDisabled ? { scale: 1.01 } : {}} whileTap={!isDisabled ? { scale: 0.99 } : {}} > <div className="flex items-center gap-3 flex-1"> {/* Icon */} {item.icon && iconPosition === "left" && ( <motion.div className="flex-shrink-0" animate={{ rotate: isOpen ? 180 : 0 }} transition={{ duration: 0.3 }} > {typeof item.icon === 'string' ? ( <span className="text-lg">{item.icon}</span> ) : ( item.icon )} </motion.div> )} {/* Title */} <motion.h3 className={cn( "font-medium text-gray-900 dark:text-white flex-1", isDisabled && "text-gray-400 dark:text-gray-500" )} animate={{ x: isOpen ? 4 : 0, }} transition={{ duration: 0.3 }} > {item.title} </motion.h3> {/* Icon */} {item.icon && iconPosition === "right" && ( <motion.div className="flex-shrink-0" animate={{ rotate: isOpen ? 180 : 0 }} transition={{ duration: 0.3 }} > {typeof item.icon === 'string' ? ( <span className="text-lg">{item.icon}</span> ) : ( item.icon )} </motion.div> )} </div> {/* Expand/Collapse Icon */} <motion.div className="flex-shrink-0 ml-3" animate={{ rotate: isOpen ? 180 : 0, scale: isOpen ? 1.1 : 1, }} transition={{ duration: 0.3 }} > {variant === "minimal" ? ( isOpen ? ( <Minus className={cn(currentSize.icon, "text-gray-500")} /> ) : ( <Plus className={cn(currentSize.icon, "text-gray-500")} /> ) ) : ( <ChevronDown className={cn(currentSize.icon, "text-gray-500")} /> )} </motion.div> </motion.div> {/* Content */} <AnimatePresence> {isOpen && ( <motion.div className={cn(currentSize.content, currentVariant.content)} initial={{ height: 0, opacity: 0 }} animate={{ height: "auto", opacity: 1 }} exit={{ height: 0, opacity: 0 }} transition={{ duration: animationDuration / 1000, ease: "easeInOut" }} > <motion.div className="pt-2" initial={{ y: -10, opacity: 0 }} animate={{ y: 0, opacity: 1 }} exit={{ y: -10, opacity: 0 }} transition={{ duration: 0.2, delay: 0.1 }} > {typeof item.content === 'string' ? ( <p className="text-gray-700 dark:text-gray-300 leading-relaxed"> {item.content} </p> ) : ( item.content )} </motion.div> </motion.div> )} </AnimatePresence> </motion.div> ); })} </div> </div> ); }; export default Accordion;
4

Update the import paths to match your project setup

Props

PropTypeDefaultDescription
itemsAccordionItem[][]Array of accordion items with title, content, and optional properties.
variant'default' | 'bordered' | 'filled' | 'minimal''default'Visual style variant of the accordion.
size'sm' | 'md' | 'lg''md'Size of the accordion items.
allowMultiplebooleanfalseAllow multiple accordion items to be open simultaneously.
defaultOpenstring | string[]undefinedDefault open accordion item(s) by key.
iconPosition'left' | 'right''right'Position of the expand/collapse icon.
animationDurationnumber300Duration of the expand/collapse animation in milliseconds.
disabledbooleanfalseDisable all accordion interactions.
onChange(openItems: string[]) => voidundefinedCallback function when accordion state changes.