Skip to Content
ComponentsAccordion

Accordion

Collapsible content sections with Item, Title, and Description sub-components. Supports defaultOpen and disabled per item.

Design Language Comparison

Neobrutalism
A bold design language with thick borders, flat shadows, and high contrast.
Each item manages its own open/close state with React useState.
Shadcn-inspired
Yes. Each button has proper aria-expanded and keyboard support.
Absolutely. Add "use client" and import directly.
Flowbite-inspired
A design system built on Tailwind CSS with clean, accessible UI components.
Override Tailwind classes via className props or extend the component.
Glassmorphism
Translucent blur panels with subtle white borders.
backdrop-blur-md requires a colourful background behind the element.
Material Design 3
Purple tonal surfaces, pill radius, soft elevation.
A lavender-tinted background derived from the primary colour.
Neumorphism
Soft dual-tone shadows on a matching background.
The shadow effect needs the component colour to blend with its container.

Neobrutalism

Thick border, flat box shadow, yellow active hover on title.

A bold design language with thick borders, flat shadows, and high contrast.
Each item manages its own open/close state with React useState.
const AccordionContext = createContext(undefined);
const useAccordion = () => useContext(AccordionContext);

const Title = ({ children }) => {
  const { show, setShow, disabled } = useAccordion();
  return (
    <button
      disabled={disabled}
      onClick={() => !disabled && setShow(!show)}
      className="w-full flex justify-between items-center px-4 py-3 font-bold bg-white hover:bg-yellow-400 transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
    >
      <span className="text-left">{children}</span>
      <ChevronDown size={20} className={`shrink-0 ml-2 transition-transform duration-200 ${show ? "rotate-180" : ""}`} />
    </button>
  );
};

const Description = ({ children }) => {
  const { show } = useAccordion();
  return (
    <div className={`overflow-hidden transition-all duration-200 ${show ? "max-h-96" : "max-h-0"}`}>
      <div className="px-4 py-3 bg-white text-sm border-t-[3px] border-black">{children}</div>
    </div>
  );
};

const Item = ({ children, defaultOpen = false, disabled = false }) => {
  const [show, setShow] = useState(defaultOpen);
  return (
    <AccordionContext.Provider value={{ show, setShow, disabled }}>
      <div className="border-b-[3px] border-black last:border-b-0">{children}</div>
    </AccordionContext.Provider>
  );
};

const NeobrutalismAccordion = ({ children }) => (
  <div className="border-[3px] border-black shadow-[4px_4px_0_#000] rounded-[2px] overflow-hidden">
    {children}
  </div>
);

NeobrutalismAccordion.Item = Item;
NeobrutalismAccordion.Title = Title;
NeobrutalismAccordion.Description = Description;

// Usage
<NeobrutalismAccordion>
  <NeobrutalismAccordion.Item>
    <NeobrutalismAccordion.Title>What is Neobrutalism?</NeobrutalismAccordion.Title>
    <NeobrutalismAccordion.Description>
      A bold design language with thick borders, flat shadows, and high contrast.
    </NeobrutalismAccordion.Description>
  </NeobrutalismAccordion.Item>
  <NeobrutalismAccordion.Item>
    <NeobrutalismAccordion.Title>How does it work?</NeobrutalismAccordion.Title>
    <NeobrutalismAccordion.Description>
      Each item manages its own open/close state with React useState.
    </NeobrutalismAccordion.Description>
  </NeobrutalismAccordion.Item>
</NeobrutalismAccordion>

Shadcn-inspired

Zinc border, underline hover on title, minimal radius.

Yes. Each button has proper aria-expanded and keyboard support.
Absolutely. Add "use client" and import directly.
const AccordionContext = createContext(undefined);
const useAccordion = () => useContext(AccordionContext);

const Title = ({ children }) => {
  const { show, setShow, disabled } = useAccordion();
  return (
    <button
      disabled={disabled}
      onClick={() => !disabled && setShow(!show)}
      className="w-full flex justify-between items-center px-4 py-3 font-medium text-sm hover:underline disabled:opacity-50 disabled:cursor-not-allowed"
    >
      <span className="text-left">{children}</span>
      <ChevronDown size={16} className={`shrink-0 ml-2 transition-transform duration-200 ${show ? "rotate-180" : ""}`} />
    </button>
  );
};

const Description = ({ children }) => {
  const { show } = useAccordion();
  return (
    <div className={`overflow-hidden transition-all duration-200 ${show ? "max-h-96" : "max-h-0"}`}>
      <div className="px-4 pb-3 pt-0 text-sm text-zinc-600">{children}</div>
    </div>
  );
};

const Item = ({ children, defaultOpen = false, disabled = false }) => {
  const [show, setShow] = useState(defaultOpen);
  return (
    <AccordionContext.Provider value={{ show, setShow, disabled }}>
      <div className="border-b border-zinc-200 last:border-b-0">{children}</div>
    </AccordionContext.Provider>
  );
};

const ShadcnAccordion = ({ children }) => (
  <div className="border border-zinc-200 rounded-[6px] overflow-hidden">
    {children}
  </div>
);

ShadcnAccordion.Item = Item;
ShadcnAccordion.Title = Title;
ShadcnAccordion.Description = Description;

// Usage
<ShadcnAccordion>
  <ShadcnAccordion.Item>
    <ShadcnAccordion.Title>Is it accessible?</ShadcnAccordion.Title>
    <ShadcnAccordion.Description>
      Yes. Each button has proper aria-expanded and keyboard support.
    </ShadcnAccordion.Description>
  </ShadcnAccordion.Item>
  <ShadcnAccordion.Item>
    <ShadcnAccordion.Title>Can I use it in Next.js?</ShadcnAccordion.Title>
    <ShadcnAccordion.Description>
      Absolutely. Add "use client" and import directly.
    </ShadcnAccordion.Description>
  </ShadcnAccordion.Item>
</ShadcnAccordion>

Flowbite-inspired

Gray border, soft shadow, gray-50 description panel.

A design system built on Tailwind CSS with clean, accessible UI components.
Override Tailwind classes via className props or extend the component.
const AccordionContext = createContext(undefined);
const useAccordion = () => useContext(AccordionContext);

const Title = ({ children }) => {
  const { show, setShow, disabled } = useAccordion();
  return (
    <button
      disabled={disabled}
      onClick={() => !disabled && setShow(!show)}
      className="w-full flex justify-between items-center px-4 py-3 font-medium text-sm hover:bg-gray-50 transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
    >
      <span className="text-left">{children}</span>
      <ChevronDown size={16} className={`shrink-0 ml-2 transition-transform duration-200 ${show ? "rotate-180" : ""}`} />
    </button>
  );
};

const Description = ({ children }) => {
  const { show } = useAccordion();
  return (
    <div className={`overflow-hidden transition-all duration-200 ${show ? "max-h-96" : "max-h-0"}`}>
      <div className="px-4 py-3 text-sm text-gray-600 bg-gray-50 border-t border-gray-200">{children}</div>
    </div>
  );
};

const Item = ({ children, defaultOpen = false, disabled = false }) => {
  const [show, setShow] = useState(defaultOpen);
  return (
    <AccordionContext.Provider value={{ show, setShow, disabled }}>
      <div className="border-b border-gray-200 last:border-b-0">{children}</div>
    </AccordionContext.Provider>
  );
};

const FlowbiteAccordion = ({ children }) => (
  <div className="border border-gray-200 rounded-lg shadow-sm overflow-hidden">
    {children}
  </div>
);

FlowbiteAccordion.Item = Item;
FlowbiteAccordion.Title = Title;
FlowbiteAccordion.Description = Description;

// Usage
<FlowbiteAccordion>
  <FlowbiteAccordion.Item>
    <FlowbiteAccordion.Title>What is Flowbite?</FlowbiteAccordion.Title>
    <FlowbiteAccordion.Description>
      A design system built on Tailwind CSS with clean, accessible UI components.
    </FlowbiteAccordion.Description>
  </FlowbiteAccordion.Item>
  <FlowbiteAccordion.Item>
    <FlowbiteAccordion.Title>How do I customize it?</FlowbiteAccordion.Title>
    <FlowbiteAccordion.Description>
      Override Tailwind classes via className props or extend the component.
    </FlowbiteAccordion.Description>
  </FlowbiteAccordion.Item>
</FlowbiteAccordion>

Glassmorphism

Glass panels with backdrop blur and translucent chevron on gradient background.

Translucent blur panels with subtle white borders.
backdrop-blur-md requires a colourful background behind the element.
const GlassmorphismAccordion = ({ items = [], allowMultiple = false }) => {
  const [open, setOpen] = useState(new Set());
  const toggle = (id) => setOpen((prev) => {
    const next = new Set(allowMultiple ? prev : []);
    prev.has(id) ? next.delete(id) : next.add(id);
    return next;
  });
  return (
    <div className="flex flex-col gap-2 font-sans">
      {items.map((item) => (
        <div key={item.id} className="bg-white/10 backdrop-blur-md border border-white/20 rounded-xl overflow-hidden">
          <button
            onClick={() => toggle(item.id)}
            className="w-full flex items-center justify-between px-4 py-3 text-left text-[0.9375rem] font-medium text-white hover:bg-white/10 transition-colors duration-200"
          >
            <span>{item.title}</span>
            <span className={`transition-transform duration-200 text-white/70 ${open.has(item.id) ? "rotate-180" : ""}`}></span>
          </button>
          {open.has(item.id) && (
            <div className="px-4 pb-4 text-sm text-white/70 border-t border-white/10 pt-3">{item.content}</div>
          )}
        </div>
      ))}
    </div>
  );
};

Material Design 3

Tonal header fill when open, purple chevron, clean dividers.

Purple tonal surfaces, pill radius, soft elevation.
A lavender-tinted background derived from the primary colour.
const Md3Accordion = ({ items = [], allowMultiple = false }) => {
  const [open, setOpen] = useState(new Set());
  const toggle = (id) => setOpen((prev) => {
    const next = new Set(allowMultiple ? prev : []);
    prev.has(id) ? next.delete(id) : next.add(id);
    return next;
  });
  return (
    <div className="flex flex-col font-sans border border-[#e7e0ec] rounded-[12px] overflow-hidden">
      {items.map((item, i) => (
        <div key={item.id} className={`${i > 0 ? "border-t border-[#e7e0ec]" : ""}`}>
          <button
            onClick={() => toggle(item.id)}
            className={`w-full flex items-center justify-between px-4 py-3 text-left text-[0.9375rem] font-medium transition-colors duration-200 ${open.has(item.id) ? "bg-[#f3edf7] text-[#6750a4]" : "bg-[#fffbfe] text-[#1c1b1f] hover:bg-[#f3edf7]"}`}
          >
            <span>{item.title}</span>
            <span className={`transition-transform duration-200 ${open.has(item.id) ? "rotate-180 text-[#6750a4]" : "text-[#49454f]"}`}></span>
          </button>
          {open.has(item.id) && (
            <div className="px-4 pb-4 text-sm text-[#49454f] bg-[#fffbfe] pt-3">{item.content}</div>
          )}
        </div>
      ))}
    </div>
  );
};

Neumorphism

Raised panel; active header gets concave inset, no border.

Soft dual-tone shadows on a matching background.
The shadow effect needs the component colour to blend with its container.
const NmAccordion = ({ items = [], allowMultiple = false }) => {
  const [open, setOpen] = useState(new Set());
  const toggle = (id) => setOpen((prev) => {
    const next = new Set(allowMultiple ? prev : []);
    prev.has(id) ? next.delete(id) : next.add(id);
    return next;
  });
  return (
    <div className="flex flex-col gap-3 font-sans">
      {items.map((item) => (
        <div key={item.id} className="bg-[#e0e5ec] rounded-xl overflow-hidden shadow-[-5px_-5px_10px_#ffffff,_5px_5px_10px_rgba(163,177,198,0.6)]">
          <button
            onClick={() => toggle(item.id)}
            className={`w-full flex items-center justify-between px-4 py-3 text-left text-[0.9375rem] font-semibold text-[#6c7a9c] transition-all duration-150 ${open.has(item.id) ? "shadow-[inset_-2px_-2px_5px_#ffffff,_inset_2px_2px_5px_rgba(163,177,198,0.4)]" : ""}`}
          >
            <span>{item.title}</span>
            <span className={`transition-transform duration-200 text-[#6c7a9c]/60 ${open.has(item.id) ? "rotate-180" : ""}`}></span>
          </button>
          {open.has(item.id) && (
            <div className="px-4 pb-4 text-sm text-[#6c7a9c] border-t border-[rgba(163,177,198,0.3)] pt-3">{item.content}</div>
          )}
        </div>
      ))}
    </div>
  );
};
Last updated on

© 2026 UI Variants. Built with Nextra.