Accordion
Organizes related details into expandable sections without leaving the page.
Preview
Example code
Orders placed before 2 PM usually leave our studio the same business day. We will email tracking details as soon as the carrier scans it.
Playground
allowsMultipleExpandedAllows more than one item to remain expanded.
isDisabledDisables every disclosure in the group.
Usage
tsx
import {
Accordion,
AccordionContent,
AccordionItem,
AccordionTrigger,
} from "earthling-ui/accordion";
export function Example() {
return (
<>
<Accordion defaultExpandedKeys={["shipping"]}>
<AccordionItem id="shipping">
<AccordionTrigger>When will my order ship?</AccordionTrigger>
<AccordionContent>
Orders usually ship within one business day.
</AccordionContent>
</AccordionItem>
</Accordion>
</>
);
}
- Use concise trigger labels that describe the content revealed below them.
- Single expansion is the default; enable multiple sections only when comparing answers is useful.
- React Aria provides keyboard navigation and expanded-state semantics through the trigger buttons.
Installation
Start with the project setup for React and Tailwind CSS 4. Both paths use the same component and theme.
Import package
Own the source
bash
npm install earthling-uiUse the component import shown above. Package updates keep the implementation current.
API reference
Declarations from the current library build, including inherited primitive props. Playground controls above show selected options; they are not the complete API.
tsx
// Generated by dts-bundle-generator v9.5.1
export declare const Accordion: import("react").ForwardRefExoticComponent<Omit<import("react-aria-components").DisclosureGroupProps & import("react").RefAttributes<HTMLDivElement>, "ref"> & import("react").RefAttributes<HTMLDivElement>>;
export declare const AccordionItem: import("react").ForwardRefExoticComponent<Omit<import("react-aria-components").DisclosureProps & import("react").RefAttributes<HTMLDivElement>, "ref"> & import("react").RefAttributes<HTMLDivElement>>;
export declare const AccordionTrigger: import("react").ForwardRefExoticComponent<Omit<import("react-aria-components").ButtonProps & import("react").RefAttributes<HTMLButtonElement>, "ref"> & import("react").RefAttributes<HTMLButtonElement>>;
export declare const AccordionContent: import("react").ForwardRefExoticComponent<Omit<import("react-aria-components").DisclosurePanelProps & import("react").RefAttributes<HTMLDivElement>, "ref"> & import("react").RefAttributes<HTMLDivElement>>;
export {};Runtime dependencies: cnfast, react-aria-components.
Source
Use the eject command to copy this implementation with its required helpers and project-specific imports.
View Accordion implementation
tsx
"use client";
import {
forwardRef,
type ComponentPropsWithoutRef,
type ComponentRef,
} from "react";
import { cn } from "@/utils/cn";
import {
DisclosureGroup,
Disclosure,
DisclosurePanel,
Header,
Button,
composeRenderProps,
} from "react-aria-components";
// Accordion
const Accordion = forwardRef<
ComponentRef<typeof DisclosureGroup>,
ComponentPropsWithoutRef<typeof DisclosureGroup>
>(({ className, ...props }, ref) => (
<DisclosureGroup
ref={ref}
{...props}
className={composeRenderProps(className, (className) =>
cn("w-full", className),
)}
/>
));
Accordion.displayName = "Accordion";
// AccordionItem
const AccordionItem = forwardRef<
ComponentRef<typeof Disclosure>,
ComponentPropsWithoutRef<typeof Disclosure>
>(({ className, ...props }, ref) => (
<Disclosure
ref={ref}
className={composeRenderProps(className, (className) =>
cn("border-b last:border-b-0", className),
)}
{...props}
/>
));
AccordionItem.displayName = "AccordionItem";
// AccordionTrigger
const AccordionTrigger = forwardRef<
ComponentRef<typeof Button>,
ComponentPropsWithoutRef<typeof Button>
>(({ className, children, ...props }, ref) => (
<Header className="flex">
<Button
slot="trigger"
ref={ref}
className={composeRenderProps(className, (className) =>
cn(
"group flex flex-1 cursor-pointer items-center justify-between gap-3 rounded-sm py-4 text-left font-medium outline-none hover:underline focus-visible:ring-2 focus-visible:ring-outline",
className,
),
)}
{...props}
>
{typeof children === "function" ? (
children
) : (
<>
{children}
<i
aria-hidden="true"
className="icon-[lucide--chevron-down] shrink-0 transition-transform duration-200 ease-out group-aria-[expanded=true]:rotate-180 motion-reduce:transition-none"
/>
</>
)}
</Button>
</Header>
));
AccordionTrigger.displayName = "AccordionTrigger";
// AccordionContent
const AccordionContent = forwardRef<
ComponentRef<typeof DisclosurePanel>,
ComponentPropsWithoutRef<typeof DisclosurePanel>
>(({ className, children, ...props }, ref) => (
<DisclosurePanel
ref={ref}
className={composeRenderProps(className, (className) =>
cn(
"h-[var(--disclosure-panel-height)] overflow-hidden text-sm transition-[height] duration-200 ease-out motion-reduce:transition-none",
className,
),
)}
{...props}
>
<div className="pb-4 pt-0">{children}</div>
</DisclosurePanel>
));
AccordionContent.displayName = "AccordionContent";
export { Accordion, AccordionItem, AccordionTrigger, AccordionContent };