A collapsible content panel for organizing information in a compact space
Import
import { Accordion } from "heroui-solid";Usage
import {
ArrowsRotateLeft,
Box,
ChevronDown,
CreditCard,
PlanetEarth,
Receipt,
ShoppingBag
} from "gravity-icons-solid"
import { Accordion } from "heroui-solid"
import { For, type JSX } from "solid-js"
const items: Array<{ content: string; icon: JSX.Element; title: string }> = [
{
content:
"Browse our products, add items to your cart, and proceed to checkout. You'll need to provide shipping and payment information to complete your purchase.",
icon: <ShoppingBag />,
title: "How do I place an order?"
},
{
content:
"Yes, you can modify or cancel your order before it's shipped. Once your order is processed, you can't make changes.",
icon: <Receipt />,
title: "Can I modify or cancel my order?"
},
{
content:
"We accept all major credit cards, including Visa, Mastercard, and American Express.",
icon: <CreditCard />,
title: "What payment methods do you accept?"
},
{
content:
"Shipping costs vary based on your location and the size of your order. We offer free shipping for orders over $50.",
icon: <Box />,
title: "How much does shipping cost?"
},
{
content:
"Yes, we ship to most countries. Please check our shipping rates and policies for more information.",
icon: <PlanetEarth />,
title: "Do you ship internationally?"
},
{
content:
"If you're not satisfied with your purchase, you can request a refund within 30 days of purchase. Please contact our customer support team for assistance.",
icon: <ArrowsRotateLeft />,
title: "How do I request a refund?"
}
]
export function Basic() {
return (
<Accordion class="w-full max-w-md">
<For each={items}>
{(item) => (
<Accordion.Item>
<Accordion.Heading>
<Accordion.Trigger>
{item.icon ? (
<span class="mr-3 size-4 shrink-0 text-muted">
{item.icon}
</span>
) : null}
{item.title}
<Accordion.Indicator>
<ChevronDown />
</Accordion.Indicator>
</Accordion.Trigger>
</Accordion.Heading>
<Accordion.Panel>
<Accordion.Body>{item.content}</Accordion.Body>
</Accordion.Panel>
</Accordion.Item>
)}
</For>
</Accordion>
)
}Anatomy
Import the Accordion component and access all parts using dot notation.
import { Accordion } from "heroui-solid";
export default () => (
<Accordion>
<Accordion.Item>
<Accordion.Heading>
<Accordion.Trigger>
<Accordion.Indicator />
</Accordion.Trigger>
</Accordion.Heading>
<Accordion.Panel>
<Accordion.Body />
</Accordion.Panel>
</Accordion.Item>
</Accordion>
);Surface
import {
ArrowsRotateLeft,
Box,
ChevronDown,
CreditCard,
PlanetEarth,
Receipt,
ShoppingBag
} from "gravity-icons-solid"
import { Accordion } from "heroui-solid"
import { For, type JSX } from "solid-js"
const items: Array<{ content: string; icon: JSX.Element; title: string }> = [
{
content:
"Browse our products, add items to your cart, and proceed to checkout. You'll need to provide shipping and payment information to complete your purchase.",
icon: <ShoppingBag />,
title: "How do I place an order?"
},
{
content:
"Yes, you can modify or cancel your order before it's shipped. Once your order is processed, you can't make changes.",
icon: <Receipt />,
title: "Can I modify or cancel my order?"
},
{
content:
"We accept all major credit cards, including Visa, Mastercard, and American Express.",
icon: <CreditCard />,
title: "What payment methods do you accept?"
},
{
content:
"Shipping costs vary based on your location and the size of your order. We offer free shipping for orders over $50.",
icon: <Box />,
title: "How much does shipping cost?"
},
{
content:
"Yes, we ship to most countries. Please check our shipping rates and policies for more information.",
icon: <PlanetEarth />,
title: "Do you ship internationally?"
},
{
content:
"If you're not satisfied with your purchase, you can request a refund within 30 days of purchase. Please contact our customer support team for assistance.",
icon: <ArrowsRotateLeft />,
title: "How do I request a refund?"
}
]
export function Surface() {
return (
<Accordion class="w-full max-w-md" variant="surface">
<For each={items}>
{(item) => (
<Accordion.Item>
<Accordion.Heading>
<Accordion.Trigger>
{item.icon ? (
<span class="mr-3 size-4 shrink-0 text-muted">
{item.icon}
</span>
) : null}
{item.title}
<Accordion.Indicator>
<ChevronDown />
</Accordion.Indicator>
</Accordion.Trigger>
</Accordion.Heading>
<Accordion.Panel>
<Accordion.Body>{item.content}</Accordion.Body>
</Accordion.Panel>
</Accordion.Item>
)}
</For>
</Accordion>
)
}Multiple Expanded
import { Accordion } from "heroui-solid"
export function Multiple() {
return (
<Accordion allowsMultipleExpanded class="w-full max-w-md">
<Accordion.Item>
<Accordion.Heading>
<Accordion.Trigger>
Getting Started
<Accordion.Indicator />
</Accordion.Trigger>
</Accordion.Heading>
<Accordion.Panel>
<Accordion.Body>
Learn the basics of HeroUI and how to integrate it into your React
project. This section covers installation, setup, and your first
component.
</Accordion.Body>
</Accordion.Panel>
</Accordion.Item>
<Accordion.Item>
<Accordion.Heading>
<Accordion.Trigger>
Core Concepts
<Accordion.Indicator />
</Accordion.Trigger>
</Accordion.Heading>
<Accordion.Panel>
<Accordion.Body>
Understand the fundamental concepts behind HeroUI, including the
compound component pattern, styling with Tailwind CSS, and
accessibility features.
</Accordion.Body>
</Accordion.Panel>
</Accordion.Item>
<Accordion.Item>
<Accordion.Heading>
<Accordion.Trigger>
Advanced Usage
<Accordion.Indicator />
</Accordion.Trigger>
</Accordion.Heading>
<Accordion.Panel>
<Accordion.Body>
Explore advanced features like custom variants, theme customization,
and integration with other libraries in your React ecosystem.
</Accordion.Body>
</Accordion.Panel>
</Accordion.Item>
<Accordion.Item>
<Accordion.Heading>
<Accordion.Trigger>
Best Practices
<Accordion.Indicator />
</Accordion.Trigger>
</Accordion.Heading>
<Accordion.Panel>
<Accordion.Body>
Follow our recommended best practices for building performant,
accessible, and maintainable applications with HeroUI components.
</Accordion.Body>
</Accordion.Panel>
</Accordion.Item>
</Accordion>
)
}Controlled
Expanded: getting-started
import { ChevronDown, ChevronUp } from "gravity-icons-solid"
import {
Accordion,
Button,
type Key,
useDisclosureGroupNavigation
} from "heroui-solid"
import { createSignal, For } from "solid-js"
const items = [
{
content:
"Learn the basics of HeroUI and how to integrate it into your React project. This section covers installation, setup, and your first component.",
id: "getting-started",
title: "Getting Started"
},
{
content:
"Understand the fundamental concepts behind HeroUI, including the compound component pattern, styling with Tailwind CSS, and accessibility features.",
id: "core-concepts",
title: "Core Concepts"
},
{
content:
"Explore advanced features like custom variants, theme customization, and integration with other libraries in your React ecosystem.",
id: "advanced-usage",
title: "Advanced Usage"
}
]
export function Controlled() {
const [expandedKeys, setExpandedKeys] = createSignal<Set<Key>>(
new Set(["getting-started"])
)
const itemIds = items.map((item) => item.id)
const { isNextDisabled, isPrevDisabled, onNext, onPrevious } =
useDisclosureGroupNavigation({
expandedKeys,
itemIds,
onExpandedChange: setExpandedKeys
})
return (
<div class="w-full max-w-md">
<div class="mb-4 flex items-center justify-between">
<p class="text-sm text-muted">
Expanded: <strong>{[...expandedKeys()].join(", ") || "none"}</strong>
</p>
<div class="flex gap-2">
<Button
aria-label="Previous item"
isDisabled={isPrevDisabled()}
size="sm"
variant="secondary"
onClick={onPrevious}
>
<ChevronUp class="size-4" />
</Button>
<Button
aria-label="Next item"
isDisabled={isNextDisabled()}
size="sm"
variant="secondary"
onClick={onNext}
>
<ChevronDown class="size-4" />
</Button>
</div>
</div>
<Accordion
expandedKeys={expandedKeys()}
onExpandedChange={setExpandedKeys}
>
<For each={items}>
{(item) => (
<Accordion.Item id={item.id}>
<Accordion.Heading>
<Accordion.Trigger>
{item.title}
<Accordion.Indicator />
</Accordion.Trigger>
</Accordion.Heading>
<Accordion.Panel>
<Accordion.Body>{item.content}</Accordion.Body>
</Accordion.Panel>
</Accordion.Item>
)}
</For>
</Accordion>
</div>
)
}Custom Indicator
import {
ChevronsDown,
CircleChevronDown,
Minus,
Plus
} from "gravity-icons-solid"
import { Accordion, type Key } from "heroui-solid"
import { createSignal, Show } from "solid-js"
export function CustomIndicator() {
const [expandedKeys, setExpandedKeys] = createSignal<Set<Key>>(new Set([""]))
return (
<Accordion
class="w-full max-w-md"
expandedKeys={expandedKeys()}
variant="surface"
onExpandedChange={setExpandedKeys}
>
<Accordion.Item id="1">
<Accordion.Heading>
<Accordion.Trigger>
Using Plus/Minus Icon
<Accordion.Indicator>
<Show when={expandedKeys().has("1")} fallback={<Plus />}>
<Minus />
</Show>
</Accordion.Indicator>
</Accordion.Trigger>
</Accordion.Heading>
<Accordion.Panel>
<Accordion.Body>
This accordion uses a plus icon that transforms when expanded. The
icon automatically rotates 45 degrees to form an X.
</Accordion.Body>
</Accordion.Panel>
</Accordion.Item>
<Accordion.Item id="2">
<Accordion.Heading>
<Accordion.Trigger>
Using Caret Icon
<Accordion.Indicator>
<CircleChevronDown />
</Accordion.Indicator>
</Accordion.Trigger>
</Accordion.Heading>
<Accordion.Panel>
<Accordion.Body>
This item uses a caret icon for the indicator. The rotation
animation is applied automatically.
</Accordion.Body>
</Accordion.Panel>
</Accordion.Item>
<Accordion.Item id="3">
<Accordion.Heading>
<Accordion.Trigger>
Using Arrow Icon
<Accordion.Indicator>
<ChevronsDown />
</Accordion.Indicator>
</Accordion.Trigger>
</Accordion.Heading>
<Accordion.Panel>
<Accordion.Body>
This item uses an arrow icon. Any icon you pass will receive the
rotation animation when the item expands.
</Accordion.Body>
</Accordion.Panel>
</Accordion.Item>
</Accordion>
)
}Disabled State
Entire accordion disabled
Individual items disabled
import { Accordion } from "heroui-solid"
export function Disabled() {
return (
<div class="flex w-full flex-col items-center gap-8">
<div class="w-full max-w-md space-y-2">
<h3 class="text-sm font-medium text-muted">
Entire accordion disabled
</h3>
<Accordion isDisabled class="w-full max-w-md">
<Accordion.Item>
<Accordion.Heading>
<Accordion.Trigger>
Disabled Item 1
<Accordion.Indicator />
</Accordion.Trigger>
</Accordion.Heading>
<Accordion.Panel>
<Accordion.Body>
This content cannot be accessed when the accordion is disabled.
</Accordion.Body>
</Accordion.Panel>
</Accordion.Item>
<Accordion.Item>
<Accordion.Heading>
<Accordion.Trigger>
Disabled Item 2
<Accordion.Indicator />
</Accordion.Trigger>
</Accordion.Heading>
<Accordion.Panel>
<Accordion.Body>
This content cannot be accessed when the accordion is disabled.
</Accordion.Body>
</Accordion.Panel>
</Accordion.Item>
</Accordion>
</div>
<div class="w-full max-w-md space-y-2">
<h3 class="text-sm font-medium text-muted">
Individual items disabled
</h3>
<Accordion class="w-full max-w-md">
<Accordion.Item>
<Accordion.Heading>
<Accordion.Trigger>
Active Item
<Accordion.Indicator />
</Accordion.Trigger>
</Accordion.Heading>
<Accordion.Panel>
<Accordion.Body>
This item is active and can be toggled normally.
</Accordion.Body>
</Accordion.Panel>
</Accordion.Item>
<Accordion.Item isDisabled>
<Accordion.Heading>
<Accordion.Trigger>
Disabled Item
<Accordion.Indicator />
</Accordion.Trigger>
</Accordion.Heading>
<Accordion.Panel>
<Accordion.Body>
This content cannot be accessed when the item is disabled.
</Accordion.Body>
</Accordion.Panel>
</Accordion.Item>
<Accordion.Item>
<Accordion.Heading>
<Accordion.Trigger>
Another Active Item
<Accordion.Indicator />
</Accordion.Trigger>
</Accordion.Heading>
<Accordion.Panel>
<Accordion.Body>
This item is also active and can be toggled.
</Accordion.Body>
</Accordion.Panel>
</Accordion.Item>
</Accordion>
</div>
</div>
)
}FAQ Layout
Frequently Asked Questions
Everything you need to know about licensing and usage.
General
Licensing
Support
import { ChevronDown } from "gravity-icons-solid"
import { Accordion } from "heroui-solid"
import { For } from "solid-js"
export function FAQ() {
const categories = [
{
items: [
{
content:
"Browse our products, add items to your cart, and proceed to checkout. You'll need to provide shipping and payment information to complete your purchase.",
title: "How do I place an order?"
},
{
content:
"Yes, you can modify or cancel your order before it's shipped. Once your order is processed, you can't make changes.",
title: "Can I modify or cancel my order?"
}
],
title: "General"
},
{
items: [
{
content:
"You can purchase a license directly from our website. Select the license type that fits your needs and proceed to checkout.",
title: "How do I purchase a license?"
},
{
content:
"A standard license is for personal use or small projects, while a pro license includes commercial use rights and priority support.",
title: "What is the difference between a standard and a pro license?"
}
],
title: "Licensing"
},
{
items: [
{
content:
"You can reach our support team through the contact form on our website, or email us directly at support@example.com.",
title: "How do I get support?"
}
],
title: "Support"
}
]
return (
<div class="flex w-full flex-col gap-6">
<div class="flex flex-col gap-1">
<h2 class="text-2xl font-bold">Frequently Asked Questions</h2>
<p class="mb-4 text-lg font-medium text-muted">
Everything you need to know about licensing and usage.
</p>
</div>
<For each={categories}>
{(category) => (
<div>
<p class="text-md mb-2 font-medium text-muted">{category.title}</p>
<Accordion class="w-full" variant="surface">
<For each={category.items}>
{(item) => (
<Accordion.Item>
<Accordion.Heading>
<Accordion.Trigger>
{item.title}
<Accordion.Indicator>
<ChevronDown />
</Accordion.Indicator>
</Accordion.Trigger>
</Accordion.Heading>
<Accordion.Panel>
<Accordion.Body>{item.content}</Accordion.Body>
</Accordion.Panel>
</Accordion.Item>
)}
</For>
</Accordion>
</div>
)}
</For>
</div>
)
}Custom Styles
import { ChevronDown } from "gravity-icons-solid"
import { Accordion, cn } from "heroui-solid"
import { For } from "solid-js"
const items = [
{
content:
"Stay informed about your account activity with real-time notifications. ",
iconUrl:
"https://heroui-assets.nyc3.cdn.digitaloceanspaces.com/docs/3dicons/bell-small.png",
subtitle: "Receive account activity updates",
title: "Set Up Notifications"
},
{
content:
"Enhance your browsing experience by installing our official browser extension",
iconUrl:
"https://heroui-assets.nyc3.cdn.digitaloceanspaces.com/docs/3dicons/compass-small.png",
subtitle: "Connect you browser to your account",
title: "Set up Browser Extension"
},
{
content:
"Begin your journey into the world of digital collectibles by creating your first NFT. ",
iconUrl:
"https://heroui-assets.nyc3.cdn.digitaloceanspaces.com/docs/3dicons/mint-collective-small.png",
subtitle: "Create your first collectible",
title: "Mint Collectible"
}
]
export function CustomStyles() {
return (
<Accordion
class="bg-surface-1/10 w-full max-w-md rounded-2xl"
variant="surface"
>
<For each={items}>
{(item) => (
<Accordion.Item
class={cn(
"group/item",
"first:[&_[data-slot=accordion-trigger]]:rounded-t-2xl", // First trigger we want to round the top
"last:[&:not(:has([data-slot=accordion-trigger][aria-expanded='true']))_[data-slot=accordion-trigger]]:rounded-b-2xl" // Last trigger we want to round the bottom
)}
>
<Accordion.Heading>
<Accordion.Trigger class="hover:bgsurface group flex items-center gap-2 transition-none">
{item.iconUrl ? (
<img
alt={item.title}
class="h-11 w-11 transition-[scale,rotate] duration-300 ease-out group-hover/item:scale-120 group-hover/item:-rotate-10 group-hover/item:drop-shadow-lg"
src={item.iconUrl}
/>
) : null}
<div class="flex flex-col gap-0">
<span class="leading-5 font-medium">{item.title}</span>
<span class="leading-6 font-normal text-muted/80">
{item.subtitle}
</span>
</div>
<Accordion.Indicator class="text-muted/50 [&>svg]:size-4">
<ChevronDown />
</Accordion.Indicator>
</Accordion.Trigger>
</Accordion.Heading>
<Accordion.Panel>
<Accordion.Body class="text-muted/80">
{item.content}
</Accordion.Body>
</Accordion.Panel>
</Accordion.Item>
)}
</For>
</Accordion>
)
}Without Separator
import {
ChevronDown,
CreditCard,
Receipt,
ShoppingBag
} from "gravity-icons-solid"
import { Accordion } from "heroui-solid"
import { For, type JSX } from "solid-js"
const items: Array<{ content: string; icon: JSX.Element; title: string }> = [
{
content:
"Browse our products, add items to your cart, and proceed to checkout. You'll need to provide shipping and payment information to complete your purchase.",
icon: <ShoppingBag />,
title: "How do I place an order?"
},
{
content:
"Yes, you can modify or cancel your order before it's shipped. Once your order is processed, you can't make changes.",
icon: <Receipt />,
title: "Can I modify or cancel my order?"
},
{
content:
"We accept all major credit cards, including Visa, Mastercard, and American Express.",
icon: <CreditCard />,
title: "What payment methods do you accept?"
}
]
export function WithoutSeparator() {
return (
<Accordion hideSeparator class="w-full max-w-md">
<For each={items}>
{(item) => (
<Accordion.Item>
<Accordion.Heading>
<Accordion.Trigger>
{item.icon ? (
<span class="mr-3 size-4 shrink-0 text-muted">
{item.icon}
</span>
) : null}
{item.title}
<Accordion.Indicator>
<ChevronDown />
</Accordion.Indicator>
</Accordion.Trigger>
</Accordion.Heading>
<Accordion.Panel>
<Accordion.Body>{item.content}</Accordion.Body>
</Accordion.Panel>
</Accordion.Item>
)}
</For>
</Accordion>
)
}Custom Element
HeroUI React overrides the rendered elements with a render prop. This port
uses Kobalte's polymorphic as prop instead — for example, a section root with
article items:
import { Accordion } from "heroui-solid"
export function CustomElement() {
return (
<Accordion as="section" class="w-full max-w-md">
<Accordion.Item as="article">
<Accordion.Heading as="h4">
<Accordion.Trigger>
Custom Elements
<Accordion.Indicator />
</Accordion.Trigger>
</Accordion.Heading>
<Accordion.Panel>
<Accordion.Body>
This accordion renders a section root, an article item, and an h4
heading via Kobalte's polymorphic as prop.
</Accordion.Body>
</Accordion.Panel>
</Accordion.Item>
</Accordion>
)
}Related Components
- DisclosureGroup: Group of collapsible panels (not ported yet)
- Disclosure: Single collapsible content section (not ported yet)
Styling
Passing Tailwind CSS classes
import { Accordion, cn } from "heroui-solid";
function CustomAccordion() {
return (
<Accordion class="bg-surface-1/10 w-full max-w-md rounded-2xl" variant="surface">
<Accordion.Item class={cn("group/item", "first:[&_[data-slot=accordion-trigger]]:rounded-t-2xl")}>
...
</Accordion.Item>
</Accordion>
);
}Customizing the component classes
To customize the Accordion component classes, you can use the @layer components directive.
Learn more.
@layer components {
.accordion {
@apply rounded-xl bg-gray-50;
}
.accordion__trigger {
@apply font-semibold text-lg;
}
.accordion--outline {
@apply shadow-lg border-2;
}
}HeroUI follows the BEM methodology to ensure component variants and states are reusable and easy to customize.
CSS Classes
The Accordion component uses these CSS classes (View source styles):
Base Classes
.accordion- Base accordion container.accordion__body- Content body container.accordion__heading- Heading wrapper.accordion__indicator- Expand/collapse indicator icon.accordion__item- Individual accordion item.accordion__panel- Collapsible panel container.accordion__trigger- Clickable trigger button
Variant Classes
.accordion--surface- Surface variant with background and rounded corners
State Classes
.accordion__trigger[aria-expanded="true"]- Expanded state.accordion__panel[data-expanded="true"]- Panel visible state
Interactive States
The component supports both CSS pseudo-classes and data attributes for flexibility:
- Hover:
:hoveron trigger - Focus:
:focus-visibleon trigger - Disabled:
:disabledor[aria-disabled="true"]on trigger - Expanded:
[aria-expanded="true"]on trigger
API Reference
Accordion Props
| Prop | Type | Default | Description |
|---|---|---|---|
allowsMultipleExpanded | boolean | false | Whether multiple items can be expanded at once |
defaultExpandedKeys | Iterable<Key> | - | The initial expanded keys |
expandedKeys | Iterable<Key> | - | The controlled expanded keys |
onExpandedChange | (keys: Set<Key>) => void | - | Handler called when expanded keys change |
isDisabled | boolean | false | Whether the entire accordion is disabled |
variant | "default" | "surface" | "default" | The visual variant of the accordion |
hideSeparator | boolean | false | Hide separator lines between accordion items |
class | string | - | Additional CSS classes |
children | JSX.Element | - | The accordion items |
as | ValidComponent | "div" | Overrides the default DOM element (Kobalte polymorphic as) |
Accordion.Item Props
| Prop | Type | Default | Description |
|---|---|---|---|
id | Key | - | Unique identifier for the item |
isDisabled | boolean | false | Whether this item is disabled |
class | string | - | Additional CSS classes |
children | JSX.Element | - | The item content |
as | ValidComponent | "div" | Overrides the default DOM element (Kobalte polymorphic as) |
Accordion.Trigger Props
| Prop | Type | Default | Description |
|---|---|---|---|
class | string | - | Additional CSS classes |
children | JSX.Element | - | Trigger content |
onClick | JSX.EventHandlerUnion<HTMLElement, MouseEvent> | - | Additional click handler |
as | ValidComponent | "button" | Overrides the default DOM element (Kobalte polymorphic as) |
Accordion.Panel Props
| Prop | Type | Default | Description |
|---|---|---|---|
class | string | - | Additional CSS classes |
children | JSX.Element | - | Panel content |
as | ValidComponent | "div" | Overrides the default DOM element (Kobalte polymorphic as) |
Accordion.Indicator Props
| Prop | Type | Default | Description |
|---|---|---|---|
class | string | - | Additional CSS classes |
children | JSX.Element | - | Custom indicator icon |
Accordion.Body Props
| Prop | Type | Default | Description |
|---|---|---|---|
class | string | - | Additional CSS classes |
children | JSX.Element | - | Body content |
Differences from HeroUI React
- Use
onClick(Solid's native event) instead of React Aria'sonPress. - Polymorphism uses Kobalte's
asprop instead of React'srenderprop override. onExpandedChangereports keys as strings (numeric item ids are coerced).- A custom
Accordion.Indicatoricon renders inside aspanwrapper carrying the indicator class (Solid has nocloneElement), so selectors like[&>svg]:size-4target the icon as expected.
Last updated: 7/19/26, 3:27 AM