Accordion

A vertically stacked set of interactive headings that each reveal an associated section of content.

import type { PropsOf } from '@builder.io/qwik';
import { component$, useStyles$ } from '@builder.io/qwik';
import { Accordion } from 'qwik-primitives';
import styles from './styles.css?inline';

const ChevronLeftIcon = component$<PropsOf<'svg'>>((props) => {
  return (
    <svg
      aria-hidden="true"
      focusable="false"
      width="15"
      height="15"
      viewBox="0 0 15 15"
      fill="none"
      xmlns="http://www.w3.org/2000/svg"
      {...props}
    >
      <path
        d="M8.84182 3.13514C9.04327 3.32401 9.05348 3.64042 8.86462 3.84188L5.43521 7.49991L8.86462 11.1579C9.05348 11.3594 9.04327 11.6758 8.84182 11.8647C8.64036 12.0535 8.32394 12.0433 8.13508 11.8419L4.38508 7.84188C4.20477 7.64955 4.20477 7.35027 4.38508 7.15794L8.13508 3.15794C8.32394 2.95648 8.64036 2.94628 8.84182 3.13514Z"
        fill="currentColor"
        fill-rule="evenodd"
        clip-rule="evenodd"
      ></path>
    </svg>
  );
});

export const AccordionDemo = component$(() => {
  useStyles$(styles);

  return (
    <Accordion.Root type="single" defaultValue="item-1" class="accordion-root">
      <Accordion.Item value="item-1" class="accordion-root-item">
        <Accordion.ItemHeader class="accordion-item-header">
          <Accordion.ItemTrigger class="accordion-item-trigger">
            <span class="accordion-item-trigger-title">Is it accessible?</span>
            <Accordion.ItemIndicator class="accordion-item-trigger-indicator">
              <ChevronLeftIcon height={16} width={16} class="accordion-item-trigger-indicator" />
            </Accordion.ItemIndicator>
          </Accordion.ItemTrigger>
        </Accordion.ItemHeader>
        <Accordion.ItemPanel class="accordion-item-panel">
          <Accordion.ItemContent>
            <div class="accordion-item-content-inner">
              <p class="accordion-item-content-text">Yes. It adheres to the WAI-ARIA design pattern.</p>
            </div>
          </Accordion.ItemContent>
        </Accordion.ItemPanel>
      </Accordion.Item>

      <Accordion.Item value="item-2" class="accordion-root-item">
        <Accordion.ItemHeader class="accordion-item-header">
          <Accordion.ItemTrigger class="accordion-item-trigger">
            <span class="accordion-item-trigger-title">Is it unstyled?</span>
            <Accordion.ItemIndicator class="accordion-item-trigger-indicator">
              <ChevronLeftIcon height={16} width={16} class="accordion-item-trigger-indicator" />
            </Accordion.ItemIndicator>
          </Accordion.ItemTrigger>
        </Accordion.ItemHeader>
        <Accordion.ItemPanel class="accordion-item-panel">
          <Accordion.ItemContent>
            <div class="accordion-item-content-inner">
              <p class="accordion-item-content-text">
                Yes. It's unstyled by default, giving you freedom over the look and feel.
              </p>
            </div>
          </Accordion.ItemContent>
        </Accordion.ItemPanel>
      </Accordion.Item>

      <Accordion.Item value="item-3" class="accordion-root-item">
        <Accordion.ItemHeader class="accordion-item-header">
          <Accordion.ItemTrigger class="accordion-item-trigger">
            <span class="accordion-item-trigger-title">Can it be animated?</span>
            <Accordion.ItemIndicator class="accordion-item-trigger-indicator">
              <ChevronLeftIcon height={16} width={16} class="accordion-item-trigger-indicator" />
            </Accordion.ItemIndicator>
          </Accordion.ItemTrigger>
        </Accordion.ItemHeader>
        <Accordion.ItemPanel class="accordion-item-panel">
          <Accordion.ItemContent>
            <div class="accordion-item-content-inner">
              <p class="accordion-item-content-text">Yes! You can animate the Accordion with CSS.</p>
            </div>
          </Accordion.ItemContent>
        </Accordion.ItemPanel>
      </Accordion.Item>
    </Accordion.Root>
  );
});

Features

Import

import { Accordion } from 'qwik-primitives';

Anatomy

Import the component and piece the parts together.

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

export const AccordionDemo = component$(() => {
  return (
    <Accordion.Root>
      <Accordion.Item>
        <Accordion.ItemHeader>
          <Accordion.ItemTrigger>
            <Accordion.ItemIndicator />
          </Accordion.ItemTrigger>
        </Accordion.ItemHeader>
        <Accordion.ItemPanel>
          <Accordion.ItemContent />
        </Accordion.ItemPanel>
      </Accordion.Item>
    </Accordion.Root>
  );
});

API Reference

Root

Contains all the parts of an accordion. This component is based on the div element.

Props

as
Description

Change the default rendered element for the one passed as, merging their props and behavior.

Read our Composition guide for more details.

Type
FunctionComponent
Default
No default value
type
Description

Determines whether one or multiple items can be opened at the same time.

Type
enum
Default
No default value
defaultValue
Description

The value of the item to expand when initially rendered and type is "single". Use when you do not need to control the state of the items.

Type
string
Default
No default value
value
Description

The controlled value of the item to expand when type is "single". Must be used in conjunction with onValueChange$.

Type
Signal
Default
No default value
onValueChange$
Description

Event handler called when the expanded state of an item changes and type is "single".

Type
QRL
Default
No default value
defaultValue
Description

The value of the item to expand when initially rendered and type is "multiple". Use when you do not need to control the state of the items.

Type
string[]
Default
No default value
value
Description

The controlled value of the item to expand when type is "multiple". Must be used in conjunction with onValueChange$.

Type
Signal
Default
No default value
onValueChange$
Description

Event handler called when the expanded state of an item changes and type is "multiple".

Type
QRL
Default
No default value
collapsible
Description

When type is "single", allows closing content when clicking trigger for an open item.

Type
boolean
Default
No default value
disabled
Description

When true, prevents the user from interacting with the accordion and all its items.

Type
boolean
Default
No default value
style
Description

The inline style for the element.

Type
CSSProperties
Default
No default value

Data attributes

[data-scope]
Values
"accordion"
[data-part]
Values
"root"
[data-disabled]
Values

Present when disabled

Item

Contains all the parts of a collapsible section. This component is based on the div element.

Props

as
Description

Change the default rendered element for the one passed as, merging their props and behavior.

Read our Composition guide for more details.

Type
FunctionComponent
Default
No default value
value
Description

A unique value for the item.

Type
string
Default
No default value
disabled
Description

When true, prevents the user from interacting with the item.

Type
boolean
Default
No default value
style
Description

The inline style for the element.

Type
CSSProperties
Default
No default value

Data attributes

[data-scope]
Values
"accordion"
[data-part]
Values
"item"
[data-state]
Values
"open" | "closed"
[data-disabled]
Values

Present when disabled

ItemHeader

Wraps an Accordion.ItemTrigger. Use the level prop to update it to the appropriate heading level for your page. This component is based on the h3 element.

Props

as
Description

Change the default rendered element for the one passed as, merging their props and behavior.

Read our Composition guide for more details.

Type
FunctionComponent
Default
No default value
level
Description

The level of the heading, determines which tag will be used (h1-h6).

Type
enum
Default
"3"
style
Description

The inline style for the element.

Type
CSSProperties
Default
No default value

Data attributes

[data-scope]
Values
"accordion"
[data-part]
Values
"item-header"
[data-state]
Values
"open" | "closed"
[data-level]
Values
"1" | "2" | "3" | "4" | "5" | "6"
[data-disabled]
Values

Present when disabled

ItemTrigger

Toggles the collapsed state of its associated item. It should be nested inside of an Accordion.ItemHeader. This component is based on the button element.

Props

as
Description

Change the default rendered element for the one passed as, merging their props and behavior.

Read our Composition guide for more details.

Type
FunctionComponent
Default
No default value
style
Description

The inline style for the element.

Type
CSSProperties
Default
No default value

Data attributes

[data-scope]
Values
"accordion"
[data-part]
Values
"item-trigger"
[data-state]
Values
"open" | "closed"
[data-disabled]
Values

Present when disabled

ItemIndicator

An optional decorative element that indicates the state of the accordion item. It should be nested inside of an Accordion.ItemTrigger. This component is based on the span element.

Props

as
Description

Change the default rendered element for the one passed as, merging their props and behavior.

Read our Composition guide for more details.

Type
FunctionComponent
Default
No default value
style
Description

The inline style for the element.

Type
CSSProperties
Default
No default value

Data attributes

[data-scope]
Values
"accordion"
[data-part]
Values
"item-indicator"
[data-state]
Values
"open" | "closed"
[data-disabled]
Values

Present when disabled

ItemPanel

The panel that expands/collapses. This component is based on the div element.

Props

as
Description

Change the default rendered element for the one passed as, merging their props and behavior.

Read our Composition guide for more details.

Type
FunctionComponent
Default
No default value
onOpen$
Description

Event handler called when the panel is fully open. If you animate the size of the panel when it opens this event handler was call after animation end.

Type
QRL
Default
No default value
onClose$
Description

Event handler called when the panel is fully close. If you animate the size of the panel when it closes this event handler was call after animation end.

Type
QRL
Default
No default value
style
Description

The inline style for the element.

Type
CSSProperties
Default
No default value

Data attributes

[data-scope]
Values
"accordion"
[data-part]
Values
"item-panel"
[data-state]
Values
"open" | "closed"
[data-disabled]
Values

Present when disabled

ItemContent

The component that contains the collapsible content for an item. Must be nested inside Accordion.ItemPanel. This component is based on the div element.

Props

as
Description

Change the default rendered element for the one passed as, merging their props and behavior.

Read our Composition guide for more details.

Type
FunctionComponent
Default
No default value
style
Description

The inline style for the element.

Type
CSSProperties
Default
No default value

Data attributes

[data-scope]
Values
"accordion"
[data-part]
Values
"item-content"
[data-state]
Values
"open" | "closed"
[data-disabled]
Values

Present when disabled

Examples

Expanded by default

Use the defaultValue prop to define the open item by default.

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

export const AccordionDemo = component$(() => {
  return (
    <Accordion.Root type="single" defaultValue="item-1">
      <Accordion.Item value="item-1">
        <Accordion.ItemHeader>
          <Accordion.ItemTrigger>Is it accessible?</Accordion.ItemTrigger>
        </Accordion.ItemHeader>
        <Accordion.ItemPanel>
          <Accordion.ItemContent>Yes. It adheres to the WAI-ARIA design pattern.</Accordion.ItemContent>
        </Accordion.ItemPanel>
      </Accordion.Item>

      <Accordion.Item value="item-2">
        <Accordion.ItemHeader>
          <Accordion.ItemTrigger>Is it unstyled?</Accordion.ItemTrigger>
        </Accordion.ItemHeader>
        <Accordion.ItemPanel>
          <Accordion.ItemContent>Yes. It's unstyled by default.</Accordion.ItemContent>
        </Accordion.ItemPanel>
      </Accordion.Item>

      <Accordion.Item value="item-3">
        <Accordion.ItemHeader>
          <Accordion.ItemTrigger>Can it be animated?</Accordion.ItemTrigger>
        </Accordion.ItemHeader>
        <Accordion.ItemPanel>
          <Accordion.ItemContent>Yes! You can animate the Accordion with CSS.</Accordion.ItemContent>
        </Accordion.ItemPanel>
      </Accordion.Item>
    </Accordion.Root>
  );
});

Allow collapsing all items

Use the collapsible prop to allow all items to close.

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

export const AccordionDemo = component$(() => {
  return (
    <Accordion.Root type="single" collapsible={true}>
      <Accordion.Item value="item-1">
        <Accordion.ItemHeader>
          <Accordion.ItemTrigger>Is it accessible?</Accordion.ItemTrigger>
        </Accordion.ItemHeader>
        <Accordion.ItemPanel>
          <Accordion.ItemContent>Yes. It adheres to the WAI-ARIA design pattern.</Accordion.ItemContent>
        </Accordion.ItemPanel>
      </Accordion.Item>

      <Accordion.Item value="item-2">
        <Accordion.ItemHeader>
          <Accordion.ItemTrigger>Is it unstyled?</Accordion.ItemTrigger>
        </Accordion.ItemHeader>
        <Accordion.ItemPanel>
          <Accordion.ItemContent>Yes. It's unstyled by default.</Accordion.ItemContent>
        </Accordion.ItemPanel>
      </Accordion.Item>

      <Accordion.Item value="item-3">
        <Accordion.ItemHeader>
          <Accordion.ItemTrigger>Can it be animated?</Accordion.ItemTrigger>
        </Accordion.ItemHeader>
        <Accordion.ItemPanel>
          <Accordion.ItemContent>Yes! You can animate the Accordion with CSS.</Accordion.ItemContent>
        </Accordion.ItemPanel>
      </Accordion.Item>
    </Accordion.Root>
  );
});

Multiple items open at the same time

Set the type prop to "multiple" to enable opening multiple items at once.

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

export const AccordionDemo = component$(() => {
  return (
    <Accordion.Root type="multiple">
      <Accordion.Item value="item-1">
        <Accordion.ItemHeader>
          <Accordion.ItemTrigger>Is it accessible?</Accordion.ItemTrigger>
        </Accordion.ItemHeader>
        <Accordion.ItemPanel>
          <Accordion.ItemContent>Yes. It adheres to the WAI-ARIA design pattern.</Accordion.ItemContent>
        </Accordion.ItemPanel>
      </Accordion.Item>

      <Accordion.Item value="item-2">
        <Accordion.ItemHeader>
          <Accordion.ItemTrigger>Is it unstyled?</Accordion.ItemTrigger>
        </Accordion.ItemHeader>
        <Accordion.ItemPanel>
          <Accordion.ItemContent>Yes. It's unstyled by default.</Accordion.ItemContent>
        </Accordion.ItemPanel>
      </Accordion.Item>

      <Accordion.Item value="item-3">
        <Accordion.ItemHeader>
          <Accordion.ItemTrigger>Can it be animated?</Accordion.ItemTrigger>
        </Accordion.ItemHeader>
        <Accordion.ItemPanel>
          <Accordion.ItemContent>Yes! You can animate the Accordion with CSS.</Accordion.ItemContent>
        </Accordion.ItemPanel>
      </Accordion.Item>
    </Accordion.Root>
  );
});

Rotated icon when item open

You can nest inside of an Accordion.ItemIndicator extra decorative elements, such as chevrons, and rotate it when the item is open.

import type { PropsOf } from '@builder.io/qwik';
import { component$, useStyles$ } from '@builder.io/qwik';
import { Accordion } from 'qwik-primitives';
import styles from './styles.css?inline';

const ChevronLeftIcon = component$<PropsOf<'svg'>>((props) => {
  return (
    <svg
      aria-hidden="true"
      focusable="false"
      width="15"
      height="15"
      viewBox="0 0 15 15"
      fill="none"
      xmlns="http://www.w3.org/2000/svg"
      {...props}
    >
      <path
        d="M8.84182 3.13514C9.04327 3.32401 9.05348 3.64042 8.86462 3.84188L5.43521 7.49991L8.86462 11.1579C9.05348 11.3594 9.04327 11.6758 8.84182 11.8647C8.64036 12.0535 8.32394 12.0433 8.13508 11.8419L4.38508 7.84188C4.20477 7.64955 4.20477 7.35027 4.38508 7.15794L8.13508 3.15794C8.32394 2.95648 8.64036 2.94628 8.84182 3.13514Z"
        fill="currentColor"
        fill-rule="evenodd"
        clip-rule="evenodd"
      ></path>
    </svg>
  );
});

export const AccordionDemo = component$(() => {
  useStyles$(styles);

  return (
    <Accordion.Root type="single">
      <Accordion.Item value="item-1">
        <Accordion.ItemHeader>
          <Accordion.ItemTrigger>
            Is it accessible?
            <Accordion.ItemIndicator class="accordion-item-indicator">
              <ChevronLeftIcon class="accordion-item-indicator-icon" />
            </Accordion.ItemIndicator>
          </Accordion.ItemTrigger>
        </Accordion.ItemHeader>
        <Accordion.ItemPanel>
          <Accordion.ItemContent>Yes. It adheres to the WAI-ARIA design pattern.</Accordion.ItemContent>
        </Accordion.ItemPanel>
      </Accordion.Item>

      <Accordion.Item value="item-2">
        <Accordion.ItemHeader>
          <Accordion.ItemTrigger>
            Is it unstyled?
            <Accordion.ItemIndicator class="accordion-item-indicator">
              <ChevronLeftIcon class="accordion-item-indicator-icon" />
            </Accordion.ItemIndicator>
          </Accordion.ItemTrigger>
        </Accordion.ItemHeader>
        <Accordion.ItemPanel>
          <Accordion.ItemContent>Yes. It's unstyled by default.</Accordion.ItemContent>
        </Accordion.ItemPanel>
      </Accordion.Item>

      <Accordion.Item value="item-3">
        <Accordion.ItemHeader>
          <Accordion.ItemTrigger>
            Can it be animated?
            <Accordion.ItemIndicator class="accordion-item-indicator">
              <ChevronLeftIcon class="accordion-item-indicator-icon" />
            </Accordion.ItemIndicator>
          </Accordion.ItemTrigger>
        </Accordion.ItemHeader>
        <Accordion.ItemPanel>
          <Accordion.ItemContent>Yes! You can animate the Accordion with CSS or JavaScript.</Accordion.ItemContent>
        </Accordion.ItemPanel>
      </Accordion.Item>
    </Accordion.Root>
  );
});

Animating item panel size

Use the grid-template-rows CSS property with 0fr or 1fr value to animate the size of the item panel when it opens/closes.

import { component$, useStyles$ } from '@builder.io/qwik';
import { Accordion } from 'qwik-primitives';
import styles from './styles.css?inline';

export const AccordionDemo = component$(() => {
  useStyles$(styles);

  return (
    <Accordion.Root type="single">
      <Accordion.Item value="item-1">
        <Accordion.ItemHeader>
          <Accordion.ItemTrigger>Is it accessible?</Accordion.ItemTrigger>
        </Accordion.ItemHeader>
        <Accordion.ItemPanel class="accordion-item-panel">
          <Accordion.ItemContent>Yes. It adheres to the WAI-ARIA design pattern.</Accordion.ItemContent>
        </Accordion.ItemPanel>
      </Accordion.Item>

      <Accordion.Item value="item-2">
        <Accordion.ItemHeader>
          <Accordion.ItemTrigger>Is it unstyled?</Accordion.ItemTrigger>
        </Accordion.ItemHeader>
        <Accordion.ItemPanel class="accordion-item-panel">
          <Accordion.ItemContent>Yes. It's unstyled by default.</Accordion.ItemContent>
        </Accordion.ItemPanel>
      </Accordion.Item>

      <Accordion.Item value="item-3">
        <Accordion.ItemHeader>
          <Accordion.ItemTrigger>Can it be animated?</Accordion.ItemTrigger>
        </Accordion.ItemHeader>
        <Accordion.ItemPanel class="accordion-item-panel">
          <Accordion.ItemContent>Yes! You can animate the Accordion with CSS or JavaScript.</Accordion.ItemContent>
        </Accordion.ItemPanel>
      </Accordion.Item>
    </Accordion.Root>
  );
});

Accessibility

Adheres to the Accordion WAI-ARIA design pattern.

Keyboard Interactions

Space
Description

When focus is on an Accordion.ItemTrigger of a collapsed section, expands the section.

Enter
Description

When focus is on an Accordion.ItemTrigger of a collapsed section, expands the section.

Tab
Description

Moves focus to the next focusable element.

Shift + Tab
Description

Moves focus to the previous focusable element.

ArrowDown
Description

When focus is on an Accordion.ItemTrigger, moves focus to the next enabled Accordion.ItemTrigger.

ArrowUp
Description

When focus is on an Accordion.ItemTrigger, moves focus to the previous enabled Accordion.ItemTrigger.

Home
Description

When focus is on an Accordion.ItemTrigger, moves focus to the first enabled Accordion.ItemTrigger.

End
Description

When focus is on an Accordion.ItemTrigger, moves focus to the last enabled Accordion.ItemTrigger.