Build websites 10x faster with HextaUI Blocks — Learn more
UI/UI

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
pnpm add @radix-ui/react-accordion motion class-variance-authority lucide-react
yarn add @radix-ui/react-accordion motion class-variance-authority lucide-react
bun add @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 shadow-sm/2",
      ghost: "",
      outline:
        "border border-border rounded-ele shadow-sm/2",
    },
    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
pnpm dlx hextaui@latest add accordion
yarn dlx hextaui@latest add accordion
bun x 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>

Examples

Multiple Items

<Accordion type="multiple" className="w-full">
  <AccordionItem value="item-1">
    <AccordionTrigger>Can I open multiple items?</AccordionTrigger>
    <AccordionContent>
      Yes. You can configure the accordion to allow multiple items to be
      open at the same time by setting type="multiple".
    </AccordionContent>
  </AccordionItem>
  <AccordionItem value="item-2">
    <AccordionTrigger>How does it work?</AccordionTrigger>
    <AccordionContent>
      When type is set to "multiple", users can expand multiple accordion
      items simultaneously.
    </AccordionContent>
  </AccordionItem>
</Accordion>

Variants

Default

Ghost

Outline

{/* Default */}
<Accordion type="single" collapsible variant="default">
  <AccordionItem value="item-1">
    <AccordionTrigger>Default variant</AccordionTrigger>
    <AccordionContent>
      This is the default accordion variant with borders and background.
    </AccordionContent>
  </AccordionItem>
</Accordion>

{/* Ghost */}
<Accordion type="single" collapsible variant="ghost">
  <AccordionItem value="item-1" variant="ghost">
    <AccordionTrigger variant="ghost">Ghost variant</AccordionTrigger>
    <AccordionContent variant="ghost">
      This is the ghost accordion variant with minimal styling.
    </AccordionContent>
  </AccordionItem>
</Accordion>

{/* Outline */}
<Accordion type="single" collapsible variant="outline">
  <AccordionItem value="item-1">
    <AccordionTrigger variant="outline">Outline variant</AccordionTrigger>
    <AccordionContent variant="outline">
      This is the outline accordion variant with border styling.
    </AccordionContent>
  </AccordionItem>
</Accordion>

Sizes

Small

Default

Large

{/* Small */}
<Accordion type="single" collapsible size="sm">
  <AccordionItem value="item-1">
    <AccordionTrigger size="sm">Small accordion</AccordionTrigger>
    <AccordionContent size="sm">
      This is a small-sized accordion with compact spacing.
    </AccordionContent>
  </AccordionItem>
</Accordion>

{/* Default */}
<Accordion type="single" collapsible>
  <AccordionItem value="item-1">
    <AccordionTrigger>Default accordion</AccordionTrigger>
    <AccordionContent>
      This is the default-sized accordion with standard spacing.
    </AccordionContent>
  </AccordionItem>
</Accordion>

{/* Large */}
<Accordion type="single" collapsible size="lg">
  <AccordionItem value="item-1">
    <AccordionTrigger size="lg">Large accordion</AccordionTrigger>
    <AccordionContent size="lg">
      This is a large-sized accordion with generous spacing.
    </AccordionContent>
  </AccordionItem>
</Accordion>

With Icons

import { Star, Shield, Zap, Heart } from "lucide-react";

<Accordion type="single" collapsible className="w-full">
  <AccordionItem value="features">
    <AccordionTrigger icon={<Star className="h-4 w-4" />}>
      Features
    </AccordionTrigger>
    <AccordionContent>
      Our platform includes advanced features like real-time collaboration,
      version control, and automated deployments.
    </AccordionContent>
  </AccordionItem>
  <AccordionItem value="security">
    <AccordionTrigger icon={<Shield className="h-4 w-4" />}>
      Security
    </AccordionTrigger>
    <AccordionContent>
      We implement enterprise-grade security with end-to-end encryption,
      two-factor authentication, and regular security audits.
    </AccordionContent>
  </AccordionItem>
</Accordion>

Custom Styling

Rounded

No Chevron

Custom Icon

import { ChevronRight } from "lucide-react";

<div className="space-y-6 w-full max-w-2xl mx-auto">
  <div>
    <h3 className="text-lg font-semibold mb-3">Rounded</h3>
    <Accordion
      type="single"
      collapsible
      className="w-full rounded-ele overflow-hidden"
    >
      <AccordionItem value="item-1">
        <AccordionTrigger>Rounded accordion</AccordionTrigger>
        <AccordionContent>
          This accordion has custom rounded corners applied.
        </AccordionContent>
      </AccordionItem>
    </Accordion>
  </div>

  <div>
    <h3 className="text-lg font-semibold mb-3">No Chevron</h3>
    <Accordion type="single" collapsible className="w-full">
      <AccordionItem value="item-1">
        <AccordionTrigger hideChevron>
          Accordion without chevron
        </AccordionTrigger>
        <AccordionContent>
          This accordion item has the chevron icon hidden.
        </AccordionContent>
      </AccordionItem>
    </Accordion>
  </div>

  <div>
    <h3 className="text-lg font-semibold mb-3">Custom Icon</h3>
    <Accordion type="single" collapsible className="w-full">
      <AccordionItem value="item-1">
        <AccordionTrigger
          icon={<ChevronRight className="h-4 w-4" />}
          hideChevron
        >
          Custom expand icon
        </AccordionTrigger>
        <AccordionContent>
          This accordion uses a custom icon instead of the default chevron.
        </AccordionContent>
      </AccordionItem>
    </Accordion>
  </div>
</div>

FAQ Example

import { HelpCircle } from "lucide-react";

const faqs = [
  {
    question: "How do I get started?",
    answer: "Getting started is easy! Simply sign up for an account, choose your plan, and follow our step-by-step onboarding guide."
  },
  {
    question: "What payment methods do you accept?",
    answer: "We accept all major credit cards (Visa, MasterCard, American Express), PayPal, and bank transfers for enterprise customers."
  },
  // ... more FAQs
];

<div className="max-w-2xl mx-auto">
  <Accordion type="single" collapsible className="w-full">
    {faqs.map((faq, index) => (
      <AccordionItem key={index} value={`faq-${index}`}>
        <AccordionTrigger icon={<HelpCircle className="h-4 w-4" />}>
          {faq.question}
        </AccordionTrigger>
        <AccordionContent>
          {faq.answer}
        </AccordionContent>
      </AccordionItem>
    ))}
  </Accordion>
</div>

Nested Accordions

import { User, Settings } from "lucide-react";

<div className="w-full max-w-2xl mx-auto">
  <Accordion type="single" collapsible className="w-full">
    <AccordionItem value="account">
      <AccordionTrigger icon={<User className="h-4 w-4" />}>
        Account Settings
      </AccordionTrigger>
      <AccordionContent>
        <Accordion
          type="single"
          collapsible
          variant="ghost"
          className="mt-2 w-full"
        >
          <AccordionItem value="profile" variant="ghost">
            <AccordionTrigger variant="ghost" size="sm">
              Profile Information
            </AccordionTrigger>
            <AccordionContent variant="ghost" size="sm">
              Manage your personal information, profile picture, and bio.
            </AccordionContent>
          </AccordionItem>
          <AccordionItem value="privacy" variant="ghost">
            <AccordionTrigger variant="ghost" size="sm">
              Privacy Settings
            </AccordionTrigger>
            <AccordionContent variant="ghost" size="sm">
              Control who can see your profile and contact you.
            </AccordionContent>
          </AccordionItem>
        </Accordion>
      </AccordionContent>
    </AccordionItem>
    <AccordionItem value="preferences">
      <AccordionTrigger icon={<Settings className="h-4 w-4" />}>
        Preferences
      </AccordionTrigger>
      <AccordionContent>
        <Accordion
          type="single"
          collapsible
          variant="ghost"
          className="mt-2 w-full"
        >
          <AccordionItem value="notifications" variant="ghost">
            <AccordionTrigger variant="ghost" size="sm">
              Notifications
            </AccordionTrigger>
            <AccordionContent variant="ghost" size="sm">
              Configure email and push notification preferences.
            </AccordionContent>
          </AccordionItem>
          <AccordionItem value="theme" variant="ghost">
            <AccordionTrigger variant="ghost" size="sm">
              Theme & Appearance
            </AccordionTrigger>
            <AccordionContent variant="ghost" size="sm">
              Choose between light, dark, or system theme preferences.
            </AccordionContent>
          </AccordionItem>
        </Accordion>
      </AccordionContent>
    </AccordionItem>
  </Accordion>
</div>

Props

Accordion

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

AccordionItem

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

AccordionTrigger

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

AccordionContent

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

Last updated on