shadimage
roiui
itemaccordionsource: ok · hit

Accordion

A collapsible content component.

npx shadcn add "https://roiui.com/r/accordion.json"
free

Interaction

accordion

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

Files2 · source cached 10/9/2026, 8:56:27 AM

registry/brook/ui/accordion/accordion.tsx
"use client";

import { Accordion } from "@base-ui/react/accordion";
import { cn } from "../../../lib/utils";
import styles from "./accordion.module.css";

function AccordionRoot({ className, ...props }: Accordion.Root.Props) {
  return <Accordion.Root className={cn(styles.root, className)} data-slot="accordion-root" {...props} />;
}

function AccordionItem({ className, ...props }: Accordion.Item.Props) {
  return <Accordion.Item className={cn(styles.item, className)} data-slot="accordion-item" {...props} />;
}

function AccordionHeader({ className, ...props }: Accordion.Header.Props) {
  return <Accordion.Header className={cn(styles.header, className)} data-slot="accordion-header" {...props} />;
}

function AccordionTrigger({ className, children, ...props }: Accordion.Trigger.Props) {
  return (
    <Accordion.Trigger className={cn(styles.trigger, className)} data-slot="accordion-trigger" {...props}>
      <div className={styles.icon}>
        <svg aria-label="Accordion toggle icon" fill="none" height="18" role="img" viewBox="0 0 20 20" width="18">
          <title>Toggle accordion</title>
          <path
            className={styles.horizontalLine}
            d="M4 10L16 10"
            stroke="currentColor"
            strokeLinecap="round"
            strokeWidth="2"
          />
          <path d="M10 4L10 16" stroke="currentColor" strokeLinecap="round" strokeWidth="2" />
        </svg>
      </div>
      <div>{children}</div>
    </Accordion.Trigger>
  );
}

function AccordionPanel({ className, children, ...props }: Accordion.Panel.Props) {
  return (
    <Accordion.Panel className={cn(styles.panel, className)} data-slot="accordion-panel" {...props}>
      <div className={styles.content}>
        <div className={styles.contentInner}>{children}</div>
      </div>
    </Accordion.Panel>
  );
}

export { AccordionRoot as Accordion, AccordionHeader, AccordionItem, AccordionPanel, AccordionRoot, AccordionTrigger };
registry/brook/ui/accordion/accordion.module.css
.root {
  width: 32rem;
  max-width: calc(100vw - 8rem);
  display: flex;
  flex-direction: column;
}

.item {
  overflow: hidden;
  background-color: var(--mix-card-75-bg);
  padding: 1rem;
  border-bottom: 0.5px solid var(--border);
  transition:
    margin 200ms var(--ease-in-out-quad),
    border-radius 200ms var(--ease-in-out-quad),
    border-color 200ms var(--ease-in-out-quad);

  &:first-child {
    border-radius: 1.5rem 1.5rem 0 0;
  }

  &:last-child {
    border-radius: 0 0 1.5rem 1.5rem;
    border-bottom-color: transparent;
  }

  &:first-child:last-child {
    border-radius: 1.5rem;
  }

  &[data-open] {
    border-radius: 1.5rem;
    margin-top: 0.5rem;
    margin-bottom: 0.5rem;
    border-bottom-color: transparent;

    &:first-child {
      margin-top: 0;
    }

    &:last-child {
      margin-bottom: 0;
    }
  }

  &:has(+ .item[data-open]):not([data-open]) {
    border-radius: 0 0 1.5rem 1.5rem;
    border-bottom-color: transparent;

    &:first-child {
      border-radius: 1.5rem;
    }
  }

  &[data-open] + &:not([data-open]) {
    border-radius: 1.5rem 1.5rem 0 0;

    &:last-child {
      border-radius: 1.5rem;
    }
  }

  &[data-open] + &:not([data-open]):has(+ .item[data-open]) {
    border-radius: 1.5rem;
    border-bottom-color: transparent;
  }
}

.header {
  margin: 0;
}

.icon {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 2rem;
  height: 2rem;
  color: var(--muted-foreground);
  transition:
    color 200ms ease,
    rotate 150ms var(--ease-in-out-quad);
  rotate: 0deg;
}

.horizontalLine {
  transition:
    opacity 150ms var(--ease-in-out-quad),
    scale 150ms var(--ease-in-out-quad);
  transform-origin: center;
}

.trigger {
  display: flex;
  width: 100%;
  align-items: center;
  gap: 1rem;

  font-size: 1rem;
  line-height: 1.5;
  letter-spacing: -0.02em;
  font-weight: 350;
  text-align: left;
  background: transparent;
  border: none;
  cursor: pointer;
  color: var(--secondary-foreground);

  &[data-panel-open] .icon {
    rotate: 90deg;
  }

  &:hover .icon {
    color: var(--foreground);
  }

  &[data-panel-open] .horizontalLine {
    opacity: 0;
    scale: 0;
  }
}

.panel {
  height: var(--accordion-panel-height);
  overflow: hidden;
  transition: height 150ms var(--ease-in-out-quad);

  &[data-starting-style],
  &[data-ending-style] {
    height: 0;
  }
}

.content {
  overflow: hidden;
  width: 100%;
}

.contentInner {
  margin-left: calc(3rem);
  padding-right: 3rem;
  font-size: 0.875rem;
  line-height: 1.6;
  color: oklch(from var(--foreground) l c h / 0.75);
}

@media (max-width: 640px) {
  .root {
    width: 100%;
    max-width: 100%;
  }

  .item {
    padding: 1rem;
  }

  .trigger {
    gap: 0.75rem;
  }

  .icon {
    width: 1.75rem;
    height: 1.75rem;
    flex-shrink: 0;

    & svg {
      width: 16px;
      height: 16px;
    }
  }

  .contentInner {
    margin-left: 2.5rem;
    padding-right: 0;
    font-size: 0.9375rem;
    line-height: 1.5;
  }
}

More from roiui

Browse all components in this library →