shadimage
uniquel
componentaccordion-chevronsource: ok · hit

Accordion Chevron

Minimal accordion with a subtly animated chevron for compact FAQs and supporting details.

npx shadcn add "https://uniquel.io/r/accordion-chevron.json"
free

Visual style

minimal

Interaction

accordion

Tags auto-generated by rules on 9/24/2026 — empty means no evidence, not lack of support.

Dependencies

Bundle weight · light
iconslucide-react

Files1 · source cached 10/15/2026, 4:15:14 AM

components/content/AccordionChevron.tsx
'use client';

import { ChevronDown } from 'lucide-react';
import { useId, useState } from 'react';

type AccordionChevronItem = {
  title: string;
  content: string;
};

type AccordionChevronProps = {
  items?: AccordionChevronItem[];
  defaultOpenIndex?: number | null;
  className?: string;
};

const DEFAULT_ITEMS: AccordionChevronItem[] = [
  {
    title: 'How does it work?',
    content: 'Use the accordion to keep related details close without making the page feel crowded.',
  },
  {
    title: 'Can I change the content?',
    content: 'Pass your own titles and descriptions through the items prop.',
  },
  {
    title: 'Does it work on mobile?',
    content: 'Yes. Each trigger has a comfortable touch target and supports keyboard navigation.',
  },
];

export default function AccordionChevron({
  items = DEFAULT_ITEMS,
  defaultOpenIndex = 0,
  className = '',
}: AccordionChevronProps) {
  const id = useId();
  const [openIndex, setOpenIndex] = useState(defaultOpenIndex);

  return (
    <div className={['w-full max-w-xl divide-y divide-[color:var(--color-border,#e5e7eb)] border-y border-[color:var(--color-border,#e5e7eb)]', className].filter(Boolean).join(' ')}>
      {items.map((item, index) => {
        const open = openIndex === index;
        const triggerId = `${id}-trigger-${index}`;
        const panelId = `${id}-panel-${index}`;

        return (
          <div key={item.title}>
            <button
              id={triggerId}
              type='button'
              aria-expanded={open}
              aria-controls={panelId}
              onClick={() => setOpenIndex(open ? null : index)}
              className='group flex w-full items-center justify-between gap-6 py-4 text-left text-sm font-medium text-[color:var(--color-foreground,#111827)]'
            >
              {item.title}
              <ChevronDown
                aria-hidden='true'
                className={['h-4 w-4 shrink-0 text-[color:var(--color-muted-foreground,#9ca3af)] transition-transform duration-400 ease-out', open ? 'rotate-180' : 'group-hover:translate-y-0.5'].join(' ')}
              />
            </button>
            <div
              id={panelId}
              role='region'
              aria-labelledby={triggerId}
              className={['grid transition-[grid-template-rows,opacity] duration-200 ease-out', open ? 'grid-rows-[1fr] opacity-100' : 'grid-rows-[0fr] opacity-0'].join(' ')}
            >
              <div className='overflow-hidden'>
                <p className='pb-4 text-sm leading-6 text-[color:var(--color-muted-foreground,#6b7280)]'>{item.content}</p>
              </div>
            </div>
          </div>
        );
      })}
    </div>
  );
}

More from uniquel

Browse all components in this library →