Skip to content

Conversation

@dmytrokirpa
Copy link
Contributor

@dmytrokirpa dmytrokirpa commented Jan 20, 2026

Previous Behavior

New Behavior

Added useAccordionBase_unstable, useAccordionHeaderBase_unstable, and useAccordionPanelBase_unstable hooks that contain state for logic and slots (but not design-related features like navigation, expand icon rotation, and collapse motion animations as per #35623).

Example how to compose a custom Accordion component:

import * as React from 'react';
import { mergeClasses } from '@griffel/react';
import {
  useAccordionBase_unstable,
  renderAccordion_unstable,
  useAccordionContextValues_unstable,
  useAccordionContext_unstable,
} from '@fluentui/react-accordion';
import type { AccordionBaseProps, AccordionState } from '@fluentui/react-accordion';
import {
  useAccordionItem_unstable,
  renderAccordionItem_unstable,
  useAccordionItemContextValues_unstable,
} from '@fluentui/react-accordion';
import type { AccordionItemProps, AccordionItemState } from '@fluentui/react-accordion';
import {
  useAccordionHeaderBase_unstable,
  renderAccordionHeader_unstable,
  useAccordionHeaderContextValues_unstable,
} from '@fluentui/react-accordion';
import type { AccordionHeaderProps, AccordionHeaderState } from '@fluentui/react-accordion';
import {
  useAccordionPanelBase_unstable,
  renderAccordionPanel_unstable,
} from '@fluentui/react-accordion';
import type { AccordionPanelBaseProps, AccordionPanelState } from '@fluentui/react-accordion';

type CustomAccordionAppearance = 'filled' | 'outline';

type CustomAccordionContextValue = {
  appearance: CustomAccordionAppearance;
};

type CustomAccordionProps = AccordionBaseProps & CustomAccordionContextValue;

const CustomAccordion = React.forwardRef<HTMLDivElement, CustomAccordionProps>(
  ({ appearance = 'filled', ...props }, ref) => {
    const state = useAccordionBase_unstable(props, ref);
    const contextValues = useAccordionContextValues_unstable(state as AccordionState);

    // Extend context to include appearance
    Object.assign(contextValues.accordion, { appearance });

    state.root.className = mergeClasses('accordion', `accordion--${appearance}`, state.root.className);

    return renderAccordion_unstable(state as AccordionState, contextValues);
  },
);

const CustomAccordionItem = React.forwardRef<HTMLDivElement, AccordionItemProps>((props, ref) => {
  const state = useAccordionItem_unstable(props, ref);
  const appearance = useAccordionContext_unstable((ctx: unknown) => (ctx as CustomAccordionContextValue).appearance);

  state.root.className = mergeClasses('accordion-item', `accordion-item--${appearance}`, state.root.className);

  const contextValues = useAccordionItemContextValues_unstable(state);
  return renderAccordionItem_unstable(state as AccordionItemState, contextValues);
});

const CustomAccordionHeader = React.forwardRef<HTMLDivElement, AccordionHeaderProps>((props, ref) => {
  const state = useAccordionHeaderBase_unstable(props, ref);
  const appearance = useAccordionContext_unstable((ctx: unknown) => (ctx as CustomAccordionContextValue).appearance);

  state.root.className = mergeClasses(
    'accordion-header',
    `accordion-header--${appearance}`,
    state.open && 'accordion-header-open',
    state.root.className,
  );

  const contextValues = useAccordionHeaderContextValues_unstable(state);
  return renderAccordionHeader_unstable(state as AccordionHeaderState, contextValues);
});

const CustomAccordionPanel = React.forwardRef<HTMLDivElement, AccordionPanelBaseProps>((props, ref) => {
  const state = useAccordionPanelBase_unstable(props, ref);
  const appearance = useAccordionContext_unstable((ctx: unknown) => (ctx as CustomAccordionContextValue).appearance);

  state.root.className = mergeClasses('accordion-panel', `accordion-panel--${appearance}`, state.root.className);

  // Don't render if not open (simulating unmountOnExit behavior)
  if (!state.open) {
    return null;
  }

  return renderAccordionPanel_unstable(state as AccordionPanelState);
});

export const Example: React.FC = () => {
  return (
    <CustomAccordion appearance="outline" defaultOpenItems={['1']}>
      <CustomAccordionItem value="1">
        <CustomAccordionHeader>First</CustomAccordionHeader>
        <CustomAccordionPanel>First panel content</CustomAccordionPanel>
      </CustomAccordionItem>
      <CustomAccordionItem value="2">
        <CustomAccordionHeader>Second</CustomAccordionHeader>
        <CustomAccordionPanel>Second panel content</CustomAccordionPanel>
      </CustomAccordionItem>
    </CustomAccordion>
  );
};

Related Issue(s)

  • Fixes #

@dmytrokirpa dmytrokirpa self-assigned this Jan 20, 2026
@github-actions
Copy link

github-actions bot commented Jan 20, 2026

📊 Bundle size report

Package & Exports Baseline (minified/GZIP) PR Change
react-accordion
Accordion (including children components)
107.486 kB
32.905 kB
104.357 kB
31.673 kB
-3.129 kB
-1.232 kB
react-components
react-components: Accordion, Button, FluentProvider, Image, Menu, Popover
237.741 kB
68.697 kB
238.065 kB
68.817 kB
324 B
120 B
react-components
react-components: entire library
1.287 MB
322.567 kB
1.288 MB
322.661 kB
304 B
94 B
Unchanged fixtures
Package & Exports Size (minified/GZIP)
react-components
react-components: Button, FluentProvider & webLightTheme
70.322 kB
20.082 kB
react-components
react-components: FluentProvider & webLightTheme
43.621 kB
14.173 kB
react-portal-compat
PortalCompatProvider
8.386 kB
2.624 kB
react-timepicker-compat
TimePicker
109.036 kB
36.023 kB
🤖 This report was generated against 47ee7ad9d466228e43efcf6769129295f7422fbb

@github-actions
Copy link

Pull request demo site: URL

@dmytrokirpa dmytrokirpa force-pushed the feat/accordion-base-hooks branch from f17ebe2 to 45f3b03 Compare January 22, 2026 11:43
@dmytrokirpa dmytrokirpa force-pushed the feat/accordion-base-hooks branch from 45f3b03 to af06cd4 Compare January 22, 2026 12:30
@dmytrokirpa dmytrokirpa marked this pull request as ready for review January 22, 2026 12:31
@dmytrokirpa dmytrokirpa requested review from a team and marcosmoura as code owners January 22, 2026 12:31
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants