Edit Profile

Dark Mode

Copy config

Copy and paste the following code into your global.css file to apply the styles.

DISCLAIMER: This component is in
Beta
status. That means that it is ready for production, but the API might change.

Accordion

A set of interactive sections that show or hide connected information.

✨ Features

  • Follows the WAI-Aria design pattern
  • Full keyboard navigation
  • Can open one or multiple items at a time
  • Supports initial and reactive values

Building blocks

import { component$ } from '@builder.io/qwik';
import { Accordion } from '@qwik-ui/headless';

export default component$(() => {
  return (
    <Accordion.Root>
      <Accordion.Item>
        <Accordion.Header>
          <Accordion.Trigger>Title</Accordion.Trigger>
        </Accordion.Header>
        <Accordion.Content>Content</Accordion.Content>
      </Accordion.Item>
    </Accordion.Root>
  );
});

🎨 Anatomy

ComponentDescription
Accordion.Root

The primary container for the accordion.

Accordion.Item

A single disclosure widget.

Accordion.Header

The heading element of an accordion item.

Accordion.Trigger

Activates to show or hide the accordion content.

Accordion.Content

Displays the content when its connected trigger is actived.

Why use a headless accordion?

While you can create a native disclosure with HTML elements like details and summary, there are some limitations and issues with them.

Native disclosure pain points

  • Cannot control multiple disclosures at once
  • Inconsistent accessible name computation
  • Unintuitive role announcements
  • Connected information is hard to find with AT

Qwik UI includes a headless Accordion component that uses ARIA and JavaScript to enhance accessibility and usability for managing multiple sections smoothly.

Component State

Initial value

To set a default or initial value on page load, use the value prop on the <Accordion.Root /> component.

Inside Content 2

The value prop on the <Accordion.Root> was set to item-2, which is the value of the second item. As a result, the second item is selected by default.

Reactive value

Pass reactive state by using the bind:value prop on the <Accordion.Root /> component.

Current open item: Not selected

Programmatic changes

You can also change the current expanded item values programmatically by updating the signal's value.

Handling selection changes

Listen to when a new item is selected by passing a callback function to the onChange$ prop.

Called change count: 0

Changed to: nothing

Multiple items

To allow multiple items to be open at the same time, set the multiple prop to true.

Non-collapsible

To disable collapsible behavior, set the collapsible prop to false.

This will prevent the accordion from collapsing when the user clicks on the trigger.

Disabled items

Items can be disabled by setting the disabled prop to true on the <Accordion.Item /> component.

Disabled Component

The component itself can also be disabled by setting the disabled prop to true on the <Accordion.Root /> component.

Advanced

Height Animation

To animate the Accordion content, the --qwikui-collapsible-content-height CSS variable in your keyframes.

@keyframes collapsible-open {
  0% {
    height: 0;
  }
  100% {
    height: var(--qwikui-collapsible-content-height);
  }
}

@keyframes collapsible-closed {
  0% {
    height: var(--qwikui-collapsible-content-height);
  }
  100% {
    height: 0;
  }
}

.collapsible-animation[data-open] {
  animation: 550ms cubic-bezier(0.87, 0, 0.13, 1) 0s 1 normal forwards collapsible-open;
}

.collapsible-animation[data-closed] {
  animation: 350ms cubic-bezier(0.87, 0, 0.13, 1) 0s 1 normal forwards collapsible-closed;
}

Why does padding or border break the animation?

Padding or border applied to Accordion.Content breaks our keyframe animation above. This is because the content height has changed.

To fix this, add a child element to the content, and set the padding or border on that element.

<Accordion.Content class="collapsible-animation collapsible-content">
  <p class="collapsible-content-outline">Content</p>
</Accordion.Content>

CSR

The Accordion automatically renders based on its environment. This means that it works for both server-side and client-side rendering.

Dynamic

You have custom control over how to render items in the Accordion, allowing for dynamic rendering of items.

Example CSS

.collapsible {
  min-width: 14rem;
}

.collapsible-trigger {
  width: 100%;
  border: 2px dotted hsla(var(--foreground) / 1);
  border-radius: calc(var(--border-radius) / 2);
  padding: 0.5rem;
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-top: -2px;
}

.collapsible-trigger[data-disabled] {
  opacity: 0.3;
}

.collapsible-trigger:hover {
  background-color: hsla(var(--primary) / 0.08);
}

.collapsible-trigger svg {
  width: 1.25rem;
  height: 1.25rem;
}

.collapsible-trigger[data-open] {
  border-bottom: none;
}

.collapsible-trigger[data-open] svg {
  transform: rotate(180deg);
}

.collapsible:not(:first-child) .collapsible-trigger {
  border-top: none;
}

.collapsible-content {
  width: 100%;
  font-weight: 500;
  background: hsla(var(--primary) / 0.2);
  border-radius: calc(var(--border-radius) / 2);
  max-width: var(--select-width);
  color: hsl(var(--foreground));
  overflow: hidden;

  /* offset the dotted border */
}

.collapsible-content-outline {
  padding: 0.5rem;
  border: 2px dotted hsla(var(--primary) / 1);
}

/* chevron transition */
.collapsible-transition {
  transition: transform 500ms ease;
}

@keyframes collapsible-open {
  0% {
    height: 0;
  }
  100% {
    height: var(--qwikui-collapsible-content-height);
  }
}

@keyframes collapsible-closed {
  0% {
    height: var(--qwikui-collapsible-content-height);
  }
  100% {
    height: 0;
  }
}

.collapsible-animation[data-open] {
  animation: 550ms cubic-bezier(0.87, 0, 0.13, 1) 0s 1 normal forwards collapsible-open;
}

.collapsible-animation[data-closed] {
  animation: 350ms cubic-bezier(0.87, 0, 0.13, 1) 0s 1 normal forwards collapsible-closed;
}

.dynamic-input {
  display: flex;
  gap: 1rem;
  justify-content: center;
}

.dynamic-input label {
  display: flex;
  flex-direction: column;
  text-align: center;
  margin-block: 0.5rem;
  align-items: center;
}

.dynamic-input input {
  margin-bottom: 0.5rem;
  width: 5rem;
  background: hsl(var(--accent));
  border: 2px dotted hsl(var(--foreground));
  padding-left: 0.5rem;
}

.dynamic-input .add input {
  background: hsla(var(--primary) / 0.2);
}

.dynamic-input .delete input {
  background: hsla(var(--accent) / 1);
}

.dynamic-buttons {
  display: flex;
  gap: 1rem;
  margin-block: 0.5rem;
}

Keyboard Interaction

Key

Description

Space
Expands the content of the focused section.
Enter
Expands the content of the focused section.
Tab
Moves focus to the next element.
Shift + Tab
Moves focus to the previous element.
ArrowDown
Moves focus to the next trigger.
ArrowUp
Moves focus to the previous trigger.
Home
Moves focus to the first trigger.
End
Moves focus to the last trigger.

API

Accordion.Root

PropTypeDescription
value
string

The initial selectedd item of the accordion.

bind:value
Signal

Reactive signal that controls the selected item.

onChange$
function

Called when the state changes.

disabled
boolean

Disables the entire accordion.

collapsible
boolean

Allows items to be collapsible.

multiple
boolean

Allows multiple items to be expanded.

Accordion.Item

PropTypeDescription
value
string

The value associated with the accordion item.

disabled
boolean

When true, the accordion item is disabled.

open
boolean

Opens the accordion item in multiple mode.