JGuang

Accordion

Accordion · Mantine Core

Preview
npm install @mantine/core@9.7.1 @mantine/hooks@9.7.1

Accordion

Install: npm install @mantine/core@9.7.1 @mantine/hooks@9.7.1

Version and compatibility

Pinned Mantine Core 9.7.1 requires React and React DOM ^19.2.0 and matching @mantine/hooks@9.7.1, from the installed npm package's peerDependencies. Do not force it into React 18 or another incompatible version. Explain upgrade impact and agree on a plan with the project owner; do not hide conflicts with --force/--legacy-peer-deps. The native Provider enables theme.respectReducedMotion and uses forceColorScheme for light/dark.

Use case

Reveal related content in collapsible sections.

Native props used by this example

These props are extracted from the local typechecked JSX and grouped by their owning native component. This is not an exhaustive prop list; see pinned source signatures and inheritance below.

Component propExplanation
Accordion.styleLocal style/layout object.
Accordion.defaultValueInitial uncontrolled value, not a live controlled update.
Accordion.Item.valueControlled value; the native type defines its shape.

Interaction and state checks

Current scenarios: Default. Check actual behavior after selecting supported disabled/error/read-only scenarios; components without those native states do not receive invented universal props. Verify keyboard focus and exits, then connect changed values through the native callback signature.

Business integration boundary

Demo content/state is local, without server persistence, authorization or fetching. A static display component is not a completed business workflow.

Host setup

Install pinned @mantine/core and @mantine/hooks, import @mantine/core/styles.css at the entry and use MantineProvider. The example's theme/variant are JGuang-authored host props, not universal native component APIs; forceColorScheme synchronizes the app theme. Native implementation source is a reading reference: use npm exports rather than copying internal engines. This batch covers Core, not dates/charts/forms extension packages. Detailed upstream prose remains English.

Runnable usage

import { useState } from 'react';
import * as M from '@mantine/core';
import '@mantine/core/styles.css';


function ComponentPreview({ variant = 'Default' }: { variant?: string }) {
const [value, setValue] = useState(35);
const content = <M.Text>Build something beautiful / 构建美好的界面</M.Text>;
return <M.Accordion style={{ width: 360 }} defaultValue="design"><M.Accordion.Item value="design"><M.Accordion.Control>Design system / 设计系统</M.Accordion.Control><M.Accordion.Panel>{content}</M.Accordion.Panel></M.Accordion.Item></M.Accordion>;
}
export default function Example({ variant = 'Default', theme = 'light' }: { variant?: string; theme?: 'light' | 'dark' }) {
return <M.MantineProvider forceColorScheme={theme} theme={{ respectReducedMotion: true }}><ComponentPreview variant={variant} /></M.MantineProvider>;
}

Preview states

Default

The example uses the native Mantine API. For supported input demos, change the local variant to Disabled, Invalid or Read only; these are example labels, not new component props. Keep the documented data, controlled values, stores and compound children when adapting the example.

Usage

Accordion allows users to expand and collapse sections of content. It helps manage large amounts of information in a limited space by showing only section headers initially and revealing content upon interaction.

Accordion is commonly used for:

  • FAQ sections: displaying questions as headers with answers revealed when clicked
  • Forms: organizing long forms into sections, for example, personal info, shipping, and payment
  • Menus: nested navigation in sidebars or mobile views

order prop

The order prop (used in all demos on this page) sets the heading level for the Accordion.Control root element. According to WAI-ARIA recommendations, it is required to use h2-h6 heading levels to fit correctly within the outline of the page.

All examples on this page use order={3}, which means that all button elements of Accordion.Control are wrapped with an h3 tag (h2 tags are used for the documentation sections).

The order prop is not enforced by the library but is required if your application must meet accessibility standards.

Change chevron

Use the chevron prop to change the chevron icon. When chevron is set, the chevronIconSize prop is ignored. To remove the chevron icon, use chevron={null}.

To customize chevron styles, use Styles API with the data-rotate attribute. This attribute is set when the item is opened if the disableChevronRotation prop is not set.

Example of a custom chevron icon with rotation styles:

Custom control label

You can use any React node as a label for Accordion.Control component. When you use nested elements in Accordion.Control, it is recommended to set aria-label attribute to make the control accessible for screen readers.

With icons

Use the icon prop to display any element in the left section of the Accordion.Control:

Change transition

To change transition duration, set transitionDuration prop:

To disable transitions, set transitionDuration to 0:

Default opened items

For multiple={false}, set defaultValue as string:

import { Accordion } from '@mantine/core';

function Demo() {
  // Second item will be opened by default
  return (
    <Accordion defaultValue="item-2">
      <Accordion.Item value="item-1">{/* item-1 */}</Accordion.Item>
      <Accordion.Item value="item-2">{/* item-2 */}</Accordion.Item>
    </Accordion>
  );
}

For multiple={true}, set defaultValue as an array of strings:

import { Accordion } from '@mantine/core';

function Demo() {
  // Both items are opened by default
  return (
    <Accordion multiple defaultValue={['item-1', 'item-2']}>
      <Accordion.Item value="item-1">{/* item-1 */}</Accordion.Item>
      <Accordion.Item value="item-2">{/* item-2 */}</Accordion.Item>
    </Accordion>
  );
}

Control opened state

For multiple={false}, set value as string:

import { useState } from 'react';
import { Accordion } from '@mantine/core';

function Demo() {
  const [value, setValue] = useState<string | null>(null);

  return (
    <Accordion value={value} onChange={setValue}>
      <Accordion.Item value="item-1">{/* item-1 */}</Accordion.Item>
      <Accordion.Item value="item-2">{/* item-2 */}</Accordion.Item>
    </Accordion>
  );
}

For multiple={true}, set value as an array of strings:

import { useState } from 'react';
import { Accordion } from '@mantine/core';

function Demo() {
  const [value, setValue] = useState<string[]>([]);

  return (
    <Accordion multiple value={value} onChange={setValue}>
      <Accordion.Item value="item-1">{/* item-1 */}</Accordion.Item>
      <Accordion.Item value="item-2">{/* item-2 */}</Accordion.Item>
    </Accordion>
  );
}

Disable collapse

By default, in single mode (multiple={false}) the open item can always be toggled closed, which leaves the Accordion fully collapsed. Set the disableCollapse prop to prevent this: once an item is open, clicking its control again is a no-op, and the only way to change state is to open a different item. This is useful for settings panels, stepper-style flows and FAQ pages where one section should always stay visible.

disableCollapse only prevents collapsing an already-open item – it does not force an item open on mount. To guarantee that one item is open from the start, pair it with defaultValue (uncontrolled) or value (controlled).

The prop has no effect when multiple is set, as multiple mode already allows any combination of opened and closed items.

Compose controls

Putting a button or link inside Accordion.Control is a common mistake when using Accordion. The Accordion.Control root element is a button. Putting interactive elements inside other interactive elements is forbidden – you will receive a DOM validation error from React if you try to implement the following component:

import { Accordion } from '@mantine/core';

// ❌ Incorrect usage: do not do this
function Demo() {
  return (
    <Accordion.Item value="item-1">
      <Accordion.Control>
        <Group>
          <span>Control 1</span>
          <button>My action</button>
        </Group>
      </Accordion.Control>
      <Accordion.Panel>Panel 1</Accordion.Panel>
    </Accordion.Item>
  );
}

Instead of putting interactive elements inside the Accordion.Control, render them next to it. For example, you can add an ActionIcon or Menu to the right side of the original control. If you need to display an interactive element over the Accordion.Control, use position: absolute instead.

Disabled items

Set the disabled prop on the Accordion.Control component to disable it. When you disable items, users cannot activate them with the mouse or keyboard, and arrow key navigation will skip them:

Unstyled Accordion

Set the unstyled prop on the Accordion component to remove all non-essential library styles. Use the unstyled prop to style the component with the Styles API without overriding any styles.

Example of using Styles API to customize Accordion styles:

TypeScript

The AccordionProps type exported from @mantine/core is a generic that accepts a boolean type describing the multiple state:

import type { AccordionProps } from '@mantine/core';

type MultipleAccordionProps = AccordionProps<true>;
type DefaultAccordionProps = AccordionProps<false>;

Accessibility

The Accordion component implements the WAI-ARIA accessibility pattern.

Keyboard interactions

Source-backed parameter reference

Implementation signatures and types are generated from the pinned local source. Optional props, native inherited types, union branches and never exclusions are preserved. A forwardRef implementation's ref is a React reference, not an additional business prop.

Accordion(_props)
export type AccordionStylesNames =
  | 'root'
  | 'content'
  | 'item'
  | 'panel'
  | 'icon'
  | 'chevron'
  | 'label'
  | 'itemTitle'
  | 'control';

export type AccordionVariant = 'default' | 'contained' | 'filled' | 'separated';

export type AccordionCssVariables = {
  root: '--accordion-transition-duration' | '--accordion-chevron-size' | '--accordion-radius';
};

export interface AccordionProps<Multiple extends boolean = false>
  extends
    BoxProps,
    StylesApiProps<AccordionFactory>,
    ElementProps<'div', 'value' | 'defaultValue' | 'onChange'> {
  /** If set, multiple items can be opened at the same time @default false */
  multiple?: Multiple;

  /** Controlled component value */
  value?: AccordionValue<Multiple>;

  /** Uncontrolled component default value */
  defaultValue?: AccordionValue<Multiple>;

  /** Called when value changes, payload type depends on `multiple` prop */
  onChange?: (value: AccordionValue<Multiple>) => void;

  /** If set, arrow keys loop through items (first to last and last to first) @default true */
  loop?: boolean;

  /** Transition duration in ms @default 200 */
  transitionDuration?: number;

  /** If set, chevron rotation is disabled @default false */
  disableChevronRotation?: boolean;

  /** If set, the open item cannot be collapsed by clicking it again, so one item always stays open. Only applies when `multiple` is `false`. @default false */
  disableCollapse?: boolean;

  /** Position of the chevron relative to the item label @default right */
  chevronPosition?: AccordionChevronPosition;

  /** Size of the chevron icon container @default auto */
  chevronSize?: number | string;

  /** Size of the default chevron icon. Ignored when `chevron` prop is set. Use `chevronSize` instead when using custom chevron. @default 16 */
  chevronIconSize?: number | string;

  /** Sets heading level (h2-h6) for `Accordion.Control` elements. Wraps each control in the corresponding heading tag, recommended to meet WAI-ARIA accessibility requirements. Has no visual effect. */
  order?: AccordionHeadingOrder;

  /** Custom chevron icon */
  chevron?: React.ReactNode;

  /** Key of `theme.radius` or any valid CSS value to set border-radius. Numbers are converted to rem. @default theme.defaultRadius */
  radius?: MantineRadius;

  /** If set to `false`, panels are unmounted when collapsed. By default, panels stay mounted when collapsed. @default true */
  keepMounted?: boolean;

  /** Controls how inactive panels content is hidden when `keepMounted` is `true`, `'activity'` – hidden with `Activity` component, `'display-none'` – hidden with `display: none` styles @default 'activity' */
  keepMountedMode?: 'activity' | 'display-none';
}

export type AccordionFactory = Factory<{
  props: AccordionProps;
  ref: HTMLDivElement;
  stylesNames: AccordionStylesNames;
  vars: AccordionCssVariables;
  variant: AccordionVariant;
  signature: <Multiple extends boolean = false>(
    props: AccordionProps<Multiple>
  ) => React.JSX.Element;
  staticComponents: {
    Item: typeof AccordionItem;
    Panel: typeof AccordionPanel;
    Control: typeof AccordionControl;
    Chevron: typeof AccordionChevron;
  };
}>;

export type Mod = Record<string, any> | string;

export type BoxMod = Mod | Mod[] | BoxMod[];

export interface BoxProps extends MantineStyleProps {
  /** Class added to the root element, if applicable */
  className?: string | undefined;

  /** Inline style added to root component element, can subscribe to theme defined on MantineProvider */
  style?: MantineStyleProp;

  /** CSS variables defined on root component element */
  __vars?: CssVarsProp;

  /** `size` property passed down the HTML element */
  __size?: string;

  /** Breakpoint above which the component is hidden with `display: none` */
  hiddenFrom?: MantineBreakpoint;

  /** Breakpoint below which the component is hidden with `display: none` */
  visibleFrom?: MantineBreakpoint;

  /** Determines whether component should be hidden in light color scheme with `display: none` */
  lightHidden?: boolean;

  /** Determines whether component should be hidden in dark color scheme with `display: none` */
  darkHidden?: boolean;

  /** Element modifiers transformed into `data-` attributes, for example, `{ 'data-size': 'xl' }`, falsy values are removed */
  mod?: BoxMod;
}

export type ElementProps<
  ElementType extends React.ElementType,
  PropsToOmit extends string = never,
> = Omit<React.ComponentProps<ElementType>, 'style' | PropsToOmit>;

export interface CSSProperties extends React.CSSProperties {
  [key: string]: any;
}

type MantineStyle = CSSProperties | ((theme: MantineTheme) => CSSProperties);

export type MantineStyleProp = MantineStyle | MantineStyle[] | MantineStyleProp[] | undefined;

export type CssVariable = `--${string}`;

export type CssVariables<Variable extends string = CssVariable> = Partial<Record<Variable, string>>;

export type CssVars<Variable extends string = CssVariable> =
  | CssVariables<Variable>
  | ((theme: MantineTheme) => CssVariables<Variable>)
  | CssVars<Variable>[];

export type CssVarsProp<Variable extends string = CssVariable> =
  | CssVars<Variable>
  | CssVars<Variable>[];

Native and external types

External types remain defined by React or native dependencies, not manually expanded into guessed props. The installed version's TypeScript definitions are authoritative.

Factory<{
  props: AccordionProps;
  ref: HTMLDivElement;
  stylesNames: AccordionStylesNames;
  vars: AccordionCssVariables;
  variant: AccordionVariant;
  signature: <Multiple extends boolean = false>(
    props: AccordionProps<Multiple>
  ) => React.JSX.Element;
  staticComponents: {
    Item: typeof AccordionItem;
    Panel: typeof AccordionPanel;
    Control: typeof AccordionControl;
    Chevron: typeof AccordionChevron;
  };
}>
MantineStyleProps
React.CSSProperties
MantineTheme
Partial<Record<Variable, string>>
Record<Variable, string>
Variable
MantineBreakpoint
Record<string, any>
StylesApiProps<AccordionFactory>
React.ElementType
Omit<React.ComponentProps<ElementType>, 'style' | PropsToOmit>
React.ComponentProps<ElementType>
ElementType
PropsToOmit
Multiple
AccordionValue<Multiple>
AccordionChevronPosition
AccordionHeadingOrder
React.ReactNode
MantineRadius
HTMLDivElement
React.JSX.Element