Accordion

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

Installation

Install the primitive and Copy-Paste the component code in a .tsx file.

npm install @radix-ui/react-accordion
import React from "react";
import * as AccordionPrimitive from "@radix-ui/react-accordion";
import { ChevronDownIcon } from "@radix-ui/react-icons";
import { accordion, type AccordionProps } from "@tailus/themer";

const defaultContextValue:AccordionProps  = {variant : "default", fancy : true};
const Context = React.createContext<AccordionProps>(defaultContextValue);

const AccordionRoot = React.forwardRef<
  React.ElementRef<typeof AccordionPrimitive.Root>,
  React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Root> & AccordionProps
  >(({ className, variant, fancy, ...props }, forwardedRef) => {
  const { root } = accordion({variant});
  return (
    <Context.Provider value={{variant, fancy} || defaultContextValue}>
      <AccordionPrimitive.Root
        className={root({className})}
        {...props}
        ref={forwardedRef}
      />
    </Context.Provider>
  )
});

const AccordionItem = React.forwardRef<
  React.ElementRef<typeof AccordionPrimitive.Item>,
  React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Item> & AccordionProps
  >(({ className, fancy, ...props }, forwardedRef) => {
  
  const { variant, fancy: contextFancy } = React.useContext(Context);
  const { item } = accordion({ variant }) 

  fancy = fancy || contextFancy;

  if (variant === "soft" && fancy) {
    throw new Error("The fancy style cannot be applied with the 'soft' variant !")
  } 

  return (
    <AccordionPrimitive.Item
      className={item({fancy, className})}
      {...props}
      ref={forwardedRef}
    />
  )
});

const AccordionTrigger = React.forwardRef<
  React.ElementRef<typeof AccordionPrimitive.Trigger>,
  React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Trigger>
>(({className, children, ...props}, forwardedRef) => {
  const {variant} = React.useContext(Context);
  const {header, trigger, triggerIcon, triggerContent} = accordion({variant})

  return (
    <AccordionPrimitive.Header className={header({className})}>
      <AccordionPrimitive.Trigger
        className={trigger({className})}
        {...props}
        ref={forwardedRef}
      >
        <div className={triggerContent({className})}>
          {children}
        </div>
        <ChevronDownIcon className={triggerIcon({className})} aria-hidden={true}/>
      </AccordionPrimitive.Trigger>
    </AccordionPrimitive.Header>
  )
});

const AccordionContent = React.forwardRef<
  React.ElementRef<typeof AccordionPrimitive.Content>,
  React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Content>
>(({className, children, ...props}, forwardedRef) => {
  const {variant} = React.useContext(Context);
  const { content } = accordion({variant})

  return (
    <AccordionPrimitive.Content
      className={content({className})}
      {...props}
      ref={forwardedRef}
    >
      <div>
        {children}
      </div>
    </AccordionPrimitive.Content>
  )
});

const Accordion = {
  Root: AccordionRoot,
  Item: AccordionItem,
  Trigger: AccordionTrigger,
  Content: AccordionContent
}

export default Accordion;

Usage

Import the Accordion parts you need to use in your component and build your Accordion.

import Accordion from "@tailus-ui/Accordion";
const items = [
    // ... your questions and answers here
]

export const FAQs = () => {
    return (
        <Accordion.Root
            type="single"
            collapsible
            variant="mixed"
            className="max-w-md w-full"
        >
            {
                items.map((item, index) => (
                    <Accordion.Item value={item.question} key={index}>
                        <Accordion.Trigger>
                            {item.question}
                        </Accordion.Trigger>
                        <Accordion.Content>
                            {item.answer}
                        </Accordion.Content>
                    </Accordion.Item>
                ))
            }
            
        </Accordion.Root>
    )
}

Reference

Root

The parent container of the Accordion component

Prop
Type
Default
variant
enum
default
fancy
boolean
false

Examples

Default

import Accordion from "tailus-ui/Accordion";

const items = [
    // ... your questions and answers here
]

export const Default = () => {
    return (
        <Accordion.Root
            type="single"
            collapsible
            className="max-w-md w-full"
        >
            {
                items.map((item) => (
                    <Accordion.Item value={item.question} key={items.indexOf(item)}>
                        <Accordion.Trigger>
                            {item.question}
                        </Accordion.Trigger>
                        <Accordion.Content>
                            {item.answer}
                        </Accordion.Content>
                    </Accordion.Item>
                ))
            }
            
        </Accordion.Root>
    )
}

Outlined

import Accordion from "tailus-ui/Accordion";

const items = [
    ... your questions and answers here
]

export const Outlined = () => {
    return (
        <Accordion.Root
            type="single"
            collapsible
            variant="outlined"
            className="max-w-md w-full"
        >
            {
                items.map((item) => (
                    <Accordion.Item value={item.question} key={items.indexOf(item)}>
                        <Accordion.Trigger>
                            {item.question}
                        </Accordion.Trigger>
                        <Accordion.Content>
                            {item.answer}
                        </Accordion.Content>
                    </Accordion.Item>
                ))
            }
            
        </Accordion.Root>
    )
}

Elevated

import Accordion from "tailus-ui/Accordion";

const items = [
    // ... your questions and answers here
]

export const Elevated = () => {
    return (
        <Accordion.Root
            type="single"
            collapsible
            variant="elevated"
            className="max-w-md w-full"
        >
            {
                items.map((item) => (
                    <Accordion.Item value={item.question} key={items.indexOf(item)}>
                        <Accordion.Trigger>
                            {item.question}
                        </Accordion.Trigger>
                        <Accordion.Content>
                            {item.answer}
                        </Accordion.Content>
                    </Accordion.Item>
                ))
            }
        </Accordion.Root>
    )
}

Mixed

import Accordion from "tailus-ui/Accordion";

const items = [
    // ... your questions and answers here
]

export const OutlinedElevated = () => {
    return (
        <Accordion.Root
            type="single"
            collapsible
            variant="outlinedElevated"
            className="max-w-md w-full"
        >
            {
                items.map((item) => (
                    <Accordion.Item value={item.question} key={items.indexOf(item)}>
                        <Accordion.Trigger>
                            {item.question}
                        </Accordion.Trigger>
                        <Accordion.Content>
                            {item.answer}
                        </Accordion.Content>
                    </Accordion.Item>
                ))
            }
            
        </Accordion.Root>
    )
}

Ghost

import Accordion from "tailus-ui/Accordion";

const items = [
    // ... your questions and answers here
]

export const Ghost = () => {
    return (
        <Accordion.Root
            type="single"
            collapsible
            variant="ghost"
            className="max-w-md w-full"
        >
            {
                items.map((item) => (
                    <Accordion.Item value={item.question} key={items.indexOf(item)}>
                        <Accordion.Trigger>
                            {item.question}
                        </Accordion.Trigger>
                        <Accordion.Content>
                            {item.answer}
                        </Accordion.Content>
                    </Accordion.Item>
                ))
            }
            
        </Accordion.Root>
    )
}

Soft

import Accordion from "tailus-ui/Accordion";

const items = [
    // ... your questions and answers here
]

export const Soft = () => {
    return (
        <Accordion.Root
            type="single"
            collapsible
            variant="soft"
            className="max-w-md w-full"
        >
            {
                items.map((item) => (
                    <Accordion.Item value={item.question} key={items.indexOf(item)}>
                        <Accordion.Trigger>
                            {item.question}
                        </Accordion.Trigger>
                        <Accordion.Content>
                            {item.answer}
                        </Accordion.Content>
                    </Accordion.Item>
                ))
            }
            
        </Accordion.Root>
    )
}

Custom

Tailus Themer is a versatile theming library designed to help you create modern and custom web User Interfaces with Tailwind CSS.

import Accordion from "tailus-ui/Accordion";

const items = [
    // ... your questions and answers here
]

export const Custom = () => {
    return (
        <Accordion.Root
            type="single"
            collapsible
            className="max-w-md w-full"
        >
            {
                items.map((item) => (
                    <Accordion.Item value={item.question} key={items.indexOf(item)}>
                        <Accordion.Trigger>
                            <img className="size-8" src={item.icon} alt="tailus accordion img" loading="lazy" width={512} height={512} />
                            {item.question}
                        </Accordion.Trigger>
                        <Accordion.Content className="ml-12">
                            {item.answer}
                        </Accordion.Content>
                    </Accordion.Item>
                ))
            }
            
        </Accordion.Root>
    )
}