V1.0

Base Components

Accordion

Allows you to display content in a collapsible manner, making it easy for users to access information while conserving screen space.

Install the following dependencies:

terminal
npm install @radix-ui/react-accordion

Create a accordion.tsx file and paste the following code into it.

/components/ui/accordion.tsx
// AlignUI Accordion v0.0.0
 
'use client';
 
import * as React from 'react';
import * as AccordionPrimitive from '@radix-ui/react-accordion';
import { RiAddLine, RiSubtractLine } from '@remixicon/react';
 
import { cnExt } from '@/utils/cn';
import type { PolymorphicComponentProps } from '@/utils/polymorphic';
 
const ACCORDION_ITEM_NAME = 'AccordionItem';
const ACCORDION_ICON_NAME = 'AccordionIcon';
const ACCORDION_ARROW_NAME = 'AccordionArrow';
const ACCORDION_TRIGGER_NAME = 'AccordionTrigger';
const ACCORDION_CONTENT_NAME = 'AccordionContent';
 
const AccordionRoot = AccordionPrimitive.Root;
const AccordionHeader = AccordionPrimitive.Header;
 
const AccordionItem = React.forwardRef<
  React.ComponentRef<typeof AccordionPrimitive.Item>,
  React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Item>
>(({ className, ...rest }, forwardedRef) => {
  return (
    <AccordionPrimitive.Item
      ref={forwardedRef}
      className={cnExt(
        // base
        'group/accordion',
        'rounded-10 bg-bg-white-0 p-3.5 ring-1 ring-inset ring-stroke-soft-200',
        'transition duration-200 ease-out',
        // hover
        'hover:bg-bg-weak-50 hover:ring-transparent',
        // has-focus-visible
        'has-[:focus-visible]:bg-bg-weak-50 has-[:focus-visible]:ring-transparent',
        // open
        'data-[state=open]:bg-bg-weak-50 data-[state=open]:ring-transparent',
        className,
      )}
      {...rest}
    />
  );
});
AccordionItem.displayName = ACCORDION_ITEM_NAME;
 
const AccordionTrigger = React.forwardRef<
  React.ComponentRef<typeof AccordionPrimitive.Trigger>,
  React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Trigger>
>(({ children, className, ...rest }, forwardedRef) => {
  return (
    <AccordionPrimitive.Trigger
      ref={forwardedRef}
      className={cnExt(
        // base
        'w-[calc(100%+theme(space.7))] text-left text-label-sm text-text-strong-950',
        'grid auto-cols-auto grid-flow-col grid-cols-[auto,minmax(0,1fr)] items-center gap-2.5',
        '-m-3.5 p-3.5 outline-none',
        // focus
        'focus:outline-none',
        className,
      )}
      {...rest}
    >
      {children}
    </AccordionPrimitive.Trigger>
  );
});
AccordionTrigger.displayName = ACCORDION_TRIGGER_NAME;
 
function AccordionIcon<T extends React.ElementType>({
  className,
  as,
  ...rest
}: PolymorphicComponentProps<T>) {
  const Component = as || 'div';
 
  return (
    <Component
      className={cnExt('size-5 text-text-sub-600', className)}
      {...rest}
    />
  );
}
AccordionIcon.displayName = ACCORDION_ICON_NAME;
 
type AccordionArrowProps = React.HTMLAttributes<HTMLDivElement> & {
  openIcon?: React.ElementType;
  closeIcon?: React.ElementType;
};
 
// open/close
function AccordionArrow({
  className,
  openIcon: OpenIcon = RiAddLine,
  closeIcon: CloseIcon = RiSubtractLine,
  ...rest
}: AccordionArrowProps) {
  return (
    <>
      <OpenIcon
        className={cnExt(
          'size-5 text-text-soft-400',
          'transition duration-200 ease-out',
          // hover
          'group-hover/accordion:text-text-sub-600',
          // open
          'group-data-[state=open]/accordion:hidden',
          className,
        )}
        {...rest}
      />
      <CloseIcon
        className={cnExt(
          'size-5 text-text-sub-600',
          // close
          'hidden group-data-[state=open]/accordion:block',
          className,
        )}
        {...rest}
      />
    </>
  );
}
AccordionArrow.displayName = ACCORDION_ARROW_NAME;
 
const AccordionContent = React.forwardRef<
  React.ComponentRef<typeof AccordionPrimitive.Content>,
  React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Content>
>(({ children, className, ...rest }, forwardedRef) => {
  return (
    <AccordionPrimitive.Content
      ref={forwardedRef}
      className='overflow-hidden data-[state=closed]:animate-accordion-up data-[state=open]:animate-accordion-down'
      {...rest}
    >
      <div
        className={cnExt(
          'pt-1.5 text-paragraph-sm text-text-sub-600',
          className,
        )}
      >
        {children}
      </div>
    </AccordionPrimitive.Content>
  );
});
AccordionContent.displayName = ACCORDION_CONTENT_NAME;
 
export {
  AccordionRoot as Root,
  AccordionHeader as Header,
  AccordionItem as Item,
  AccordionTrigger as Trigger,
  AccordionIcon as Icon,
  AccordionArrow as Arrow,
  AccordionContent as Content,
};

Update the import paths to match your project setup.

Examples

Arrow Position

API Reference

This component is based on the Radix UI Accordion primitives. Refer to their documentation for the API reference.

Accordion.Root

This component is based on the Accordion.Root primitive.

Accordion.Item

This component is based on the Accordion.Item primitive.

Accordion.Header

This component is based on the Accordion.Header primitive.

Accordion.Trigger

This component is based on the Accordion.Trigger primitive.

Accordion.Content

This component is based on the Accordion.Content primitive.

Accordion.Icon

This component is polymorphic, allowing you to change the underlying HTML element using the as prop.

PropTypeDefault
as
React.ElementType
div

Accordion.Arrow

The Accordion.Arrow component displays an icon that toggles between an open and closed state based on the accordion's state.

PropTypeDefault
openIcon
React.ElementType
RiAddLine
closeIcon
React.ElementType
RiSubtractLine
© 2024 AlignUI Design System. All rights reserved.