When building modern SharePoint Framework (SPFx) solutions, reusable UI components play a crucial role in keeping your code clean, scalable, and maintainable. In particular, interactive components help improve the user experience without cluttering the interface.
Among these components, the Accordion is a commonly used UI element. It allows users to expand and collapse sections, making it easier to display large amounts of information in a compact and organized layout. In this blog, we’ll walk through how to create a custom accordion component in SPFx using React.
Create the Accordion Wrapper Component
To begin with, we’ll create a wrapper component that acts as a container for multiple accordion items. At a high level, this component’s responsibility is intentionally simple: it renders child accordion items while keeping styling and layout consistent across the entire accordion.This approach allows individual accordion items to remain focused on their own behavior, while the wrapper handles structure and reusability.
Accordion.tsx
import * as React from 'react';
import styles from './Accordion.module.scss';
import classNames from 'classnames';
import { IAccordionItemProps } from './subcomponents/AccordionItem';
import { ReactElement } from 'react';
export interface IAccordionProps {
children?:
| ReactElement<IAccordionItemProps>
| ReactElement<IAccordionItemProps>[];
className?: string;
}
const Accordion: React.FunctionComponent<
React.PropsWithChildren<IAccordionProps>
> = (props) => {
const { children, className } = props;
return (
<div className={classNames(styles.accordionSubcomponent, className)}>
{children}
</div>
);
};
export default Accordion;
Styling with SCSS Modules
Next, let’s focus on styling. SPFx supports SCSS modules, which is ideal for avoiding global CSS conflicts and keeping styles scoped to individual components. Let’s see styling for accordion and accordion items.
Accordion.module.scss
.accordionSubcomponent {
margin-bottom: 12px;
.accordionTitleRow {
display: flex;
flex-direction: row;
align-items: center;
padding: 5px;
font-size: 18px;
font-weight: 600;
cursor: pointer;
-webkit-touch-callout: none;
-webkit-user-select: none;
-khtml-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
border-bottom: 1px solid;
border-color: "[theme: neutralQuaternaryAlt]";
background: "[theme: neutralLighter]";
}
.accordionTitleRow:hover {
opacity: .8;
}
.accordionIconCol {
padding: 0px 5px;
}
.accordionHeaderCol {
display: inline-block;
width: 100%;
}
.iconExpandCollapse {
margin-top: -4px;
font-weight: 600;
vertical-align: middle;
}
.accordionContent {
margin-left: 12px;
display: grid;
grid-template-rows: 0fr;
overflow: hidden;
transition: grid-template-rows 200ms;
&.expanded {
grid-template-rows: 1fr;
}
.expandableContent {
min-height: 0;
}
}
}
Styling Highlights
- Grid‑based animation for expand/collapse
- SharePoint theme tokens
- Hover effects for better UX
Creating Accordion Item Component
Each expandable section is managed by AccordionItem.tsx.
import * as React from 'react';
import styles from '../Accordion.module.scss';
import classNames from 'classnames';
import { Icon } from '@fluentui/react';
import { useState } from 'react';
export interface IAccordionItemProps {
iconCollapsed?: string;
iconExpanded?: string;
headerText?: string;
headerClassName?: string;
bodyClassName?: string;
isExpandedByDefault?: boolean;
}
const AccordionItem: React.FunctionComponent<React.PropsWithChildren<IAccordionItemProps>> = (props: React.PropsWithChildren<IAccordionItemProps>) => {
const {
iconCollapsed,
iconExpanded,
headerText,
headerClassName,
bodyClassName,
isExpandedByDefault,
children
} = props;
const [isExpanded, setIsExpanded] = useState<boolean>(!!isExpandedByDefault);
const _toggleAccordion = (): void => {
setIsExpanded((prevIsExpanded) => !prevIsExpanded);
}
return (
<Stack>
<div className={styles.accordionTitleRow} onClick={_toggleAccordion}>
<div className={styles.accordionIconCol}>
<Icon
iconName={isExpanded ? iconExpanded : iconCollapsed}
className={styles.iconExpandCollapse}
/>
</div>
<div className={classNames(styles.accordionHeaderCol, headerClassName)}>
{headerText}
</div>
</div>
<div className={classNames(styles.accordionContent, bodyClassName, {[styles.expanded]: isExpanded})}>
<div className={styles.expandableContent}>
{children}
</div>
</div>
</Stack>
)
}
AccordionItem.defaultProps = {
iconExpanded: 'ChevronDown',
iconCollapsed: 'ChevronUp'
};
export default AccordionItem;
Example Usage in SPFx Web Part
<Accordion>
<AccordionItem headerText="What is SPFx?">
<p>SPFx is a development model for SharePoint customizations.</p>
</AccordionItem>
<AccordionItem
headerText="Why use custom controls?"
isExpandedByDefault={true}
>
<p>Custom controls improve reusability and UI consistency.</p>
</AccordionItem>
</Accordion>

Conclusion
By building a custom accordion component in SPFx using React, you gain:
- Full control over UI behavior
- Lightweight and reusable code
- Native SharePoint theming
This pattern is perfect for:
- FAQ sections
- Configuration panels
- Dashboard summaries
