Docs
Accordion

Accordion

Show or hide content in accordion

Our components provide the best user experience in both dark and light modes.
To ensure our website looks great on all devices, we utilized a responsive design approach with CSS media queries and flexible grid layouts.
By lazy-loading images and leveraging browser caching, we significantly improved the performance and loading speed of our web application.

Installation

Copy and paste the following code into your project.

components/edil-ozi/accordion.tsx
"use client";
import {
  Children,
  cloneElement,
  createContext,
  FC,
  ReactElement,
  ReactNode,
  useContext,
  useEffect,
  useRef,
  useState,
} from "react";
 
import { cn } from "@/lib/utils";
 
interface Children {
  children: ReactNode;
}
 
interface AccordionProps extends Children {
  multiple?: boolean;
}
interface AccordionItemProps extends Children {
  value: string;
  className?: string;
  disabled?: boolean;
}
interface AccordionSummary extends Children {
  expandIcon?: ReactNode;
  isExpanded?: boolean;
  handleClick?: () => void;
  setHeights?: (prev: {}) => void;
  className?: string;
}
interface AccordionDetails extends Children {
  setHeights?: (prev: {}) => void;
  className?: string;
}
interface AccordionContextType {
  activeItem: string;
  setToggle: (value: string) => void;
  multiple: boolean;
}
 
const AccordionContext = createContext<AccordionContextType | undefined>(undefined);
 
const useAccordionContext = (): AccordionContextType => {
  const context = useContext(AccordionContext);
  if (!context) {
    throw new Error("No context found for Accordion");
  }
  return context;
};
 
const Accordion: FC<AccordionProps> = ({ children, multiple = true }) => {
  const [activeItem, setActiveItem] = useState("");
 
  const setToggle = (value: string) => {
    setActiveItem(() => (activeItem !== value ? value : ""));
  };
 
  return (
    <AccordionContext.Provider value={{ activeItem, setToggle, multiple }}>
      <div className="w-full">{children}</div>
    </AccordionContext.Provider>
  );
};
 
const AccordionSummary: FC<AccordionSummary> = ({
  children,
  isExpanded,
  handleClick,
  setHeights,
  expandIcon,
  className,
}) => {
  const ref = useRef<HTMLButtonElement>(null);
 
  useEffect(() => {
    if (ref.current !== null) {
      setHeights?.((prev: {}) => ({ ...prev, summary: ref.current?.clientHeight }));
    }
  }, [setHeights]);
 
  return (
    <button
      ref={ref}
      className={cn("flex w-full cursor-pointer items-center justify-between p-4 font-medium", className)}
      onClick={handleClick}
    >
      <span className="text-start">{children}</span>
      <span className={`transition ${isExpanded && "rotate-180"}`}>
        {!expandIcon ? (
          <svg
            fill="none"
            width="24"
            height="24"
            shapeRendering="geometricPrecision"
            stroke="currentColor"
            strokeLinecap="round"
            strokeLinejoin="round"
            strokeWidth="1.5"
            viewBox="0 0 24 24"
          >
            <path d="M6 9l6 6 6-6"></path>
          </svg>
        ) : (
          expandIcon
        )}
      </span>
    </button>
  );
};
 
const AccordionDetails: FC<AccordionDetails> = ({ children, setHeights, className }) => {
  const ref = useRef<HTMLDivElement>(null);
  useEffect(() => {
    if (ref.current !== null) {
      setHeights?.((prev: {}) => ({ ...prev, details: ref.current?.clientHeight }));
    }
  }, [setHeights]);
  return (
    <div
      ref={ref}
      className={cn(`px-4 pb-5 pt-1 text-neutral-900 dark:text-neutral-300`, className)}
    >
      {children}
    </div>
  );
};
 
const AccordionItem: FC<AccordionItemProps> = ({ children, value, className, disabled }) => {
  const { activeItem, setToggle, multiple } = useAccordionContext();
  const [activeItemMultiple, setActiveItemMultiple] = useState("");
  const [heights, setHeights] = useState({ summary: 0, details: 0 });
 
  const isExpanded = !multiple ? activeItem === value : activeItemMultiple === value;
  const height = isExpanded ? heights.summary + heights.details : heights.summary;
 
  const handleClick = () => {
    if (!multiple) {
      setToggle(value);
    } else {
      setActiveItemMultiple(() => (activeItemMultiple !== value ? value : ""));
    }
  };
 
  return (
    <div
      style={{ height: `${height}px` }}
      className={cn(
        `box-content overflow-hidden border-b border-b-neutral-300 bg-gray-100 shadow-sm shadow-zinc-400 transition-all duration-200 ease-in-out first:rounded-t-sm last:rounded-b-sm dark:border-b-neutral-800 dark:bg-zinc-900 dark:shadow-zinc-950 ${isExpanded && "border-b-0 first:mb-4 last:mt-4 [&:not(:last-child):not(:first-child)]:my-4"} ${disabled && "bg-neutral-300 text-neutral-500 dark:bg-neutral-800"}`,
        className,
      )}
    >
      {Children.map(children, (child) => {
        return cloneElement(child as ReactElement, {
          isExpanded: isExpanded,
          handleClick: !disabled ? handleClick : null,
          setHeights: setHeights,
        });
      })}
    </div>
  );
};
 
export { Accordion, AccordionItem, AccordionSummary, AccordionDetails };

Examples

Only one expanded at a time

Use " multiple " prop, and set it to " false " to expand only one accordion.

Ensuring cross-browser compatibility, we tested our web application on multiple browsers and platforms to provide a consistent user experience.
We added a dark mode toggle to our website, allowing users to switch between light and dark themes based on their preferences.
Developing our site as a single page application (SPA) with Vue.js improved the user experience by reducing page reloads and increasing responsiveness.

With disabled accordion

Use " disabled " prop to make it disabled.

There is below documentation about this API.
Implementing client-side form validation with JavaScript improved data integrity and provided immediate feedback to users on input errors.
How did you open it?

Props

Prop nameTypeDefaultDescription
valuestring-Must be UNIQUE
multiplebooleantrueOnly one expanded at a time or multiple
disabledbooleanfalseMakes accordion disabled
expandIconElement-Expand icon
classNamestring-className for Accordeon element