Build websites 10x faster with HextaUI Blocks — Learn more
UIUI

Accordion

A vertically stacked set of interactive headings that each reveal an associated section of content.

import {
  Accordion,
  AccordionContent,
  AccordionItem,
  AccordionTrigger,
} from "@/components/ui/accordion";   

export function AccordionDemo() {
  return (
    <Accordion type="single" collapsible className="w-full max-w-2xl mx-auto">
      <AccordionItem value="item-1">
        <AccordionTrigger>Is it accessible?</AccordionTrigger>
        <AccordionContent>
          Yes. It adheres to the WAI-ARIA design pattern and includes full keyboard
          navigation support.
        </AccordionContent>
      </AccordionItem>
      <AccordionItem value="item-2">
        <AccordionTrigger>Is it styled?</AccordionTrigger>
        <AccordionContent>
          Yes. It comes with default styles that match the design system, but you
          can customize them to fit your needs.
        </AccordionContent>
      </AccordionItem>
      <AccordionItem value="item-3">
        <AccordionTrigger>Is it animated?</AccordionTrigger>
        <AccordionContent>
          Yes. It uses smooth CSS animations for expanding and collapsing that
          enhance the user experience.
        </AccordionContent>
      </AccordionItem>
    </Accordion>
  );
}

Installation

Install following dependencies:

npm install @radix-ui/react-accordion motion class-variance-authority lucide-react

Add required animation and keyframes to your CSS file or tailwind config file based on your Tailwind version.

app/global.css
  @theme {
    --animate-accordion-down: accordion-down 0.3s ease-out;
    --animate-accordion-up: accordion-up 0.3s ease-out;
  }

  @keyframes accordion-down {
    from {
      height: 0;
    }
    to {
      height: var(--radix-accordion-content-height);
    }
  }

  @keyframes accordion-up {
    from {
      height: var(--radix-accordion-content-height);
    }
    to {
      height: 0;
    }
  }
tailwind.config.js
  module.exports = {
    theme: {
      extend: {
        keyframes: {
          accordion-down: {
            from: {
              height: 0,
            },
            to: {
              height: var(--radix-accordion-content-height),
            },
          },
          accordion-up: {
            from: {
              height: var(--radix-accordion-content-height),
            },
            to: {
              height: 0,
            },
          },
        }
        animations: {
          "accordion-down": "accordion-down 0.3s ease-out",
          "accordion-up": "accordion-up 0.3s ease-out",
        },
      }
    }
  }

Copy and paste the following code into your project.

components/ui/accordion.tsx
"use client";

import * as React from "react";
import * as AccordionPrimitive from "@radix-ui/react-accordion";
import { cva, type VariantProps } from "class-variance-authority";
import { cn } from "@/lib/utils";
import { ChevronDown } from "lucide-react";

const accordionVariants = cva("w-full max-w-ele", {
  variants: {
    variant: {
      default:
        "border border-border rounded-ele overflow-hidden ",
      ghost: "",
      outline:
        "border border-border rounded-ele ",
    },
    size: {
      sm: "text-sm max-w-lg",
      default: "max-w-2xl",
      lg: "text-lg max-w-4xl",
    },
  },
  defaultVariants: {
    variant: "default",
    size: "default",
  },
});

const accordionItemVariants = cva(
  "border-b border-border last:border-b-0",
  {
    variants: {
      variant: {
        default: "",
        ghost:
          "border-b border-border last:border-b-0 mb-2 last:mb-0",
        outline: "",
      },
    },
    defaultVariants: {
      variant: "default",
    },
  }
);

const accordionTriggerVariants = cva(
  "flex flex-1 items-center justify-between py-4 px-6 text-left font-medium transition-all focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 group",
  {
    variants: {
      variant: {
        default: "",
        ghost: "px-0",
        outline: "",
      },
      size: {
        sm: "py-3 px-4 text-sm",
        default: "py-4 px-6",
        lg: "py-5 px-6 text-lg",
      },
    },
    defaultVariants: {
      variant: "default",
      size: "default",
    },
  }
);

const accordionContentVariants = cva(
  "px-6 pb-4 pt-0 text-muted-foreground",
  {
    variants: {
      variant: {
        default: "",
        ghost: "px-0",
        outline: "",
      },
      size: {
        sm: "px-4 pb-3 text-sm",
        default: "px-6 pb-4",
        lg: "px-6 pb-5",
      },
    },
    defaultVariants: {
      variant: "default",
      size: "default",
    },
  }
);

export interface AccordionProps extends VariantProps<typeof accordionVariants> {
  className?: string;
  children?: React.ReactNode;
}

// Single accordion props
export interface AccordionSingleProps extends AccordionProps {
  type: "single";
  collapsible?: boolean;
  defaultValue?: string;
  value?: string;
  onValueChange?: (value: string) => void;
}

// Multiple accordion props
export interface AccordionMultipleProps extends AccordionProps {
  type: "multiple";
  defaultValue?: string[];
  value?: string[];
  onValueChange?: (value: string[]) => void;
}

export type AccordionCombinedProps =
  | AccordionSingleProps
  | AccordionMultipleProps;

export interface AccordionItemProps
  extends VariantProps<typeof accordionItemVariants> {
  value: string;
  disabled?: boolean;
  className?: string;
  children?: React.ReactNode;
}

export interface AccordionTriggerProps
  extends VariantProps<typeof accordionTriggerVariants> {
  icon?: React.ReactNode;
  hideChevron?: boolean;
  className?: string;
  children?: React.ReactNode;
  onClick?: () => void;
}

export interface AccordionContentProps
  extends VariantProps<typeof accordionContentVariants> {
  className?: string;
  children?: React.ReactNode;
}

const Accordion = React.forwardRef<
  React.ElementRef<typeof AccordionPrimitive.Root>,
  AccordionCombinedProps
>(({ className, variant, size, children, ...props }, ref) => (
  <AccordionPrimitive.Root
    ref={ref}
    className={cn(accordionVariants({ variant, size }), className)}
    {...props}
  >
    {children}
  </AccordionPrimitive.Root>
));
Accordion.displayName = "Accordion";

const AccordionItem = React.forwardRef<
  React.ElementRef<typeof AccordionPrimitive.Item>,
  AccordionItemProps
>(({ className, variant, children, ...props }, ref) => (
  <AccordionPrimitive.Item
    ref={ref}
    className={cn(accordionItemVariants({ variant }), className)}
    {...props}
  >
    {children}
  </AccordionPrimitive.Item>
));
AccordionItem.displayName = "AccordionItem";

const AccordionTrigger = React.forwardRef<
  React.ElementRef<typeof AccordionPrimitive.Trigger>,
  AccordionTriggerProps
>(
  (
    { className, children, variant, size, icon, hideChevron = false, ...props },
    ref
  ) => (
    <AccordionPrimitive.Header className="flex">
      <AccordionPrimitive.Trigger
        ref={ref}
        className={cn(accordionTriggerVariants({ variant, size }), className)}
        {...props}
      >
        <div className="flex items-center gap-2">
          {icon && <span className="shrink-0">{icon}</span>}
          <span className="text-left group-hover:underline transition-all duration-200">
            {children}
          </span>
        </div>
        {!hideChevron && (
          <ChevronDown className="h-4 w-4 shrink-0 transition-transform duration-200 ease-in-out group-data-[state=open]:rotate-180" />
        )}
      </AccordionPrimitive.Trigger>
    </AccordionPrimitive.Header>
  )
);
AccordionTrigger.displayName = AccordionPrimitive.Trigger.displayName;

const AccordionContent = React.forwardRef<
  React.ElementRef<typeof AccordionPrimitive.Content>,
  AccordionContentProps
>(({ className, children, variant, size, ...props }, ref) => (
  <AccordionPrimitive.Content
    ref={ref}
    className="overflow-hidden data-[state=closed]:animate-accordion-up data-[state=open]:animate-accordion-down"
    {...props}
  >
    <div className={cn(accordionContentVariants({ variant, size }), className)}>
      {children}
    </div>
  </AccordionPrimitive.Content>
));
AccordionContent.displayName = AccordionPrimitive.Content.displayName;

export { Accordion, AccordionItem, AccordionTrigger, AccordionContent };
npx hextaui@latest add accordion

Usage

import {
  Accordion,
  AccordionContent,
  AccordionItem,
  AccordionTrigger,
} from "@/components/ui/accordion";
<Accordion type="single" collapsible>
  <AccordionItem value="item-1">
    <AccordionTrigger>Is it accessible?</AccordionTrigger>
    <AccordionContent>
      Yes. It adheres to the WAI-ARIA design pattern.
    </AccordionContent>
  </AccordionItem>
</Accordion>

Props

Accordion

PropTypeDefault
type?
"single" | "multiple"
undefined
collapsible?
boolean
false
defaultValue?
string
undefined
defaultValueArray?
string[]
undefined
value?
string
undefined
valueArray?
string[]
undefined
onValueChange?
function
undefined
variant?
"default" | "ghost" | "outline"
"default"
size?
"sm" | "default" | "lg"
"default"
className?
string
undefined

AccordionItem

PropTypeDefault
value?
string
undefined
disabled?
boolean
false
variant?
"default" | "ghost" | "outline"
"default"
className?
string
undefined

AccordionTrigger

PropTypeDefault
icon?
ReactNode
undefined
hideChevron?
boolean
false
variant?
"default" | "ghost" | "outline"
"default"
size?
"sm" | "default" | "lg"
"default"
className?
string
undefined
children?
ReactNode
undefined

AccordionContent

PropTypeDefault
variant?
"default" | "ghost" | "outline"
"default"
size?
"sm" | "default" | "lg"
"default"
className?
string
undefined
children?
ReactNode
undefined
Edit on GitHub

Last updated on