首页
/ Material UI Accordion 组件深度指南:展开/折叠面板的 API、源码实现与最佳实践

Material UI Accordion 组件深度指南:展开/折叠面板的 API、源码实现与最佳实践

2026-09-05 17:42:44作者:田桥桑Industrious

Material UI 的 Accordion(手风琴)组件用于在页面上以可展开/折叠的方式展示相关联的内容区块。本篇基于当前仓库的官方文档 docs/data/material/components/accordion/accordion.mdpackages/mui-material/src 下的真实源码编写,覆盖 Accordion 系列的完整 API、受控/非受控用法、转场定制、无障碍(WAI-ARIA)要求、DOM 结构解剖,以及基于源码验证的性能优化策略,帮助你在 React 项目中正确且高效地构建手风琴交互。

说明:Accordion 已不再出现在最新版 Material Design 官方指南中,但仓库文档明确承诺 Material UI 会继续维护该组件(见 accordion.md 中的提示),可以放心用于生产项目。

组件家族:四个互补组件的分工

Accordion 不是一个单体组件,而是由四个职责清晰的组件组合而成(文档 "Introduction" 一节):

  • Accordion:外层包装器,负责分组相关组件、管理展开状态;
  • AccordionSummary:头部包装器,点击时展开或折叠内容;
  • AccordionDetails:内容区包装器,承载折叠区内具体 UI;
  • AccordionActions:可选包装器,用于把一组按钮分组放在面板底部。

基础引入方式:

import Accordion from '@mui/material/Accordion';
import AccordionDetails from '@mui/material/AccordionDetails';
import AccordionSummary from '@mui/material/AccordionSummary';

从源码结构看,Accordion 组件通过解构 children 的第一个元素将其识别为 summary(见 Accordion.jsconst [summary, ...children] = React.Children.toArray(childrenProp);),并通过 PropTypes 校验强制要求"第一个子元素必须是合法的 React 元素,且不接受 Fragment"(见 Accordion.js)。这意味着 <Accordion> 内第一个子节点必须是 <AccordionSummary>,后续子节点渲染进内容区。

AccordionSummary 内部基于 ButtonBase 实现,点击时会先调用 Context 中的 toggle(event) 翻转展开状态,再触发用户传入的 onClick(见 AccordionSummary.js);它还会自动设置 aria-expanded 并禁用涟漪效果(focusRipple: false, disableRipple: true,见 AccordionSummary.js),因为手风琴头部本身是一个"看起来像标题"的按钮,不需要按钮的默认视觉反馈。

AccordionDetails 的实现则非常薄——只是一个带 padding: theme.spacing(1, 2, 2)div(见 AccordionDetails.js),所有间距与视觉样式都交给用户通过 sx 或 theme 定制。

完整的最简用法可参考仓库中的演示文件 AccordionUsage.js

基础用法

展开指示图标(expandIcon)

通过 AccordionSummaryexpandIcon prop 更换展开指示图标。图标"翻转 180°"的动画由组件自动处理:从源码可见 expandIconWrapper 插槽默认 transform: 'rotate(0deg)',展开态追加 .expanded 类后变为 rotate(180deg),并带有 theme.transitions.duration.shortest 的过渡(见 AccordionSummary.js):

<AccordionSummary expandIcon={<ExpandMoreIcon />} aria-controls="p1c" id="p1h">
  Accordion 1
</AccordionSummary>

示例见 AccordionExpandIcon.js

默认展开(defaultExpanded)

使用 AccordiondefaultExpanded prop(默认 false)让面板初始处于展开状态:

<Accordion defaultExpanded>...</Accordion>

单元测试 Accordion.test.js 验证了该行为:渲染 <Accordion defaultExpanded> 后,根元素会带有 MuiAccordion-expanded 类名。示例见 AccordionExpandDefault.js

更换默认转场(slots.transition)

使用 slots.transitionslotProps.transition 可以替换默认的 Collapse 转场组件。源码中 transition 插槽的默认元素正是 Collapse,且 Accordion 传入 in={expanded} timeout="auto"(见 Accordion.js):

<Accordion
  slots={{ transition: Fade }}
  slotProps={{ transition: { timeout: 900 } }}
>
  ...
</Accordion>

timeout="auto" 意味着转场时长会从子元素的 CSS 过渡中自动测量;如果自定义转场组件不支持 auto,可显式传入 timeout。替换转场的合法前提是自定义组件实现 Material UI 的 Transition 接口——仓库测试中甚至用一个极简的 NoTransition 组件验证了该插槽的可替换性(见 Accordion.test.js),describeConformance 中还专门把 heading 插槽替换为 h4 验证了标题层级可换(见 Accordion.test.js)。示例见 AccordionTransition.js

禁用项(disabled)

Accordion 上设置 disabled prop 即可同时禁用交互与焦点:

<Accordion disabled>
  <AccordionSummary expandIcon={<ExpandMoreIcon />} id="d1h" aria-controls="d1c">
    Disabled Accordion
  </AccordionSummary>
</Accordion>

源码实现链路上,disabled 状态经由 AccordionContext 下发给 AccordionSummary(见 Accordion.js),Summary 再以 disabled 传给内部 ButtonBase 并应用 palette.action.disabledOpacity 的半透明样式(见 AccordionSummary.js)。示例见 DisabledAccordion.js

受控 Accordion(Controlled)

Accordion 既可以是受控(由父组件通过 props 管理状态),也可以是非受控(组件自身本地状态管理):

  • 受控:传入 expanded + onChange,组件只反映外部状态,测试 Accordion.test.js 验证了 setProps({ expanded: false }) 会移除 expanded 类名;
  • 非受控:只传 defaultExpanded 或不传任何状态 prop,状态保存在组件内部。

源码中这一双模式由 useControlled 钩子实现(见 Accordion.js),内部 handleChange 回调在每次切换后以 (event, !expanded) 形式通知 onChange

"同一时间只允许一个面板展开"的经典实现(文档 "Only one expanded at a time" 一节的完整模式,摘自 ControlledAccordions.js):

import * as React from 'react';
import Accordion from '@mui/material/Accordion';
import AccordionDetails from '@mui/material/AccordionDetails';
import AccordionSummary from '@mui/material/AccordionSummary';
import Typography from '@mui/material/Typography';
import ExpandMoreIcon from '@mui/icons-material/ExpandMore';

export default function ControlledAccordions() {
  const [expanded, setExpanded] = React.useState(false);

  const handleChange = (panel) => (event, isExpanded) => {
    setExpanded(isExpanded ? panel : false);
  };

  return (
    <div>
      <Accordion expanded={expanded === 'panel1'} onChange={handleChange('panel1')}>
        <AccordionSummary
          expandIcon={<ExpandMoreIcon />}
          aria-controls="panel1bh-content"
          id="panel1bh-header"
        >
          <Typography component="span" sx={{ width: '33%', flexShrink: 0 }}>
            General settings
          </Typography>
          <Typography component="span" sx={{ color: 'text.secondary' }}>
            I am an accordion
          </Typography>
        </AccordionSummary>
        <AccordionDetails>
          <Typography>
            Nulla facilisi. Phasellus sollicitudin nulla et quam mattis feugiat.
            Aliquam eget maximus est, id dignissim quam.
          </Typography>
        </AccordionDetails>
      </Accordion>
      {/* 其余 panel2 / panel3 / panel4 结构相同,仅 id 与文案不同 */}
    </div>
  );
}

该模式的要点:expanded 保存当前展开面板的标识(如 'panel1'),点击已展开面板时 isExpandedfalse,状态回落到 false——即允许"全部收起"。

核心 Props 参考(以仓库类型定义为准)

以下为 Accordion 的全部自有 props(摘自 Accordion.d.ts),此外还继承了 Paper 的全部 props(elevationsquarevariant 等)与 sx

Prop 类型 默认值 说明
children ReactNode(必需) 第一个子元素必须是 AccordionSummary,其余渲染进内容区
defaultExpanded boolean false 非受控模式下初始是否展开
expanded boolean 受控展开状态;设置后即进入受控模式
onChange (event, expanded: boolean) => void 展开/折叠状态变化回调;注意 event 是通用合成事件而非 change 事件
disabled boolean false 禁用交互与焦点,头部按钮变半透明
disableGutters boolean false 移除展开时面板间的 16px 0 外边距,避免展开撑高页面(对应 CSS variant &.expanded { margin: '16px 0' },见 Accordion.js
classes / className object / string 工具类名覆盖与根类名
sx SxProps<Theme> 样式系统 prop
slots { root?, heading?, region?, transition? } {} 替换内部组件;默认分别为 Paper'h3''div'Collapse
slotProps 各插槽的 props {} 向各内部插槽透传 props;heading 插槽即通过它改标题层级

AccordionSummary 自有 props 包括:childrenexpandIcon(展开指示图标)、focusVisibleClassName(键盘焦点高亮类,:focus-visible 的 polyfill 方案)、onClickclassessxslots/slotPropsrootcontentexpandIconWrapper 三个插槽)。idaria-controls 会透传到内部按钮上(无障碍要求,见下文)。

定制:styled 覆盖与标题层级

视觉定制(styled 包装)

文档示例 CustomizedAccordions.js 展示了用 styled 包装三个子组件实现"无投影、直角、分隔线列表"风格的完整写法:

import { styled } from '@mui/material/styles';
import MuiAccordion from '@mui/material/Accordion';
import MuiAccordionSummary, {
  accordionSummaryClasses,
} from '@mui/material/AccordionSummary';
import MuiAccordionDetails from '@mui/material/AccordionDetails';

const Accordion = styled((props) => (
  <MuiAccordion disableGutters elevation={0} square {...props} />
))(({ theme }) => ({
  border: `1px solid ${theme.palette.divider}`,
  '&:not(:last-child)': { borderBottom: 0 },
  '&::before': { display: 'none' }, // 隐藏默认的顶部分隔线伪元素
}));

const AccordionSummary = styled((props) => (
  <MuiAccordionSummary
    expandIcon={<ArrowForwardIosSharpIcon sx={{ fontSize: '0.9rem' }} />}
    {...props}
  />
))(({ theme }) => ({
  backgroundColor: 'rgba(0, 0, 0, .03)',
  flexDirection: 'row-reverse',
  [`& .${accordionSummaryClasses.expandIconWrapper}.${accordionSummaryClasses.expanded}`]: {
    transform: 'rotate(90deg)', // 覆盖默认 180 度翻转
  },
  [`& .${accordionSummaryClasses.content}`]: {
    marginLeft: theme.spacing(1),
  },
  ...theme.applyStyles('dark', { backgroundColor: 'rgba(255, 255, 255, .05)' }),
}));

const AccordionDetails = styled(MuiAccordionDetails)(({ theme }) => ({
  padding: theme.spacing(2),
  borderTop: '1px solid rgba(0, 0, 0, .125)',
}));

配合上文的受控 useState 逻辑即可得到文档演示中的"只展开一个 + 视觉定制"效果。其中 accordionSummaryClasses 命名导出(contentexpandIconWrapperexpanded 等)来自 accordionSummaryClasses.tsAccordion 自身同样导出 accordionClassesrootheadingroundedexpandeddisabledguttersregion,见 accordionClasses.ts),可用于 sx 选择器或 CSS 深度定制。

从源码看,默认外观的许多细节都来自伪元素:&::before 是一条 1px 的顶部分隔线,展开时 opacity: 0 淡出,首个面板(:first-of-type)隐藏分隔线(见 Accordion.js);圆角则由 rounded variant 按 :first-of-type/:last-of-type 只保留两端圆角、中间保持直角(见 Accordion.js)。因此用 &::before { display: 'none' }square 可以显著简化定制。

修改标题层级(slotProps.heading.component)

Accordion 默认用 h3 元素渲染标题(源码中 AccordionHeading = styled('h3', ...),见 Accordion.js)。为保持文档标题层级正确,可用 slotProps.heading.component 更换:

<Accordion slotProps={{ heading: { component: 'h4' } }}>
  <AccordionSummary
    expandIcon={<ExpandMoreIcon />}
    aria-controls="panel1-content"
    id="panel1-header"
  >
    Accordion
  </AccordionSummary>
  <AccordionDetails>
    Lorem ipsum dolor sit amet, consectetur adipiscing elit.
  </AccordionDetails>
</Accordion>

也可以直接换组件:slots={{ heading: 'h4' }},仓库测试已验证 heading 插槽替换为 h4 时类名仍正确应用(见 Accordion.test.js)。

性能:内容默认挂载与 unmountOnExit

文档 "Performance" 一节指出的关键默认行为是:Accordion 内容即使未展开也会被挂载(内容始终存在于 DOM 中,仅通过转场控制可见性)。这一默认取舍是为服务端渲染(SSR)与 SEO 考虑的——折叠内容对爬虫和首屏 HTML 依然可见。

当折叠区内部嵌入了较大的组件树、或页面上有大量 Accordion 时,可通过 slotProps.transition.unmountOnExit 改为"折叠即卸载":

<Accordion slotProps={{ transition: { unmountOnExit: true } }} />

unmountOnExit 是 Material UI 转场组件(Collapse 等)的标准 prop:转场结束后立即从 DOM 移除子树,减少内存占用与后续渲染成本。从源码看,Accordion 把 slotProps.transition 原样转发给默认 Collapse(见 Accordion.js),因此所有 Collapse 支持的 prop 均可透传。

无障碍(WAI-ARIA)

遵循 WAI-ARIA 的 Accordion 模式(文档 frontmatter 中的 waiAria 链接指向该规范),需要在 AccordionSummary 上设置 idaria-controls

<Accordion>
  <AccordionSummary id="panel-header" aria-controls="panel-content">
    Header
  </AccordionSummary>
  <AccordionDetails>
    Lorem ipsum dolor sit amet, consectetur adipiscing elit.
  </AccordionDetails>
</Accordion>

剩下的关联是组件自动完成的——源码中 region 插槽直接从 summary 的 props 推导:aria-labelledby 取 summary 的 id,自身 id 取 summary 的 aria-controls,并固定 role="region"(见 Accordion.js):

// Accordion.js 内部(简化)
const [AccordionRegionSlot, accordionRegionProps] = useSlot('region', {
  elementType: AccordionRegion,
  ...
  additionalProps: {
    'aria-labelledby': summary.props.id,
    id: summary.props['aria-controls'],
    role: 'region',
  },
});

因此在 Summary 上手动写好 id + aria-controls 后,内容区的 role="region"idaria-labelledby 三者自动配对,无需重复声明。React 18+ 下可用 React.useId() 生成唯一 id,避免多个面板 id 冲突(DisabledAccordion.js 演示了这种写法)。

DOM 结构解剖(Anatomy)

文档给出的静态结构如下,可用作 CSS 选择器与测试断言的稳定依据:

<div class="MuiAccordion-root">
  <h3 class="MuiAccordion-heading">
    <button class="MuiButtonBase-root MuiAccordionSummary-root" aria-expanded="">
      <!-- Accordion summary goes here -->
    </button>
  </h3>
  <div class="MuiAccordion-region" role="region">
    <div class="MuiAccordionDetails-root">
      <!-- Accordion content goes here -->
    </div>
  </div>
</div>

与源码对照确认的要点:

  1. 根元素是 Paper(而非普通 div),所以默认带投影(elevation),可用 elevation={0} 去除,也可用 slots={{ root: ... }} 整体替换;
  2. 头部按钮的 aria-expanded 随展开状态同步(测试断言 aria-expanded='false',见 Accordion.test.js);
  3. 内容实际包在 transition 插槽(默认 Collapse) 与 region 之间:渲染树为 Root → heading(summary) + TransitionSlot → Region → children(见 Accordion.js),这也解释了 slotProps.transition 的作用位置;
  4. 状态类名(MuiAccordion-expandedMuiAccordion-disabledMuiAccordion-gutters 等)均由 composeClasses 依据 ownerState 组合到根与子元素上(见 Accordion.js)。

相关资源

小结

  • Accordion 家族四组件各司其职;children 首元素必须是 AccordionSummary,这是源码 PropTypes 的硬校验;
  • 受控与"单开互斥"模式通过 expanded + onChange + 父级 useState 实现,useControlled 保证两种模式可无缝切换;
  • 四个内部插槽 root / heading / transition / region 分别默认 Paper / h3 / Collapse / div,是定制视觉与性能的主要入口;
  • 无障碍只需在 Summary 上写 idaria-controls,region 的 ARIA 关联由组件自动推导;
  • 需要性能优化时,用 slotProps.transition.unmountOnExit 把默认"始终挂载"改为"折叠即卸载",代价是 SSR 首屏 HTML 中不再包含折叠内容。
登录后查看全文
热门项目推荐
相关项目推荐

项目优选

收起
kernelkernel
deepin linux kernel
C
33
18
ops-transformerops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.12 K
2.72 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
528
588
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
906
1.83 K
pytorchpytorch
作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU,为开发者提供昇腾 AI 处理器的超强算力。
Python
854
1.34 K
docsdocs
暂无描述
Markdown
891
5.79 K
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.53 K
1.01 K
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.34 K
1.45 K
cann-learning-hubcann-learning-hub
CANN 学习中心仓,支持在线互动运行、边学边练,提供教程、示例与优化方案,一站式助力昇腾开发者快速上手。
Jupyter Notebook
988
506
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
540
384