Skip to contents

Accordion

A set of collapsible panels with headings.

import type { JSX } from '@solidjs/web';
import { Accordion } from 'base-ui-solid/accordion';
import styles from './index.module.css';

export default function ExampleAccordion() {
  return (
    <Accordion.Root class={styles.Accordion}>
      <Accordion.Item class={styles.Item}>
        <Accordion.Header class={styles.Header}>
          <Accordion.Trigger class={styles.Trigger}>
            What is Base UI?
            <PlusIcon class={styles.Icon} />
          </Accordion.Trigger>
        </Accordion.Header>
        <Accordion.Panel class={styles.Panel}>
          <div class={styles.Content}>
            Base UI is a library of high-quality unstyled React components for design systems and
            web apps.
          </div>
        </Accordion.Panel>
      </Accordion.Item>

      <Accordion.Item class={styles.Item}>
        <Accordion.Header class={styles.Header}>
          <Accordion.Trigger class={styles.Trigger}>
            How do I get started?
            <PlusIcon class={styles.Icon} />
          </Accordion.Trigger>
        </Accordion.Header>
        <Accordion.Panel class={styles.Panel}>
          <div class={styles.Content}>
            Head to the “Quick start” guide in the docs. If you’ve used unstyled libraries before,
            you’ll feel at home.
          </div>
        </Accordion.Panel>
      </Accordion.Item>

      <Accordion.Item class={styles.Item}>
        <Accordion.Header class={styles.Header}>
          <Accordion.Trigger class={styles.Trigger}>
            Can I use it for my project?
            <PlusIcon class={styles.Icon} />
          </Accordion.Trigger>
        </Accordion.Header>
        <Accordion.Panel class={styles.Panel}>
          <div class={styles.Content}>Of course! Base UI is free and open source.</div>
        </Accordion.Panel>
      </Accordion.Item>
    </Accordion.Root>
  );
}

function PlusIcon(props: JSX.IntrinsicElements['svg']) {
  return (
    <svg
      width="16"
      height="16"
      viewBox="0 0 16 16"
      fill="none"
      stroke="currentColor"
      stroke-linecap="square"
      stroke-linejoin="round"
      {...props}
      style={
        typeof props.style === 'string'
          ? `display: block; ${props.style}`
          : { display: 'block', ...(typeof props.style === 'object' ? props.style : {}) }
      }
    >
      <path d="M1.5 8h13M8 14.5v-13" />
    </svg>
  );
}

Anatomy

Import the component and assemble its parts:

Anatomy
import { Accordion } from 'base-ui-solid/accordion';

<Accordion.Root>
  <Accordion.Item>
    <Accordion.Header>
      <Accordion.Trigger />
    </Accordion.Header>
    <Accordion.Panel />
  </Accordion.Item>
</Accordion.Root>;

Examples

Open multiple panels

You can set up the accordion to allow multiple panels to be open at the same time using the multiple prop.

import type { JSX } from '@solidjs/web';
import { Accordion } from 'base-ui-solid/accordion';
import styles from './index.module.css';

export default function ExampleAccordion() {
  return (
    <Accordion.Root class={styles.Accordion} multiple>
      <Accordion.Item class={styles.Item}>
        <Accordion.Header class={styles.Header}>
          <Accordion.Trigger class={styles.Trigger}>
            What is Base UI?
            <PlusIcon class={styles.Icon} />
          </Accordion.Trigger>
        </Accordion.Header>
        <Accordion.Panel class={styles.Panel}>
          <div class={styles.Content}>
            Base UI is a library of high-quality unstyled React components for design systems and
            web apps.
          </div>
        </Accordion.Panel>
      </Accordion.Item>

      <Accordion.Item class={styles.Item}>
        <Accordion.Header class={styles.Header}>
          <Accordion.Trigger class={styles.Trigger}>
            How do I get started?
            <PlusIcon class={styles.Icon} />
          </Accordion.Trigger>
        </Accordion.Header>
        <Accordion.Panel class={styles.Panel}>
          <div class={styles.Content}>
            Head to the “Quick start” guide in the docs. If you’ve used unstyled libraries before,
            you’ll feel at home.
          </div>
        </Accordion.Panel>
      </Accordion.Item>

      <Accordion.Item class={styles.Item}>
        <Accordion.Header class={styles.Header}>
          <Accordion.Trigger class={styles.Trigger}>
            Can I use it for my project?
            <PlusIcon class={styles.Icon} />
          </Accordion.Trigger>
        </Accordion.Header>
        <Accordion.Panel class={styles.Panel}>
          <div class={styles.Content}>Of course! Base UI is free and open source.</div>
        </Accordion.Panel>
      </Accordion.Item>
    </Accordion.Root>
  );
}

function PlusIcon(props: JSX.IntrinsicElements['svg']) {
  return (
    <svg
      width="16"
      height="16"
      viewBox="0 0 16 16"
      fill="none"
      stroke="currentColor"
      stroke-linecap="square"
      stroke-linejoin="round"
      {...props}
      style={
        typeof props.style === 'string'
          ? `display: block; ${props.style}`
          : { display: 'block', ...(typeof props.style === 'object' ? props.style : {}) }
      }
    >
      <path d="M1.5 8h13M8 14.5v-13" />
    </svg>
  );
}

Hidden until found

The hiddenUntilFound prop hides closed panels with hidden="until-found" so the browser can search their contents and reveal the matching panel automatically. It can be set on each Accordion.Panel, or once on Accordion.Root to apply to all panels.

To try it, press Ctrl+F (Cmd+F on macOS) and search for “restocking”—the browser opens the closed panel containing the match. When hiddenUntilFound is enabled, closed panels always remain mounted in the DOM, which also makes their contents indexable by search engines.

Older browsers that don’t support hidden="until-found" keep panels hidden until their trigger opens them, and find-in-page skips over the contents.

import type { JSX } from '@solidjs/web';
import { Accordion } from 'base-ui-solid/accordion';
import styles from './index.module.css';

export default function ExampleAccordion() {
  return (
    <Accordion.Root class={styles.Accordion} hiddenUntilFound>
      <Accordion.Item class={styles.Item}>
        <Accordion.Header class={styles.Header}>
          <Accordion.Trigger class={styles.Trigger}>
            How long does shipping take?
            <PlusIcon class={styles.Icon} />
          </Accordion.Trigger>
        </Accordion.Header>
        <Accordion.Panel class={styles.Panel}>
          <div class={styles.Content}>
            Standard shipping takes 3–5 business days. Express delivery arrives in 1–2 business
            days.
          </div>
        </Accordion.Panel>
      </Accordion.Item>

      <Accordion.Item class={styles.Item}>
        <Accordion.Header class={styles.Header}>
          <Accordion.Trigger class={styles.Trigger}>
            What is your return policy?
            <PlusIcon class={styles.Icon} />
          </Accordion.Trigger>
        </Accordion.Header>
        <Accordion.Panel class={styles.Panel}>
          <div class={styles.Content}>
            You can return any item within 30 days of delivery. Opened items may be subject to a 10%
            restocking fee.
          </div>
        </Accordion.Panel>
      </Accordion.Item>

      <Accordion.Item class={styles.Item}>
        <Accordion.Header class={styles.Header}>
          <Accordion.Trigger class={styles.Trigger}>
            Do you ship internationally?
            <PlusIcon class={styles.Icon} />
          </Accordion.Trigger>
        </Accordion.Header>
        <Accordion.Panel class={styles.Panel}>
          <div class={styles.Content}>
            Yes, we ship to over 40 countries. International orders typically arrive within 7–14
            business days.
          </div>
        </Accordion.Panel>
      </Accordion.Item>

      <Accordion.Item class={styles.Item}>
        <Accordion.Header class={styles.Header}>
          <Accordion.Trigger class={styles.Trigger}>
            How can I track my order?
            <PlusIcon class={styles.Icon} />
          </Accordion.Trigger>
        </Accordion.Header>
        <Accordion.Panel class={styles.Panel}>
          <div class={styles.Content}>
            Once your order ships, you’ll receive a tracking link by email. Tracking updates can
            take up to 24 hours to appear.
          </div>
        </Accordion.Panel>
      </Accordion.Item>
    </Accordion.Root>
  );
}

function PlusIcon(props: JSX.IntrinsicElements['svg']) {
  return (
    <svg
      width="16"
      height="16"
      viewBox="0 0 16 16"
      fill="none"
      stroke="currentColor"
      stroke-linecap="square"
      stroke-linejoin="round"
      {...props}
      style={
        typeof props.style === 'string'
          ? `display: block; ${props.style}`
          : { display: 'block', ...(typeof props.style === 'object' ? props.style : {}) }
      }
    >
      <path d="M1.5 8h13M8 14.5v-13" />
    </svg>
  );
}

API reference

Root

Groups all parts of the accordion. Renders a <div> element.

Prop
Type
Default
defaultValueValue[]—
The uncontrolled value of the item(s) that should be initially expanded. To render a controlled accordion, use the value prop instead.Value[]
valueValue[]—
The controlled value of the item(s) that should be expanded. To render an uncontrolled accordion, use the defaultValue prop instead.Value[]
onValueChangefunction—
Event handler called when an accordion item is expanded or collapsed. Provides the new value as an argument.((value: Value[], eventDetails: Accordion.Root.ChangeEventDetails) => void)
hiddenUntilFoundbooleanfalse
Allows the browser’s built-in page search to find and expand the panel contents. Overrides the keepMounted prop and uses hidden="until-found" to hide the element without removing it from the DOM.boolean
loopFocusboolean—
Deprecated following the [APG guidance update](https://github.com/w3c/aria-practices/pull/3434) to remove roving focus. This prop no longer affects keyboard focus behavior.boolean
multiplebooleanfalse
Whether multiple items can be open at the same time.boolean
disabledbooleanfalse
Whether the component should ignore user interaction.boolean
orientationOrientation'vertical'
Deprecated following the [APG guidance update](https://github.com/w3c/aria-practices/pull/3434) to remove roving focus. This prop no longer affects keyboard focus behavior.Orientation
classfunction—
CSS class applied to the element, or a function that returns a class based on the component’s state.JSX.ClassValue | ((state) => JSX.ClassValue)
stylefunction—
Style applied to the element, or a function that returns a style object based on the component’s state.JSX.CSSProperties | string | ((state) => JSX.CSSProperties | string | undefined)
keepMountedbooleanfalse
Whether to keep the element in the DOM while the panel is closed. This prop is ignored when hiddenUntilFound is used.boolean
renderfunction—
Replace the default element with a tag name, component, or render function.keyof JSX.IntrinsicElements | Component | ((props, state) => JSX.Element)

Data attributes

Name
Type
Default
data-orientation-—
Indicates the orientation of the accordion.-
data-disabled-—
Present when the accordion is disabled.-
Attribute
Description
data-orientation
Indicates the orientation of the accordion.
data-disabled
Present when the accordion is disabled.
Root.State
type AccordionRootState<TValue = any> = {
  /**
   * The current value.
   * Treat it as read-only: it may be a shared frozen array when no value is set.
   */
  value: TValue[];
  /** Whether the component should ignore user interaction. */
  disabled: boolean;
  /**
   * The component orientation.
   *
   * Deprecated following the [APG guidance update](https://github.com/w3c/aria-practices/pull/3434)
   * to remove roving focus.
   *
   * This state no longer affects keyboard focus behavior.
   * @deprecated
   */
  orientation: Orientation;
};
Root.ChangeEventReason
type AccordionRootChangeEventReason = 'trigger-press' | 'none';
Root.ChangeEventDetails
type AccordionRootChangeEventDetails = (
  | { reason: 'trigger-press'; event: MouseEvent | PointerEvent | TouchEvent | KeyboardEvent }
  | { reason: 'none'; event: Event }
) & {
  /** Cancels Base UI from handling the event. */
  cancel: () => void;
  /** Allows the event to propagate in cases where Base UI will stop the propagation. */
  allowPropagation: () => void;
  /** Indicates whether the event has been canceled. */
  isCanceled: boolean;
  /** Indicates whether the event is allowed to propagate. */
  isPropagationAllowed: boolean;
  /** The element that triggered the event, if applicable. */
  trigger: Element | undefined;
};
Root.Value
type AccordionRootValue<TValue = any> = TValue[];

Item

Groups an accordion header with the corresponding panel. Renders a <div> element.

Prop
Type
Default
valueany—
A unique value that identifies this accordion item. If no value is provided, a unique ID will be generated automatically. Use when controlling the accordion programmatically, or to set an initial open state.any
onOpenChangefunction—
Event handler called when the panel is opened or closed.((open: boolean, eventDetails: Accordion.Item.ChangeEventDetails) => void)
disabledbooleanfalse
Whether the component should ignore user interaction.boolean
classfunction—
CSS class applied to the element, or a function that returns a class based on the component’s state.JSX.ClassValue | ((state) => JSX.ClassValue)
stylefunction—
Style applied to the element, or a function that returns a style object based on the component’s state.JSX.CSSProperties | string | ((state) => JSX.CSSProperties | string | undefined)
renderfunction—
Replace the default element with a tag name, component, or render function.keyof JSX.IntrinsicElements | Component | ((props, state) => JSX.Element)

Data attributes

Name
Type
Default
data-open-—
Present when the accordion item is open.-
data-disabled-—
Present when the accordion item is disabled.-
data-indexnumber—
Indicates the index of the accordion item.number
Attribute
Description
data-open
Present when the accordion item is open.
data-disabled
Present when the accordion item is disabled.
data-index
Indicates the index of the accordion item.
Item.State
type AccordionItemState = {
  /** Whether the accordion item's panel is currently hidden. */
  hidden: boolean;
  /** The item index. */
  index: number;
  /** Whether the component is open. */
  open: boolean;
  /**
   * The current value.
   * Treat it as read-only: it may be a shared frozen array when no value is set.
   */
  value: any[];
  /** Whether the component should ignore user interaction. */
  disabled: boolean;
  /**
   * The component orientation.
   *
   * Deprecated following the [APG guidance update](https://github.com/w3c/aria-practices/pull/3434)
   * to remove roving focus.
   *
   * This state no longer affects keyboard focus behavior.
   * @deprecated
   */
  orientation: Orientation;
};
Item.ChangeEventReason
type AccordionItemChangeEventReason = 'trigger-press' | 'none';
Item.ChangeEventDetails
type AccordionItemChangeEventDetails = (
  | { reason: 'trigger-press'; event: MouseEvent | PointerEvent | TouchEvent | KeyboardEvent }
  | { reason: 'none'; event: Event }
) & {
  /** Cancels Base UI from handling the event. */
  cancel: () => void;
  /** Allows the event to propagate in cases where Base UI will stop the propagation. */
  allowPropagation: () => void;
  /** Indicates whether the event has been canceled. */
  isCanceled: boolean;
  /** Indicates whether the event is allowed to propagate. */
  isPropagationAllowed: boolean;
  /** The element that triggered the event, if applicable. */
  trigger: Element | undefined;
};

A heading that labels the corresponding panel. Renders an <h3> element.

Prop
Type
Default
classfunction—
CSS class applied to the element, or a function that returns a class based on the component’s state.JSX.ClassValue | ((state) => JSX.ClassValue)
stylefunction—
Style applied to the element, or a function that returns a style object based on the component’s state.JSX.CSSProperties | string | ((state) => JSX.CSSProperties | string | undefined)
renderfunction—
Replace the default element with a tag name, component, or render function.keyof JSX.IntrinsicElements | Component | ((props, state) => JSX.Element)

Data attributes

Name
Type
Default
data-open-—
Present when the accordion item is open.-
data-disabled-—
Present when the accordion item is disabled.-
data-indexnumber—
Indicates the index of the accordion item.number
Attribute
Description
data-open
Present when the accordion item is open.
data-disabled
Present when the accordion item is disabled.
data-index
Indicates the index of the accordion item.
Header.State
type AccordionHeaderState = {
  /** Whether the accordion item's panel is currently hidden. */
  hidden: boolean;
  /** The item index. */
  index: number;
  /** Whether the component is open. */
  open: boolean;
  /**
   * The current value.
   * Treat it as read-only: it may be a shared frozen array when no value is set.
   */
  value: any[];
  /** Whether the component should ignore user interaction. */
  disabled: boolean;
  /**
   * The component orientation.
   *
   * Deprecated following the [APG guidance update](https://github.com/w3c/aria-practices/pull/3434)
   * to remove roving focus.
   *
   * This state no longer affects keyboard focus behavior.
   * @deprecated
   */
  orientation: Orientation;
};

Trigger

A button that opens and closes the corresponding panel. Renders a <button> element.

Prop
Type
Default
nativeButtonbooleantrue
Whether the component renders a native <button> element when replacing it via the render prop. Set to false if the rendered element is not a button (for example, <div>).boolean
classfunction—
CSS class applied to the element, or a function that returns a class based on the component’s state.JSX.ClassValue | ((state) => JSX.ClassValue)
stylefunction—
Style applied to the element, or a function that returns a style object based on the component’s state.JSX.CSSProperties | string | ((state) => JSX.CSSProperties | string | undefined)
renderfunction—
Replace the default element with a tag name, component, or render function.keyof JSX.IntrinsicElements | Component | ((props, state) => JSX.Element)

Data attributes

Name
Type
Default
data-panel-open-—
Present when the accordion panel is open.-
data-disabled-—
Present when the accordion item is disabled.-
data-indexnumber—
Indicates the index of the accordion item.number
Attribute
Description
data-panel-open
Present when the accordion panel is open.
data-disabled
Present when the accordion item is disabled.
data-index
Indicates the index of the accordion item.
Trigger.State
type AccordionTriggerState = {
  /** Whether the accordion item's panel is currently hidden. */
  hidden: boolean;
  /** The item index. */
  index: number;
  /** Whether the component is open. */
  open: boolean;
  /**
   * The current value.
   * Treat it as read-only: it may be a shared frozen array when no value is set.
   */
  value: any[];
  /** Whether the component should ignore user interaction. */
  disabled: boolean;
  /**
   * The component orientation.
   *
   * Deprecated following the [APG guidance update](https://github.com/w3c/aria-practices/pull/3434)
   * to remove roving focus.
   *
   * This state no longer affects keyboard focus behavior.
   * @deprecated
   */
  orientation: Orientation;
};

Panel

A collapsible panel with the accordion item contents. Renders a <div> element.

Prop
Type
Default
hiddenUntilFoundbooleanfalse
Allows the browser’s built-in page search to find and expand the panel contents. Overrides the keepMounted prop and uses hidden="until-found" to hide the element without removing it from the DOM.boolean
classfunction—
CSS class applied to the element, or a function that returns a class based on the component’s state.JSX.ClassValue | ((state) => JSX.ClassValue)
stylefunction—
Style applied to the element, or a function that returns a style object based on the component’s state.JSX.CSSProperties | string | ((state) => JSX.CSSProperties | string | undefined)
keepMountedbooleanfalse
Whether to keep the element in the DOM while the panel is closed. This prop is ignored when hiddenUntilFound is used.boolean
renderfunction—
Replace the default element with a tag name, component, or render function.keyof JSX.IntrinsicElements | Component | ((props, state) => JSX.Element)

Data attributes

Name
Type
Default
data-open-—
Present when the accordion panel is open.-
data-orientation-—
Indicates the orientation of the accordion.-
data-disabled-—
Present when the accordion item is disabled.-
data-indexnumber—
Indicates the index of the accordion item.number
data-starting-style-—
Present when the panel begins animating in.-
data-ending-style-—
Present when the panel is animating out.-
Attribute
Description
data-open
Present when the accordion panel is open.
data-orientation
Indicates the orientation of the accordion.
data-disabled
Present when the accordion item is disabled.
data-index
Indicates the index of the accordion item.
data-starting-style
Present when the panel begins animating in.
data-ending-style
Present when the panel is animating out.

CSS variables

Name
Type
Default
--accordion-panel-heightnumber—
The accordion panel’s height.number
--accordion-panel-widthnumber—
The accordion panel’s width.number
CSS Variable
Description
--accordion-panel-height
The accordion panel’s height.
--accordion-panel-width
The accordion panel’s width.
Panel.State
type AccordionPanelState = {
  /** The transition status of the component. */
  transitionStatus: TransitionStatus;
  /** Whether the accordion item's panel is currently hidden. */
  hidden: boolean;
  /** The item index. */
  index: number;
  /** Whether the component is open. */
  open: boolean;
  /**
   * The current value.
   * Treat it as read-only: it may be a shared frozen array when no value is set.
   */
  value: any[];
  /** Whether the component should ignore user interaction. */
  disabled: boolean;
  /**
   * The component orientation.
   *
   * Deprecated following the [APG guidance update](https://github.com/w3c/aria-practices/pull/3434)
   * to remove roving focus.
   *
   * This state no longer affects keyboard focus behavior.
   * @deprecated
   */
  orientation: Orientation;
};