Repository: microsoft/fluentui
Pull request #34166: Add getSlotClassNameProp to allow custom style hooks to preserve the original className while overriding the component default className.
Author: login: behowell
Created: 2025-04-03T18:36:19Z | Merged: 2025-04-16T18:31:06Z

## Previous Behavior

More details are available in https://github.com/microsoft/fluentui/issues/34144 which describes the issue. In short: custom style hooks currently have no way to override the component's base styles while still respecting styles added via the `className` prop on the component itself.

## New Behavior

Save each slot's user-provided class name (the `className` prop) in a metadata field on the slot object. This allows it to be accessed later, even after the default styles are applied. Provide a new function `getSlotClassNameProp_unstable` to access this value.

Each custom style hook will need to be updated to append a call to `getSlotClassNameProp_unstable` at the end of each call to `mergeClasses`. This refactoring can be done with the following copilot prompt:

```
Update all calls to mergeClasses to add a call to getSlotClassNameProp_unstable as the last param. Here's an example diff:

-state.foo.className = mergeClasses(state.foo.className, styles.bar); 
+state.foo.className = mergeClasses(state.foo.className, styles.bar, getSlotClassNameProp_unstable(state.foo));

Also import getSlotClassNameProp_unstable from @fluentui/react-components.
```

## Related Issue(s)


- #34144 
  - The issue is not directly fixed by this PR, but it gives custom style hooks the tools necessary to fix it. Each custom style hook will need to be updated individually to use the new function.



Diff:
diff --git a/apps/vr-tests-react-components/src/stories/CustomStyleHooks.stories.tsx b/apps/vr-tests-react-components/src/stories/CustomStyleHooks.stories.tsx
index 980e30c7a3c6d..0bae6a944d525 100644
--- a/apps/vr-tests-react-components/src/stories/CustomStyleHooks.stories.tsx
+++ b/apps/vr-tests-react-components/src/stories/CustomStyleHooks.stories.tsx
@@ -10,6 +10,7 @@ import type {
 } from '@fluentui/react-button';
 import { FluentProvider, FluentProviderCustomStyleHooks } from '@fluentui/react-provider';
 import { makeStyles, mergeClasses, shorthands } from '@griffel/react';
+import { getSlotClassNameProp_unstable } from '@fluentui/react-utilities';
 import { type StoryParameters, Steps } from 'storywright';
 
 export default {
@@ -30,6 +31,12 @@ const useCustomStyles = makeStyles({
     ...shorthands.borderColor('crimson'),
     ...shorthands.borderRadius('0'),
   },
+
+  purpleButton: {
+    ...shorthands.borderColor('indigo'),
+    backgroundColor: 'purple',
+    color: 'lavender',
+  },
 });
 
 export const ButtonCustomStyles = () => {
@@ -38,7 +45,11 @@ export const ButtonCustomStyles = () => {
   const customStyleHooks: FluentProviderCustomStyleHooks = {
     useButtonStyles_unstable: (state: unknown) => {
       const componentState = state as ButtonState;
-      componentState.root.className = mergeClasses(componentState.root.className, styles.button);
+      componentState.root.className = mergeClasses(
+        componentState.root.className,
+        styles.button,
+        getSlotClassNameProp_unstable(componentState.root),
+      );
     },
   };
 
@@ -57,7 +68,11 @@ export const CompoundButtonCustomStyles = () => {
   const customStyleHooks: FluentProviderCustomStyleHooks = {
     useCompoundButtonStyles_unstable: (state: unknown) => {
       const componentState = state as CompoundButtonState;
-      componentState.root.className = mergeClasses(componentState.root.className, styles.button);
+      componentState.root.className = mergeClasses(
+        componentState.root.className,
+        styles.button,
+        getSlotClassNameProp_unstable(componentState.root),
+      );
     },
   };
 
@@ -76,7 +91,11 @@ export const MenuButtonCustomStyles = () => {
   const customStyleHooks: FluentProviderCustomStyleHooks = {
     useMenuButtonStyles_unstable: (state: unknown) => {
       const componentState = state as MenuButtonState;
-      componentState.root.className = mergeClasses(componentState.root.className, styles.button);
+      componentState.root.className = mergeClasses(
+        componentState.root.className,
+        styles.button,
+        getSlotClassNameProp_unstable(componentState.root),
+      );
     },
   };
 
@@ -96,12 +115,17 @@ export const SplitButtonCustomStyles = () => {
     useSplitButtonStyles_unstable: (state: unknown) => {
       const componentState = state as SplitButtonState;
       if (componentState.menuButton) {
-        componentState.menuButton.className = mergeClasses(componentState.menuButton.className, styles.button);
+        componentState.menuButton.className = mergeClasses(
+          componentState.menuButton.className,
+          styles.button,
+          getSlotClassNameProp_unstable(componentState.menuButton),
+        );
       }
       if (componentState.primaryActionButton) {
         componentState.primaryActionButton.className = mergeClasses(
           componentState.primaryActionButton.className,
           styles.button,
+          getSlotClassNameProp_unstable(componentState.primaryActionButton),
         );
       }
     },
@@ -122,7 +146,11 @@ export const ToggleButtonCustomStyles = () => {
   const customStyleHooks: FluentProviderCustomStyleHooks = {
     useToggleButtonStyles_unstable: (state: unknown) => {
       const componentState = state as ToggleButtonState;
-      componentState.root.className = mergeClasses(componentState.root.className, styles.button);
+      componentState.root.className = mergeClasses(
+        componentState.root.className,
+        styles.button,
+        getSlotClassNameProp_unstable(componentState.root),
+      );
     },
   };
 
@@ -134,3 +162,26 @@ export const ToggleButtonCustomStyles = () => {
 };
 
 ToggleButtonCustomStyles.storyName = 'ToggleButton';
+
+export const ClassNamePropWithCustomStyles = () => {
+  const styles = useCustomStyles();
+
+  const customStyleHooks: FluentProviderCustomStyleHooks = {
+    useButtonStyles_unstable: (state: unknown) => {
+      const componentState = state as ButtonState;
+      componentState.root.className = mergeClasses(
+        componentState.root.className,
+        styles.button,
+        getSlotClassNameProp_unstable(componentState.root),
+      );
+    },
+  };
+
+  return (
+    <FluentProvider customStyleHooks_unstable={customStyleHooks}>
+      <Button className={styles.purpleButton}>Purple button</Button>
+    </FluentProvider>
+  );
+};
+
+ClassNamePropWithCustomStyles.storyName = 'Button with className';
diff --git a/change/@fluentui-react-components-6929107a-28e2-44fc-a7db-c26a1525e66a.json b/change/@fluentui-react-components-6929107a-28e2-44fc-a7db-c26a1525e66a.json
new file mode 100644
index 0000000000000..7ef0cf667f492
--- /dev/null
+++ b/change/@fluentui-react-components-6929107a-28e2-44fc-a7db-c26a1525e66a.json
@@ -0,0 +1,7 @@
+{
+  "type": "minor",
+  "comment": "Add getSlotClassNameProp_unstable function to allow custom style hooks to preserve the original className while overriding the component default className.",
+  "packageName": "@fluentui/react-components",
+  "email": "behowell@microsoft.com",
+  "dependentChangeType": "patch"
+}
diff --git a/change/@fluentui-react-jsx-runtime-f24d1b2a-12c0-4040-baad-4d9cd5a2bebf.json b/change/@fluentui-react-jsx-runtime-f24d1b2a-12c0-4040-baad-4d9cd5a2bebf.json
new file mode 100644
index 0000000000000..57099cae8aa54
--- /dev/null
+++ b/change/@fluentui-react-jsx-runtime-f24d1b2a-12c0-4040-baad-4d9cd5a2bebf.json
@@ -0,0 +1,7 @@
+{
+  "type": "patch",
+  "comment": "Support internal slot metadata change for SLOT_CLASS_NAME_PROP_SYMBOL",
+  "packageName": "@fluentui/react-jsx-runtime",
+  "email": "behowell@microsoft.com",
+  "dependentChangeType": "patch"
+}
diff --git a/change/@fluentui-react-utilities-9a6dd4bb-e5d9-47b6-ab20-776b214c5d54.json b/change/@fluentui-react-utilities-9a6dd4bb-e5d9-47b6-ab20-776b214c5d54.json
new file mode 100644
index 0000000000000..0d6a47d7d7d51
--- /dev/null
+++ b/change/@fluentui-react-utilities-9a6dd4bb-e5d9-47b6-ab20-776b214c5d54.json
@@ -0,0 +1,7 @@
+{
+  "type": "minor",
+  "comment": "Add getSlotClassNameProp_unstable function to allow custom style hooks to preserve the original className while overriding the component default className.",
+  "packageName": "@fluentui/react-utilities",
+  "email": "behowell@microsoft.com",
+  "dependentChangeType": "patch"
+}
diff --git a/packages/react-components/react-components/etc/react-components.api.md b/packages/react-components/react-components/etc/react-components.api.md
index 0dfbab8e738bc..8fd27ac41ba6a 100644
--- a/packages/react-components/react-components/etc/react-components.api.md
+++ b/packages/react-components/react-components/etc/react-components.api.md
@@ -450,6 +450,7 @@ import { ForwardRefComponent } from '@fluentui/react-utilities';
 import { getIntrinsicElementProps } from '@fluentui/react-utilities';
 import { getNativeElementProps } from '@fluentui/react-utilities';
 import { getPartitionedNativeProps } from '@fluentui/react-utilities';
+import { getSlotClassNameProp_unstable } from '@fluentui/react-utilities';
 import { getSlots } from '@fluentui/react-utilities';
 import { GriffelRenderer } from '@griffel/react';
 import { GriffelResetStyle } from '@griffel/react';
@@ -2754,6 +2755,8 @@ export { getNativeElementProps }
 
 export { getPartitionedNativeProps }
 
+export { getSlotClassNameProp_unstable }
+
 export { getSlots }
 
 export { GriffelRenderer }
diff --git a/packages/react-components/react-components/src/index.ts b/packages/react-components/react-components/src/index.ts
index b273440ea86cd..ccba5b17e015c 100644
--- a/packages/react-components/react-components/src/index.ts
+++ b/packages/react-components/react-components/src/index.ts
@@ -107,6 +107,7 @@ export {
   getNativeElementProps,
   getIntrinsicElementProps,
   getPartitionedNativeProps,
+  getSlotClassNameProp_unstable,
   // getSlots is deprecated but removing it would be a breaking change
   // eslint-disable-next-line @typescript-eslint/no-deprecated
   getSlots,
diff --git a/packages/react-components/react-jsx-runtime/src/utils/getMetadataFromSlotComponent.ts b/packages/react-components/react-jsx-runtime/src/utils/getMetadataFromSlotComponent.ts
index 8c95fd3ba8004..976bcecbf40c9 100644
--- a/packages/react-components/react-jsx-runtime/src/utils/getMetadataFromSlotComponent.ts
+++ b/packages/react-components/react-jsx-runtime/src/utils/getMetadataFromSlotComponent.ts
@@ -1,5 +1,9 @@
 import type * as React from 'react';
-import { SLOT_ELEMENT_TYPE_SYMBOL, SLOT_RENDER_FUNCTION_SYMBOL } from '@fluentui/react-utilities';
+import {
+  SLOT_CLASS_NAME_PROP_SYMBOL,
+  SLOT_ELEMENT_TYPE_SYMBOL,
+  SLOT_RENDER_FUNCTION_SYMBOL,
+} from '@fluentui/react-utilities';
 import type { SlotComponentType, UnknownSlotProps } from '@fluentui/react-utilities';
 
 /**
@@ -8,6 +12,7 @@ import type { SlotComponentType, UnknownSlotProps } from '@fluentui/react-utilit
 export function getMetadataFromSlotComponent<Props extends UnknownSlotProps>(type: SlotComponentType<Props>) {
   const {
     as,
+    [SLOT_CLASS_NAME_PROP_SYMBOL]: _classNameProp,
     [SLOT_ELEMENT_TYPE_SYMBOL]: baseElementType,
     [SLOT_RENDER_FUNCTION_SYMBOL]: renderFunction,
     ...propsWithoutMetadata
diff --git a/packages/react-components/react-utilities/etc/react-utilities.api.md b/packages/react-components/react-utilities/etc/react-utilities.api.md
index 7bf62331fcf8b..70e8390f3ef24 100644
--- a/packages/react-components/react-utilities/etc/react-utilities.api.md
+++ b/packages/react-components/react-utilities/etc/react-utilities.api.md
@@ -96,6 +96,9 @@ export const getPartitionedNativeProps: <Props extends Pick<React_2.HTMLAttribut
 // @internal
 export const getRTLSafeKey: (key: string, dir: 'ltr' | 'rtl') => string;
 
+// @public
+export const getSlotClassNameProp_unstable: (slot: UnknownSlotProps) => string | undefined;
+
 // @public @deprecated
 export function getSlots<R extends SlotPropsRecord>(state: ComponentState<R>): {
     slots: Slots<R>;
@@ -267,6 +270,9 @@ declare namespace slot {
 }
 export { slot }
 
+// @internal
+export const SLOT_CLASS_NAME_PROP_SYMBOL: unique symbol;
+
 // @internal
 export const SLOT_ELEMENT_TYPE_SYMBOL: unique symbol;
 
@@ -283,6 +289,7 @@ export type SlotComponentType<Props> = Props & {
     (props: React_2.PropsWithChildren<{}>): React_2.ReactElement | null;
     [SLOT_RENDER_FUNCTION_SYMBOL]?: SlotRenderFunction<Props>;
     [SLOT_ELEMENT_TYPE_SYMBOL]: React_2.ComponentType<Props> | (Props extends AsIntrinsicElement<infer As> ? As : keyof JSX.IntrinsicElements);
+    [SLOT_CLASS_NAME_PROP_SYMBOL]?: string;
 };
 
 // @public (undocumented)
diff --git a/packages/react-components/react-utilities/src/compose/constants.ts b/packages/react-components/react-utilities/src/compose/constants.ts
index f3526b6de2e44..5d5fe86f88022 100644
--- a/packages/react-components/react-utilities/src/compose/constants.ts
+++ b/packages/react-components/react-utilities/src/compose/constants.ts
@@ -8,3 +8,8 @@ export const SLOT_RENDER_FUNCTION_SYMBOL = Symbol.for('fui.slotRenderFunction');
  * Internal reference for the render function
  */
 export const SLOT_ELEMENT_TYPE_SYMBOL = Symbol.for('fui.slotElementType');
+/**
+ * @internal
+ * Internal cache of the original className prop for the slot, before being modified by the useStyles hook.
+ */
+export const SLOT_CLASS_NAME_PROP_SYMBOL = Symbol.for('fui.slotClassNameProp');
diff --git a/packages/react-components/react-utilities/src/compose/getSlotClassNameProp.test.ts b/packages/react-components/react-utilities/src/compose/getSlotClassNameProp.test.ts
new file mode 100644
index 0000000000000..e71d97d3d231d
--- /dev/null
+++ b/packages/react-components/react-utilities/src/compose/getSlotClassNameProp.test.ts
@@ -0,0 +1,30 @@
+import { getSlotClassNameProp } from './getSlotClassNameProp';
+import * as slot from './slot';
+import type { ComponentProps, Slot } from './types';
+
+type TestSlots = {
+  slotA: NonNullable<Slot<'div'>>;
+};
+
+type TestProps = ComponentProps<TestSlots>;
+
+const mergeClasses = (...classNames: (string | false | undefined)[]) => classNames.filter(Boolean).join(' ');
+
+describe('getSlotClassNameProp', () => {
+  it('returns the original class name even if the slot className is modified', () => {
+    const props: TestProps = { slotA: { className: 'originalClassName' } };
+    const slotA = slot.always(props.slotA, { elementType: 'div' });
+    slotA.className = mergeClasses(slotA.className, 'overrideClassName');
+
+    expect(getSlotClassNameProp(slotA)).toEqual('originalClassName');
+    expect(slotA.className).toEqual('originalClassName overrideClassName');
+  });
+  it('returns undefined if the slot does not have a className', () => {
+    const props: TestProps = { slotA: {} };
+    const slotA = slot.always(props.slotA, { elementType: 'div' });
+    slotA.className = mergeClasses(slotA.className, 'overrideClassName');
+
+    expect(getSlotClassNameProp(slotA)).toBeUndefined();
+    expect(slotA.className).toEqual('overrideClassName');
+  });
+});
diff --git a/packages/react-components/react-utilities/src/compose/getSlotClassNameProp.ts b/packages/react-components/react-utilities/src/compose/getSlotClassNameProp.ts
new file mode 100644
index 0000000000000..448188a989f36
--- /dev/null
+++ b/packages/react-components/react-utilities/src/compose/getSlotClassNameProp.ts
@@ -0,0 +1,24 @@
+import { SLOT_CLASS_NAME_PROP_SYMBOL } from '../compose/constants';
+import type { UnknownSlotProps } from '../compose/types';
+
+/**
+ * Get the className prop set on the slot by the user, without including the default classes added by the component.
+ * Custom style hooks should merge this className _after_ any additional classes added by the hook, to ensure that
+ * classes added by the user take precedence over the custom style hook.
+ *
+ * Example usage in a custom style hook:
+ * ```ts
+ * state.root.className = mergeClasses(
+ *   state.root.className,
+ *   customStyles.root,
+ *   getSlotClassNameProp_unstable(state.root));
+ * ```
+ *
+ * @returns The className prop set on the slot by the user, or undefined if not set.
+ */
+export const getSlotClassNameProp = (slot: UnknownSlotProps) => {
+  if (SLOT_CLASS_NAME_PROP_SYMBOL in slot && typeof slot[SLOT_CLASS_NAME_PROP_SYMBOL] === 'string') {
+    return slot[SLOT_CLASS_NAME_PROP_SYMBOL];
+  }
+  return undefined;
+};
diff --git a/packages/react-components/react-utilities/src/compose/index.ts b/packages/react-components/react-utilities/src/compose/index.ts
index fb6f26f68e14b..9dd0db0086066 100644
--- a/packages/react-components/react-utilities/src/compose/index.ts
+++ b/packages/react-components/react-utilities/src/compose/index.ts
@@ -21,10 +21,11 @@ export type {
   UnknownSlotProps,
 } from './types';
 export { isResolvedShorthand } from './isResolvedShorthand';
-export { SLOT_ELEMENT_TYPE_SYMBOL, SLOT_RENDER_FUNCTION_SYMBOL } from './constants';
+export { SLOT_CLASS_NAME_PROP_SYMBOL, SLOT_ELEMENT_TYPE_SYMBOL, SLOT_RENDER_FUNCTION_SYMBOL } from './constants';
 export { isSlot } from './isSlot';
 export { assertSlots } from './assertSlots';
 export { getIntrinsicElementProps } from './getIntrinsicElementProps';
+export { getSlotClassNameProp as getSlotClassNameProp_unstable } from './getSlotClassNameProp';
 
 // eslint-disable-next-line @typescript-eslint/no-deprecated
 export type { ObjectSlotProps, Slots } from './deprecated/getSlots';
diff --git a/packages/react-components/react-utilities/src/compose/slot.test.tsx b/packages/react-components/react-utilities/src/compose/slot.test.tsx
index d49e24dba6350..bcd47a4b9b9c8 100644
--- a/packages/react-components/react-utilities/src/compose/slot.test.tsx
+++ b/packages/react-components/react-utilities/src/compose/slot.test.tsx
@@ -1,7 +1,7 @@
 import * as React from 'react';
 import * as slot from './slot';
 import type { ComponentProps, Slot } from './types';
-import { SLOT_ELEMENT_TYPE_SYMBOL, SLOT_RENDER_FUNCTION_SYMBOL } from './constants';
+import { SLOT_CLASS_NAME_PROP_SYMBOL, SLOT_ELEMENT_TYPE_SYMBOL, SLOT_RENDER_FUNCTION_SYMBOL } from './constants';
 
 type TestSlots = {
   slotA?: Slot<'div'>;
@@ -84,6 +84,20 @@ describe('slot', () => {
       [SLOT_RENDER_FUNCTION_SYMBOL]: expect.any(Function),
     });
   });
+
+  it('saves the original className', () => {
+    const props: TestProps = { slotA: { className: 'test1' } };
+    const resolvedProps = slot.optional(props.slotA, { elementType: 'div' });
+    if (resolvedProps) {
+      resolvedProps.className = [resolvedProps.className, 'test2'].join(' ');
+    }
+    expect(resolvedProps).toEqual({
+      [SLOT_ELEMENT_TYPE_SYMBOL]: 'div',
+      [SLOT_CLASS_NAME_PROP_SYMBOL]: 'test1',
+      className: 'test1 test2',
+    });
+  });
+
   describe('.resolveShorthand', () => {
     it('resolves a string', () => {
       expect(slot.resolveShorthand('hello')).toEqual({ children: 'hello' });
diff --git a/packages/react-components/react-utilities/src/compose/slot.ts b/packages/react-components/react-utilities/src/compose/slot.ts
index 6520b3fb4d425..f8e506ca11cc7 100644
--- a/packages/react-components/react-utilities/src/compose/slot.ts
+++ b/packages/react-components/react-utilities/src/compose/slot.ts
@@ -6,7 +6,7 @@ import type {
   UnknownSlotProps,
 } from './types';
 import * as React from 'react';
-import { SLOT_ELEMENT_TYPE_SYMBOL, SLOT_RENDER_FUNCTION_SYMBOL } from './constants';
+import { SLOT_CLASS_NAME_PROP_SYMBOL, SLOT_ELEMENT_TYPE_SYMBOL, SLOT_RENDER_FUNCTION_SYMBOL } from './constants';
 
 export type SlotOptions<Props extends UnknownSlotProps> = {
   elementType:
@@ -41,6 +41,7 @@ export function always<Props extends UnknownSlotProps>(
     ...defaultProps,
     ...props,
     [SLOT_ELEMENT_TYPE_SYMBOL]: elementType,
+    [SLOT_CLASS_NAME_PROP_SYMBOL]: props?.className,
   } as SlotComponentType<Props>;
 
   if (props && typeof props.children === 'function') {
diff --git a/packages/react-components/react-utilities/src/compose/types.ts b/packages/react-components/react-utilities/src/compose/types.ts
index 73b4de70f1d61..277b29455a4f9 100644
--- a/packages/react-components/react-utilities/src/compose/types.ts
+++ b/packages/react-components/react-utilities/src/compose/types.ts
@@ -1,5 +1,5 @@
 import * as React from 'react';
-import { SLOT_ELEMENT_TYPE_SYMBOL, SLOT_RENDER_FUNCTION_SYMBOL } from './constants';
+import { SLOT_CLASS_NAME_PROP_SYMBOL, SLOT_ELEMENT_TYPE_SYMBOL, SLOT_RENDER_FUNCTION_SYMBOL } from './constants';
 import { DistributiveOmit, ReplaceNullWithUndefined } from '../utils/types';
 
 export type SlotRenderFunction<Props> = (
@@ -252,6 +252,11 @@ export type SlotComponentType<Props> = Props & {
   [SLOT_ELEMENT_TYPE_SYMBOL]:
     | React.ComponentType<Props>
     | (Props extends AsIntrinsicElement<infer As> ? As : keyof JSX.IntrinsicElements);
+  /**
+   * @internal
+   * The original className prop for the slot, before being modified by the useStyles hook.
+   */
+  [SLOT_CLASS_NAME_PROP_SYMBOL]?: string;
 };
 
 /**
diff --git a/packages/react-components/react-utilities/src/index.ts b/packages/react-components/react-utilities/src/index.ts
index 2377571564989..76b147ac229fb 100644
--- a/packages/react-components/react-utilities/src/index.ts
+++ b/packages/react-components/react-utilities/src/index.ts
@@ -10,6 +10,8 @@ export {
   resolveShorthand,
   isResolvedShorthand,
   getIntrinsicElementProps,
+  getSlotClassNameProp_unstable,
+  SLOT_CLASS_NAME_PROP_SYMBOL,
   SLOT_ELEMENT_TYPE_SYMBOL,
   SLOT_RENDER_FUNCTION_SYMBOL,
 } from './compose/index';


Reviews:
user: login: behowell
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-03T18:38:09Z
user: login: behowell
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-03T18:58:43Z
user: login: github-actions[bot]
state: COMMENTED
author_association: NONE
submitted_at: 2025-04-03T18:59:21Z
user: login: CampbellOwen
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-03T20:19:21Z
user: login: sopranopillow
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-04T04:10:31Z
user: login: sopranopillow
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-04T04:28:28Z
user: login: sopranopillow
body: I think other than the overrideslots stuff it looks good. I like this approach since we keep it very internal aside from the function which we can deprecate later in the future
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-04T04:31:04Z
user: login: GeoffCox
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-04T17:39:39Z
user: login: GeoffCox
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-04T17:47:22Z
user: login: GeoffCox
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-04T17:50:35Z
user: login: behowell
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-04T19:11:56Z
user: login: behowell
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-08T19:47:26Z
user: login: ling1726
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-09T16:56:57Z
user: login: sopranopillow
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-04-09T18:37:48Z
user: login: behowell
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-09T21:12:41Z
user: login: behowell
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-09T21:16:39Z
user: login: khmakoto
state: APPROVED
author_association: MEMBER
submitted_at: 2025-04-10T21:04:37Z
user: login: CampbellOwen
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-10T21:45:32Z
user: login: behowell
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-10T23:15:56Z
user: login: CampbellOwen
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-10T23:54:28Z
user: login: micahgodbolt
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-14T17:00:29Z
user: login: ling1726
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-15T14:56:46Z
user: login: ling1726
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-15T14:59:49Z
user: login: micahgodbolt
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-15T16:05:21Z
user: login: behowell
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-15T16:59:41Z
user: login: behowell
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-15T17:02:23Z
user: login: behowell
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-15T20:51:58Z
user: login: behowell
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-15T21:01:16Z
user: login: behowell
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-15T21:04:57Z
user: login: ling1726
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-04-16T10:47:04Z

Review comments:
diff_hunk: @@ -38,7 +45,7 @@ export const ButtonCustomStyles = () => {
   const customStyleHooks: FluentProviderCustomStyleHooks = {
     useButtonStyles_unstable: (state: unknown) => {
       const componentState = state as ButtonState;
-      componentState.root.className = mergeClasses(componentState.root.className, styles.button);
path: apps/vr-tests-react-components/src/stories/CustomStyleHooks.stories.tsx
user: login: behowell
body: The refactoring for the `mergeClasses` calls and imports were done by the following copilot prompt:
```
Replace all calls to mergeClasses with overrideSlotClasses. Here's an example diff: 

-state.foo.className = mergeClasses(state.foo.className, styles.bar);
+overrideSlotClasses(state.foo, styles.bar);

Also remove the import for mergeClasses and import overrideSlotClasses from @fluentui/react-utilities.
```
created_at: 2025-04-03T18:38:09Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,9 @@
+import { mergeClasses } from '@griffel/react';
+import { SLOT_USER_CLASS_NAME_SYMBOL } from '../compose/constants';
+import { UnknownSlotProps } from '../compose/types';
+import { isSlot } from './isSlot';
+
+export const overrideSlotClasses = (slot: UnknownSlotProps, ...classNames: (string | false | undefined)[]) => {
+  const userClassName = isSlot(slot) ? slot[SLOT_USER_CLASS_NAME_SYMBOL] : undefined;
+  slot.className = mergeClasses(slot.className, ...classNames, userClassName);
+};
path: packages/react-components/react-utilities/src/compose/overrideSlotClasses.ts
user: login: behowell
body: Need to find the right package for this function to live. It depends on `@griffel/react`, so it can't be in react-utilities. There doesn't seem to be a great option for a package that depends on griffel, but isn't for a specific component. Maybe react-provider since it has the custom style provider, but I don't love it... 

Alternatively, we could provide a different function that doesn't directly call `mergeClasses`. For example, a `getSlotUserClassName` function that just returns the class name that must be applied after the custom style hook's styles. But I'm not sure if we want to support that long-term...
created_at: 2025-04-03T18:58:42Z
author_association: CONTRIBUTOR
diff_hunk: @@ -9,7 +9,8 @@ import type {
   ToggleButtonState,
path: apps/vr-tests-react-components/src/stories/CustomStyleHooks.stories.tsx
user: login: github-actions[bot]
body: ## 🕵🏾‍♀️ [<b data-uid='vrt-gist'></b> visual changes to review in the  Visual Change Report](https://aka.ms/visual-changes-review?organization=microsoft&project=fluentui&prId=34166&commitId=a5b0aeb26f72c71bb00da090bcd8fbf12651b7c8&env=prod&clientType=FLUENTUI)


  <details><summary><b>vr-tests-react-components/Avatar Converged</b> 1 screenshots</summary>

|Image Name|Diff(in Pixels)| Image Type
|-|-|-|
|vr-tests-react-components/Avatar Converged.badgeMask - RTL.normal.chromium.png|3| Changed|

</details>


  <details><summary><b>vr-tests-react-components/FluentProvider CustomStyleHooks</b> 1 screenshots</summary>

|Image Name|Diff(in Pixels)| Image Type
|-|-|-|
|vr-tests-react-components/FluentProvider CustomStyleHooks.Button with className.normal.chromium.png|0| Added|

</details>


  <details><summary><b>vr-tests-react-components/Positioning</b> 2 screenshots</summary>

|Image Name|Diff(in Pixels)| Image Type
|-|-|-|
|vr-tests-react-components/Positioning.Positioning end.updated 2 times.chromium.png|595| Changed|
|vr-tests-react-components/Positioning.Positioning end.chromium.png|628| Changed|

</details>


  <details><summary><b>vr-tests-react-components/Skeleton converged</b> 1 screenshots</summary>

|Image Name|Diff(in Pixels)| Image Type
|-|-|-|
|vr-tests-react-components/Skeleton converged.Translucent Skeleton with circle - High Contrast.default.chromium.png|1| Changed|

</details>




<div id="vrtCommentFluent UI"/>
created_at: 2025-04-03T18:59:21Z
author_association: NONE
diff_hunk: @@ -0,0 +1,9 @@
+import { mergeClasses } from '@griffel/react';
+import { SLOT_USER_CLASS_NAME_SYMBOL } from '../compose/constants';
+import { UnknownSlotProps } from '../compose/types';
+import { isSlot } from './isSlot';
+
+export const overrideSlotClasses = (slot: UnknownSlotProps, ...classNames: (string | false | undefined)[]) => {
path: packages/react-components/react-utilities/src/compose/overrideSlotClasses.ts
user: login: CampbellOwen
body: I wonder if it'd be possible to make the behaviour more self-explanatory with the name. e.g. `overrideSlotBaseClasses` to show that it's overriding the built-in styles, but _not_ overriding the user provided styles.
created_at: 2025-04-03T20:19:17Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,9 @@
+import { mergeClasses } from '@griffel/react';
+import { SLOT_USER_CLASS_NAME_SYMBOL } from '../compose/constants';
+import { UnknownSlotProps } from '../compose/types';
+import { isSlot } from './isSlot';
+
+export const overrideSlotClasses = (slot: UnknownSlotProps, ...classNames: (string | false | undefined)[]) => {
path: packages/react-components/react-utilities/src/compose/overrideSlotClasses.ts
user: login: sopranopillow
body: maybe `overrideDefaultSlotClasses`?
created_at: 2025-04-04T04:10:30Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,9 @@
+import { mergeClasses } from '@griffel/react';
+import { SLOT_USER_CLASS_NAME_SYMBOL } from '../compose/constants';
+import { UnknownSlotProps } from '../compose/types';
+import { isSlot } from './isSlot';
+
+export const overrideSlotClasses = (slot: UnknownSlotProps, ...classNames: (string | false | undefined)[]) => {
+  const userClassName = isSlot(slot) ? slot[SLOT_USER_CLASS_NAME_SYMBOL] : undefined;
+  slot.className = mergeClasses(slot.className, ...classNames, userClassName);
+};
path: packages/react-components/react-utilities/src/compose/overrideSlotClasses.ts
user: login: sopranopillow
body: we could also merge it ourselves though we would lose the extra work mergeClasses does and would have to restrict the classNames type to only string[]. Or let the user that provides the classnames call mergeClasses since they would have to do it from the custom style hooks anyways.
```ts
export const overrideSlotClasses = (slot: UnknownSlotProps, className: string) => {
  const userClassName = isSlot(slot) ? slot[SLOT_USER_CLASS_NAME_SYMBOL] : undefined;
  slot.className = `${slot.className} ${className} ${userClassName}`;
};

overrideSlotClasses(state, mergeClasses(class1, class2, class3))
```
this is not ideal but at least lets you move it to react-utilities.
created_at: 2025-04-04T04:28:27Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1,5 +1,5 @@
 import * as React from 'react';
-import { SLOT_ELEMENT_TYPE_SYMBOL, SLOT_RENDER_FUNCTION_SYMBOL } from './constants';
+import { SLOT_ELEMENT_TYPE_SYMBOL, SLOT_RENDER_FUNCTION_SYMBOL, SLOT_USER_CLASS_NAME_SYMBOL } from './constants';
path: packages/react-components/react-utilities/src/compose/types.ts
user: login: GeoffCox
body: Consider naming this to match up with the overrideSlotClasses - maybe SLOT_ORIGINAL_CLASS_NAME_SYMBOL, SLOT_OVERRIDE_CLASS_NAME_SYMBOL, SLOT_EXTERNAL_CLASS_NAME_SYMBOL or SLOT_CALLER_CLASS_NAME_SYMBOL.  User is kind of a confusing term because people might wonder if we mean the end user.  
created_at: 2025-04-04T17:39:39Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,9 @@
+import { mergeClasses } from '@griffel/react';
+import { SLOT_USER_CLASS_NAME_SYMBOL } from '../compose/constants';
+import { UnknownSlotProps } from '../compose/types';
+import { isSlot } from './isSlot';
+
+export const overrideSlotClasses = (slot: UnknownSlotProps, ...classNames: (string | false | undefined)[]) => {
+  const userClassName = isSlot(slot) ? slot[SLOT_USER_CLASS_NAME_SYMBOL] : undefined;
+  slot.className = mergeClasses(slot.className, ...classNames, userClassName);
+};
path: packages/react-components/react-utilities/src/compose/overrideSlotClasses.ts
user: login: GeoffCox
body: Is it that bad to add a dependency on griffel to react-utilities?  react-utilities-compat depends on griffel.
created_at: 2025-04-04T17:47:22Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,9 @@
+import { mergeClasses } from '@griffel/react';
+import { SLOT_USER_CLASS_NAME_SYMBOL } from '../compose/constants';
+import { UnknownSlotProps } from '../compose/types';
+import { isSlot } from './isSlot';
+
+export const overrideSlotClasses = (slot: UnknownSlotProps, ...classNames: (string | false | undefined)[]) => {
path: packages/react-components/react-utilities/src/compose/overrideSlotClasses.ts
user: login: GeoffCox
body: @behowell - Where will the existing fluent components call this? Maybe the hook that gives back the custom style hook could wrap the custom style hook call and call this afterwards if we wanted to make it automatic and not have to touch every component.
created_at: 2025-04-04T17:50:35Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,9 @@
+import { mergeClasses } from '@griffel/react';
+import { SLOT_USER_CLASS_NAME_SYMBOL } from '../compose/constants';
+import { UnknownSlotProps } from '../compose/types';
+import { isSlot } from './isSlot';
+
+export const overrideSlotClasses = (slot: UnknownSlotProps, ...classNames: (string | false | undefined)[]) => {
path: packages/react-components/react-utilities/src/compose/overrideSlotClasses.ts
user: login: behowell
body: The intent is for the custom style hook itself to call this function, as a replacement for `mergeClasses`. That way we aren't changing the behavior of existing hooks.

I looked briefly into a way to do this automatically so that style hooks don't need to be modified (perhaps aside from a single opt-in flag if we're worried about a breaking change), but it ended up being a bit hacky. I'll look into it a little more to see if there's another way...
created_at: 2025-04-04T19:11:56Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1,5 +1,5 @@
 import * as React from 'react';
-import { SLOT_ELEMENT_TYPE_SYMBOL, SLOT_RENDER_FUNCTION_SYMBOL } from './constants';
+import { SLOT_ELEMENT_TYPE_SYMBOL, SLOT_RENDER_FUNCTION_SYMBOL, SLOT_USER_CLASS_NAME_SYMBOL } from './constants';
path: packages/react-components/react-utilities/src/compose/types.ts
user: login: behowell
body: Renamed to `SLOT_ORIGINAL_CLASS_NAME_SYMBOL`
created_at: 2025-04-08T19:47:26Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,11 @@
+import { mergeClasses } from '@griffel/react';
+import { SLOT_ORIGINAL_CLASS_NAME_SYMBOL } from '../compose/constants';
+import type { SlotComponentType, UnknownSlotProps } from '../compose/types';
+
+export const overrideDefaultSlotClasses = (slot: UnknownSlotProps, ...classNames: (string | false | undefined)[]) => {
path: packages/react-components/react-utilities/src/compose/overrideDefaultSlotClasses.ts
user: login: ling1726
body: As discussed in tech sync, let's avoid a blessed class merging utility which could lead to confusion and abuse for the 99% of cases users should be using mergeClsases.

Let's just export a very obscure utiliity that returns the original class so that users make the explicit decision to take a dependency on it. This will buy us some breathing room to implement a long term next version of custom style hooks and also avoids a dependency on griffel
created_at: 2025-04-09T16:56:56Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,9 @@
+import { SLOT_ORIGINAL_CLASS_NAME_SYMBOL } from '../compose/constants';
+import type { UnknownSlotProps } from '../compose/types';
+
+export const getSlotClassNameProp = (slot: UnknownSlotProps) => {
path: packages/react-components/react-utilities/src/compose/getSlotClassNameProp.ts
user: login: sopranopillow
body: could we use a more descriptive name?

```suggestion
export const getSlotOriginalClassNameProp = (slot: UnknownSlotProps) => {
```
created_at: 2025-04-09T18:36:50Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,9 @@
+import { SLOT_ORIGINAL_CLASS_NAME_SYMBOL } from '../compose/constants';
+import type { UnknownSlotProps } from '../compose/types';
+
+export const getSlotClassNameProp = (slot: UnknownSlotProps) => {
path: packages/react-components/react-utilities/src/compose/getSlotClassNameProp.ts
user: login: behowell
body: Renamed to `getSlotOriginalClassNameProp`, and leaving this thread open for further bikeshedding on the name :)
created_at: 2025-04-09T21:12:41Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,9 @@
+import { mergeClasses } from '@griffel/react';
+import { SLOT_USER_CLASS_NAME_SYMBOL } from '../compose/constants';
+import { UnknownSlotProps } from '../compose/types';
+import { isSlot } from './isSlot';
+
+export const overrideSlotClasses = (slot: UnknownSlotProps, ...classNames: (string | false | undefined)[]) => {
+  const userClassName = isSlot(slot) ? slot[SLOT_USER_CLASS_NAME_SYMBOL] : undefined;
+  slot.className = mergeClasses(slot.className, ...classNames, userClassName);
+};
path: packages/react-components/react-utilities/src/compose/overrideSlotClasses.ts
user: login: behowell
body: Based on a discussion in tech sync, we're going to just export a getter for the original className.
created_at: 2025-04-09T21:16:39Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,9 @@
+import { SLOT_ORIGINAL_CLASS_NAME_SYMBOL } from '../compose/constants';
+import type { UnknownSlotProps } from '../compose/types';
+
+export const getSlotClassNameProp = (slot: UnknownSlotProps) => {
path: packages/react-components/react-utilities/src/compose/getSlotClassNameProp.ts
user: login: CampbellOwen
body: I think I find the "original" part a bit misleading because that makes me think of the classnames provided by the control itself rather than the supplemental classname provided by the user. i.e. `getSlotOriginalClassNameProp` might return something like `fui-Button`.

Either that, or `getSlotOriginalClassNameProp` would return the final result of `mergeClasses` within the component (the "original" classname before the custom style hook was run).

I don't have too strong feelings about it and am okay if `getSlotOriginalClassNameProp` is preferred, but I don't find it super intuitive 
created_at: 2025-04-10T21:45:32Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,9 @@
+import { SLOT_ORIGINAL_CLASS_NAME_SYMBOL } from '../compose/constants';
+import type { UnknownSlotProps } from '../compose/types';
+
+export const getSlotClassNameProp = (slot: UnknownSlotProps) => {
path: packages/react-components/react-utilities/src/compose/getSlotClassNameProp.ts
user: login: behowell
body: @CampbellOwen do you have another suggestion for the name?
created_at: 2025-04-10T23:15:56Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,9 @@
+import { SLOT_ORIGINAL_CLASS_NAME_SYMBOL } from '../compose/constants';
+import type { UnknownSlotProps } from '../compose/types';
+
+export const getSlotClassNameProp = (slot: UnknownSlotProps) => {
path: packages/react-components/react-utilities/src/compose/getSlotClassNameProp.ts
user: login: CampbellOwen
body: To be honest I like your original `getSlotClassNameProp` 😄. I don't think it's a big deal either way though
created_at: 2025-04-10T23:54:28Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,9 @@
+import { SLOT_ORIGINAL_CLASS_NAME_SYMBOL } from '../compose/constants';
+import type { UnknownSlotProps } from '../compose/types';
+
+export const getSlotClassNameProp = (slot: UnknownSlotProps) => {
path: packages/react-components/react-utilities/src/compose/getSlotClassNameProp.ts
user: login: micahgodbolt
body: i like short and sweet plus docs to explain. I think almost any name we pick will need some explanation. 
created_at: 2025-04-14T17:00:29Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,9 @@
+import { SLOT_ORIGINAL_CLASS_NAME_SYMBOL } from '../compose/constants';
+import type { UnknownSlotProps } from '../compose/types';
+
+export const getSlotClassNameProp = (slot: UnknownSlotProps) => {
path: packages/react-components/react-utilities/src/compose/getSlotClassNameProp.ts
user: login: ling1726
body: https://justflipacoin.com/
created_at: 2025-04-15T14:56:46Z
author_association: CONTRIBUTOR
diff_hunk: @@ -57,7 +68,11 @@ export const CompoundButtonCustomStyles = () => {
   const customStyleHooks: FluentProviderCustomStyleHooks = {
     useCompoundButtonStyles_unstable: (state: unknown) => {
       const componentState = state as CompoundButtonState;
-      componentState.root.className = mergeClasses(componentState.root.className, styles.button);
+      componentState.root.className = mergeClasses(
path: apps/vr-tests-react-components/src/stories/CustomStyleHooks.stories.tsx
user: login: ling1726
body: Should we be publicly documenting this in the docsite? As we discussed in tech sync, we should be using this to unblock advanced usage. The API we settled on is a good compromise to reduce the risk of abuse.

I would rather go the direction of - if someone knows to use this without asking us, then they probably know what they are doing. If we get reported this issue in the future we can either:

1. point to a long term solution
2. explain how to use this hack and that it shouldn't be used for any other use case
created_at: 2025-04-15T14:59:48Z
author_association: CONTRIBUTOR
diff_hunk: @@ -57,7 +68,11 @@ export const CompoundButtonCustomStyles = () => {
   const customStyleHooks: FluentProviderCustomStyleHooks = {
     useCompoundButtonStyles_unstable: (state: unknown) => {
       const componentState = state as CompoundButtonState;
-      componentState.root.className = mergeClasses(componentState.root.className, styles.button);
+      componentState.root.className = mergeClasses(
path: apps/vr-tests-react-components/src/stories/CustomStyleHooks.stories.tsx
user: login: micahgodbolt
body: yeah, happy to leave the story in the repo for reference, but not publish to docsite.
created_at: 2025-04-15T16:05:21Z
author_association: MEMBER
diff_hunk: @@ -57,7 +68,11 @@ export const CompoundButtonCustomStyles = () => {
   const customStyleHooks: FluentProviderCustomStyleHooks = {
     useCompoundButtonStyles_unstable: (state: unknown) => {
       const componentState = state as CompoundButtonState;
-      componentState.root.className = mergeClasses(componentState.root.className, styles.button);
+      componentState.root.className = mergeClasses(
path: apps/vr-tests-react-components/src/stories/CustomStyleHooks.stories.tsx
user: login: behowell
body: @ling1726 I wouldn't necessarily call this advanced usage. At least, it's not more advanced than any of the rest of the custom styling hook API. In fact, it's (currently) required to use this function to correctly implement a custom style hook.
created_at: 2025-04-15T16:59:41Z
author_association: CONTRIBUTOR
diff_hunk: @@ -57,7 +68,11 @@ export const CompoundButtonCustomStyles = () => {
   const customStyleHooks: FluentProviderCustomStyleHooks = {
     useCompoundButtonStyles_unstable: (state: unknown) => {
       const componentState = state as CompoundButtonState;
-      componentState.root.className = mergeClasses(componentState.root.className, styles.button);
+      componentState.root.className = mergeClasses(
path: apps/vr-tests-react-components/src/stories/CustomStyleHooks.stories.tsx
user: login: behowell
body: (FWIW this particular comment is on the vr-test story, which isn't the public docsite, but the AdvancedStylingTechniques.stories.mdx change is on the public docsite.)
created_at: 2025-04-15T17:02:22Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,9 @@
+import { SLOT_ORIGINAL_CLASS_NAME_SYMBOL } from '../compose/constants';
+import type { UnknownSlotProps } from '../compose/types';
+
+export const getSlotClassNameProp = (slot: UnknownSlotProps) => {
path: packages/react-components/react-utilities/src/compose/getSlotClassNameProp.ts
user: login: behowell
body: > https://justflipacoin.com/

Alright it's decided, we're going to name it `HEADS`. Or actually, `getSlotClassNameProp_unstable`.
created_at: 2025-04-15T20:51:58Z
author_association: CONTRIBUTOR
diff_hunk: @@ -57,7 +68,11 @@ export const CompoundButtonCustomStyles = () => {
   const customStyleHooks: FluentProviderCustomStyleHooks = {
     useCompoundButtonStyles_unstable: (state: unknown) => {
       const componentState = state as CompoundButtonState;
-      componentState.root.className = mergeClasses(componentState.root.className, styles.button);
+      componentState.root.className = mergeClasses(
path: apps/vr-tests-react-components/src/stories/CustomStyleHooks.stories.tsx
user: login: behowell
body: I'll pull out the public docsite change to another PR, so this one can be unblocked for now.
created_at: 2025-04-15T21:01:16Z
author_association: CONTRIBUTOR
diff_hunk: @@ -57,7 +68,11 @@ export const CompoundButtonCustomStyles = () => {
   const customStyleHooks: FluentProviderCustomStyleHooks = {
     useCompoundButtonStyles_unstable: (state: unknown) => {
       const componentState = state as CompoundButtonState;
-      componentState.root.className = mergeClasses(componentState.root.className, styles.button);
+      componentState.root.className = mergeClasses(
path: apps/vr-tests-react-components/src/stories/CustomStyleHooks.stories.tsx
user: login: behowell
body: * #34266
created_at: 2025-04-15T21:04:57Z
author_association: CONTRIBUTOR

PR comments:
user: login: github-actions[bot]
created_at: 2025-04-03T18:42:18Z
body: ## 📊 Bundle size report

| Package & Exports | Baseline (minified/GZIP) | PR    | Change     |
| :---------------- | -----------------------: | ----: | ---------: |
| <samp>react-accordion</samp> <br /> <abbr title='bundle-size/Accordion.fixture.js'>Accordion (including children components)</abbr> | `107.764 kB`<br />`33.081 kB` | `107.842 kB`<br />`33.115 kB` | `78 B` <img aria-hidden="true" src="https://microsoft.github.io/monosize/images/increase.png" /><br />`34 B` <img aria-hidden="true" src="https://microsoft.github.io/monosize/images/increase.png" />|
| <samp>react-avatar</samp> <br /> <abbr title='bundle-size/Avatar.fixture.js'>Avatar</abbr> | `49.329 kB`<br />`15.824 kB` | `49.41 kB`<br />`15.856 kB` | `81 B` <img aria-hidden="true" src="https://microsoft.github.io/monosize/images/increase.png" /><br />`32 B` <img aria-hidden="true" src="https://microsoft.github.io/monosize/images/increase.png" />|
| <samp>react-avatar</samp> <br /> <abbr title='bundle-size/AvatarGroup.fixture.js'>AvatarGroup</abbr> | `20.132 kB`<br />`7.976 kB` | `20.21 kB`<br />`8.004 kB` | `78 B` <img aria-hidden="true" src="https://microsoft.github.io/monosize/images/increase.png" /><br />`28 B` <img aria-hidden="true" src="https://microsoft.github.io/monosize/images/increase.png" />|
| <samp>react-avatar</samp> <br /> <abbr title='bundle-size/AvatarGroupItem.fixture.js'>AvatarGroupItem</abbr> | `63.473 kB`<br />`20.043 kB` | `63.554 kB`<br />`20.073 kB` | `81 B` <img aria-hidden="true" src="https://microsoft.github.io/monosize/images/increase.png" /><br />`30 B` <img aria-hidden="true" src="https://microsoft.github.io/monosize/images/increase.png" />|
| <samp>react-badge</samp> <br /> <abbr title='bundle-size/Badge.fixture.js'>Badge</abbr> | `25.959 kB`<br />`8.598 kB` | `26.037 kB`<br />`8.624 kB` | `78 B` <img aria-hidden="true" src="https://microsoft.github.io/monosize/images/increase.png" /><br />`26 B` <img aria-hidden="true" src="https://microsoft.github.io/monosize/images/increase.png" />|
| <samp>react-badge</samp> <br /> <abbr title='bundle-size/CounterBadge.fixture.js'>CounterBadge</abbr> | `26.738 kB`<br />`8.872 kB` | `26.816 kB`<br />`8.899 kB` | `78 B` <img aria-hidden="true" src="https://microsoft.github.io/monosize/images/increase.png" /><br />`27 B` <img aria-hidden="true" src="https://microsoft.github.io/monosize/images/increase.png" />|
| <samp>react-badge</samp> <br /> <abbr title='bundle-size/PresenseBadge.fixture.js'>PresenceBadge</abbr> | `25.745 kB`<br />`9.463 kB` | `25.823 kB`<br />`9.491 kB` | `78 B` <img aria-hidden="true" src="https://microsoft.github.io/monosize/images/increase.png" /><br />`28 B` <img aria-hidden="true" src="https://microsoft.github.io/monosize/images/increase.png" />|
| <samp>react-breadcrumb</samp> <br /> <abbr title='bundle-size/index.fixture.js'>@fluentui/react-breadcrumb - package</abbr> | `114.824 kB`<br />`31.78 kB` | `114.904 kB`<br />`31.809 kB` | `80 B` <img aria-hidden="true" src="https://microsoft.github.io/monosize/images/increase.png" /><br />`29 B` <img aria-hidden="true" src="https://microsoft.github.io/monosize/images/increase.png" />|
| <samp>react-button</samp> <br /> <abbr title='bundle-size/Button.fixture.js'>Button</abbr> | `37.602 kB`<br />`10.858 kB` | `37.68 kB`<br />`10.889 kB` | `78 B` <img aria-hidden="true" src="https://microsoft.github.io/monosize/images/increase.png" /><br />`31 B` <img aria-hidden="true" src="https://microsoft.github.io/monosize/images/increase.png" />|
| <samp>react-button</samp> <br /> <abbr title='bundle-size/CompoundButton.fixture.js'>CompoundButton</abbr> | `44.016 kB`<br />`12.162 kB` | `44.094 kB`<br />`12.191 kB` | `78 B` <img aria-hidden="true" src="https://microsoft.github.io/monosize/images/increase.png" /><br />`29 B` <img aria-hidden="true" src="https://microsoft.github.io/monosize/images/increase.png" />|
| <samp>react-button</samp> <br /> <abbr title='bundle-size/MenuButton.fixture.js'>MenuButton</abbr> | `42.417 kB`<br />`12.209 kB` | `42.495 kB`<br />`12.237 kB` | `78 B` <img aria-hidden="true" src="https://microsoft.github.io/monosize/images/increase.png" /><br />`28 B` <img aria-hidden="true" src="https://microsoft.github.io/monosize/images/increase.png" />|
| <samp>react-button</samp> <br /> <abbr title='bundle-size/SplitButton.fixture.js'>SplitButton</abbr> | `50.482 kB`<br />`13.776 kB` | `50.561 kB`<br />`13.803 kB` | `79 B` <img aria-hidden="true" src="https://microsoft.github.io/monosize/images/increase.png" /><br />`27 B` <img aria-hidden="true" src="https://microsoft.github.io/monosize/images/increase.png" />|
| <samp>react-button</samp> <br /> <abbr title='bundle-size/ToggleButton.fixture.js'>ToggleButton</abbr> | `53.535 kB`<br />`12.631 kB` | `53.612 kB`<br />`12.665 kB` | `77 B` <img aria-hidden="true" src="https://microsoft.github.io/monosize/images/increase.png" /><br />`34 B` <img aria-hidden="true" src="https://microsoft.github.io/monosize/images/increase.png" />|
| <samp>react-card</samp> <br /> <abbr title='bundle-size/Card.All.fixture.js'>Card - All</abbr> | `102.158 kB`<br />`28.837 kB` | `102.239 kB`<br />`28.862 kB` | `81 B` <img aria-hidden="true" src="https://microsoft.github.io/monosize/images/increase.png" /><br />`25 B` <img aria-hidden="true" src="https://microsoft.github.io/monosize/images/increase.png" />|
| <samp>react-card</samp> <br /> <abbr title='bundle-size/Card.fixture.js'>Card</abbr> | `94.804 kB`<br />`27.014 kB` | `94.881 kB`<br />`27.038 kB` | `77 B` <img aria-hidden="true" src="https://microsoft.github.io/monosize/images/increase.png" /><br />`24 B` <img aria-hidden="true" src="https://microsoft.github.io/monosize/images/increase.png" />|
| <samp>react-card</samp> <br /> <abbr title='bundle-size/CardFooter.fixture.js'>CardFooter</abbr> | `14.56 kB`<br />`5.874 kB` | `14.637 kB`<br />`5.902 kB` | `77 B` <img aria-hidden="true" src="https://microsoft.github.io/monosize/images/increase.png" /><br />`28 B` <img aria-hidden="true" src="https://microsoft.github.io/monosize/images/increase.png" />|
| <samp>react-card</samp> <br /> <abbr title='bundle-size/CardHeader.fixture.js'>CardHeader</abbr> | `17.093 kB`<br />`6.745 kB` | `17.17 kB`<br />`6.769 kB` | `77 B` <img aria-hidden="true" src="https://microsoft.github.io/monosize/images/increase.png" /><br />`24 B` <img aria-hidden="true" src="https://microsoft.github.io/monosize/images/increase.png" />|
| <samp>react-card</samp> <br /> <abbr title='bundle-size/CardPreview.fixture.js'>CardPreview</abbr> | `14.626 kB`<br />`6 kB` | `14.703 kB`<br />`6.027 kB` | `77 B` <img aria-hidden="true" src="https://microsoft.github.io/monosize/images/increase.png" /><br />`27 B` <img aria-hidden="true" src="https://microsoft.github.io/monosize/images/increase.png" />|
| <samp>react-checkbox</samp> <br /> <abbr title='bundle-size/Checkbox.fixture.js'>Checkbox</abbr> | `35.144 kB`<br />`12.085 kB` | `35.221 kB`<br />`12.11 kB` | `77 B` <img aria-hidden="true" src="https://microsoft.github.io/monosize/images/increase.png" /><br />`25 B` <img aria-hidden="true" src="https://microsoft.github.io/monosize/images/increase.png" />|
| <samp>react-color-picker</samp> <br /> <abbr title='bundle-size/ColorArea.fixture.js'>ColorArea</abbr> | `50.206 kB`<br />`17.662 kB` | `50.284 kB`<br />`17.696 kB` | `78 B` <img aria-hidden="true" src="https://microsoft.github.io/monosize/images/increase.png" /><br />`34 B` <img aria-hidden="true" src="https://microsoft.github.io/monosize/images/increase.png" />|
| <samp>react-color-picker</samp> <br /> <abbr title='bundle-size/ColorPicker.fixture.js'>ColorPicker</abbr> | `18.848 kB`<br />`7.503 kB` | `18.925 kB`<br />`7.529 kB` | `77 B` <img aria-hidden="true" src="https://microsoft.github.io/monosize/images/increase.png" /><br />`26 B` <img aria-hidden="true" src="https://microsoft.github.io/monosize/images/increase.png" />|
| <samp>react-color-picker</samp> <br /> <abbr title='bundle-size/ColorSlider.fixture.js'>ColorSlider</abbr> | `42.392 kB`<br />`15.711 kB` | `42.47 kB`<br />`15.741 kB` | `78 B` <img aria-hidden="true" src="https://microsoft.github.io/monosize/images/increase.png" /><br />`30 B` <img aria-hidden="true" src="https://microsoft.github.io/monosize/images/increase.png" />|
| <samp>react-combobox</samp> <br /> <abbr title='bundle-size/Combobox.fixture.js'>Combobox (including child components)</abbr> | `106.21 kB`<br />`34.695 kB` | `106.288 kB`<br />`34.738 kB` | `78 B` <img aria-hidden="true" src="https://microsoft.github.io/monosize/images/increase.png" /><br />`43 B` <img aria-hidden="true" src="https://microsoft.github.io/monosize/images/increase.png" />|
| <samp>react-combobox</samp> <br /> <abbr title='bundle-size/Dropdown.fixture.js'>Dropdown (including child components)</abbr> | `106.835 kB`<br />`34.631 kB` | `106.913 kB`<br />`34.661 kB` | `78 B` <img aria-hidden="true" src="https://microsoft.github.io/monosize/images/increase.png" /><br />`30 B` <img aria-hidden="true" src="https://microsoft.github.io/monosize/images/increase.png" />|
| <samp>react-components</samp> <br /> <abbr title='bundle-size/ButtonProviderAndTheme.fixture.js'>react-components: Button, FluentProvider & webLightTheme</abbr> | `69.66 kB`<br />`20.247 kB` | `69.737 kB`<br />`20.275 kB` | `77 B` <img aria-hidden="true" src="https://microsoft.github.io/monosize/images/increase.png" /><br />`28 B` <img aria-hidden="true" src="https://microsoft.github.io/monosize/images/increase.png" />|
| <samp>react-components</samp> <br /> <abbr title='bundle-size/MultipleComponents.fixture.js'>react-components: Accordion, Button, FluentProvider, Image, Menu, Popover</abbr> | `225.68 kB`<br />`65.463 kB` | `225.757 kB`<br />`65.488 kB` | `77 B` <img aria-hidden="true" src="https://microsoft.github.io/monosize/images/increase.png" /><br />`25 B` <img aria-hidden="true" src="https://microsoft.github.io/monosize/images/increase.png" />|
| <samp>react-components</samp> <br /> <abbr title='bundle-size/ProviderAndTheme.fixture.js'>react-components: FluentProvider & webLightTheme</abbr> | `44.495 kB`<br />`14.606 kB` | `44.573 kB`<br />`14.637 kB` | `78 B` <img aria-hidden="true" src="https://microsoft.github.io/monosize/images/increase.png" /><br />`31 B` <img aria-hidden="true" src="https://microsoft.github.io/monosize/images/increase.png" />|
| <samp>react-components</samp> <br /> <abbr title='bundle-size/ReactComponents.fixture.js'>react-components: entire library</abbr> | `1.228 MB`<br />`309.457 kB` | `1.228 MB`<br />`309.469 kB` | `177 B` <img aria-hidden="true" src="https://microsoft.github.io/monosize/images/increase.png" /><br />`12 B` <img aria-hidden="true" src="https://microsoft.github.io/monosize/images/increase.png" />|
| <samp>react-datepicker-compat</samp> <br /> <abbr title='bundle-size/DatePicker.fixture.js'>DatePicker Compat</abbr> | `226.104 kB`<br />`64.092 kB` | `226.182 kB`<br />`64.128 kB` | `78 B` <img aria-hidden="true" src="https://microsoft.github.io/monosize/images/increase.png" /><br />`36 B` <img aria-hidden="true" src="https://microsoft.github.io/monosize/images/increase.png" />|
| <samp>react-dialog</samp> <br /> <abbr title='bundle-size/Dialog.fixture.js'>Dialog (including children components)</abbr> | `102.069 kB`<br />`30.655 kB` | `102.147 kB`<br />`30.683 kB` | `78 B` <img aria-hidden="true" src="https://microsoft.github.io/monosize/images/increase.png" /><br />`28 B` <img aria-hidden="true" src="https://microsoft.github.io/monosize/images/increase.png" />|
| <samp>react-divider</samp> <br /> <abbr title='bundle-size/Divider.fixture.js'>Divider</abbr> | `21.354 kB`<br />`7.963 kB` | `21.431 kB`<br />`7.984 kB` | `77 B` <img aria-hidden="true" src="https://microsoft.github.io/monosize/images/increase.png" /><br />`21 B` <img aria-hidden="true" src="https://microsoft.github.io/monosize/images/increase.png" />|
| <samp>react-field</samp> <br /> <abbr title='bundle-size/Field.fixture.js'>Field</abbr> | `23.471 kB`<br />`8.909 kB` | `23.548 kB`<br />`8.941 kB` | `77 B` <img aria-hidden="true" src="https://microsoft.github.io/monosize/images/increase.png" /><br />`32 B` <img aria-hidden="true" src="https://microsoft.github.io/monosize/images/increase.png" />|
| <samp>react-image</samp> <br /> <abbr title='bundle-size/Image.fixture.js'>Image</abbr> | `15.386 kB`<br />`6.243 kB` | `15.463 kB`<br />`6.271 kB` | `77 B` <img aria-hidden="true" src="https://microsoft.github.io/monosize/images/increase.png" /><br />`28 B` <img aria-hidden="true" src="https://microsoft.github.io/monosize/images/increase.png" />|
| <samp>react-input</samp> <br /> <abbr title='bundle-size/Input.fixture.js'>Input</abbr> | `28.04 kB`<br />`9.451 kB` | `28.117 kB`<br />`9.479 kB` | `77 B` <img aria-hidden="true" src="https://microsoft.github.io/monosize/images/increase.png" /><br />`28 B` <img aria-hidden="true" src="https://microsoft.github.io/monosize/images/increase.png" />|
| <samp>react-jsx-runtime</samp> <br /> <abbr title='bundle-size/Classic.fixture.js'>Classic Pragma</abbr> | `1.057 kB`<br />`530 B` | `1.101 kB`<br />`550 B` | `44 B` <img aria-hidden="true" src="https://microsoft.github.io/monosize/images/increase.png" /><br />`20 B` <img aria-hidden="true" src="https://microsoft.github.io/monosize/images/increase.png" />|
| <samp>react-jsx-runtime</samp> <br /> <abbr title='bundle-size/DevRuntime.fixture.js'>JSX Dev Runtime</abbr> | `3.771 kB`<br />`1.643 kB` | `3.815 kB`<br />`1.664 kB` | `44 B` <img aria-hidden="true" src="https://microsoft.github.io/monosize/images/increase.png" /><br />`21 B` <img aria-hidden="true" src="https://microsoft.github.io/monosize/images/increase.png" />|
| <samp>react-jsx-runtime</samp> <br /> <abbr title='bundle-size/Runtime.fixture.js'>JSX Runtime</abbr> | `4.367 kB`<br />`1.874 kB` | `4.411 kB`<br />`1.894 kB` | `44 B` <img aria-hidden="true" src="https://microsoft.github.io/monosize/images/increase.png" /><br />`20 B` <img aria-hidden="true" src="https://microsoft.github.io/monosize/images/increase.png" />|
| <samp>react-label</samp> <br /> <abbr title='bundle-size/Label.fixture.js'>Label</abbr> | `14.697 kB`<br />`5.999 kB` | `14.774 kB`<br />`6.025 kB` | `77 B` <img aria-hidden="true" src="https://microsoft.github.io/monosize/images/increase.png" /><br />`26 B` <img aria-hidden="true" src="https://microsoft.github.io/monosize/images/increase.png" />|
| <samp>react-link</samp> <br /> <abbr title='bundle-size/Label.fixture.js'>Link</abbr> | `17.625 kB`<br />`7.146 kB` | `17.702 kB`<br />`7.169 kB` | `77 B` <img aria-hidden="true" src="https://microsoft.github.io/monosize/images/increase.png" /><br />`23 B` <img aria-hidden="true" src="https://microsoft.github.io/monosize/images/increase.png" />|
| <samp>react-list</samp> <br /> <abbr title='bundle-size/List.fixture.js'>List</abbr> | `89.441 kB`<br />`26.656 kB` | `89.519 kB`<br />`26.679 kB` | `78 B` <img aria-hidden="true" src="https://microsoft.github.io/monosize/images/increase.png" /><br />`23 B` <img aria-hidden="true" src="https://microsoft.github.io/monosize/images/increase.png" />|
| <samp>react-list</samp> <br /> <abbr title='bundle-size/ListItem.fixture.js'>ListItem</abbr> | `112.778 kB`<br />`33.437 kB` | `112.856 kB`<br />`33.461 kB` | `78 B` <img aria-hidden="true" src="https://microsoft.github.io/monosize/images/increase.png" /><br />`24 B` <img aria-hidden="true" src="https://microsoft.github.io/monosize/images/increase.png" />|
| <samp>react-menu</samp> <br /> <abbr title='bundle-size/Menu.fixture.js'>Menu (including children components)</abbr> | `155.577 kB`<br />`46.851 kB` | `155.655 kB`<br />`46.879 kB` | `78 B` <img aria-hidden="true" src="https://microsoft.github.io/monosize/images/increase.png" /><br />`28 B` <img aria-hidden="true" src="https://microsoft.github.io/monosize/images/increase.png" />|
| <samp>react-menu</samp> <br /> <abbr title='bundle-size/Menu.Selectable.fixture.js'>Menu  (including selectable components)</abbr> | `158.559 kB`<br />`47.451 kB` | `158.637 kB`<br />`47.477 kB` | `78 B` <img aria-hidden="true" src="https://microsoft.github.io/monosize/images/increase.png" /><br />`26 B` <img aria-hidden="true" src="https://microsoft.github.io/monosize/images/increase.png" />|
| <samp>react-message-bar</samp> <br /> <abbr title='bundle-size/Default.fixture.js'>MessageBar (all components)</abbr> | `24.904 kB`<br />`9.273 kB` | `24.982 kB`<br />`9.301 kB` | `78 B` <img aria-hidden="true" src="https://microsoft.github.io/monosize/images/increase.png" /><br />`28 B` <img aria-hidden="true" src="https://microsoft.github.io/monosize/images/increase.png" />|
| <samp>react-persona</samp> <br /> <abbr title='bundle-size/Persona.fixture.js'>Persona</abbr> | `56.22 kB`<br />`17.704 kB` | `56.301 kB`<br />`17.736 kB` | `81 B` <img aria-hidden="true" src="https://microsoft.github.io/monosize/images/increase.png" /><br />`32 B` <img aria-hidden="true" src="https://microsoft.github.io/monosize/images/increase.png" />|
| <samp>react-popover</samp> <br /> <abbr title='bundle-size/Popover.fixture.js'>Popover</abbr> | `131.074 kB`<br />`41.013 kB` | `131.152 kB`<br />`41.032 kB` | `78 B` <img aria-hidden="true" src="https://microsoft.github.io/monosize/images/increase.png" /><br />`19 B` <img aria-hidden="true" src="https://microsoft.github.io/monosize/images/increase.png" />|
| <samp>react-progress</samp> <br /> <abbr title='bundle-size/ProgressBar.fixture.js'>ProgressBar</abbr> | `17.11 kB`<br />`6.899 kB` | `17.188 kB`<br />`6.921 kB` | `78 B` <img aria-hidden="true" src="https://microsoft.github.io/monosize/images/increase.png" /><br />`22 B` <img aria-hidden="true" src="https://microsoft.github.io/monosize/images/increase.png" />|
| <samp>react-provider</samp> <br /> <abbr title='bundle-size/FluentProvider.fixture.js'>FluentProvider</abbr> | `24.671 kB`<br />`8.909 kB` | `24.749 kB`<br />`8.939 kB` | `78 B` <img aria-hidden="true" src="https://microsoft.github.io/monosize/images/increase.png" /><br />`30 B` <img aria-hidden="true" src="https://microsoft.github.io/monosize/images/increase.png" />|
| <samp>react-radio</samp> <br /> <abbr title='bundle-size/Radio.fixture.js'>Radio</abbr> | `32.698 kB`<br />`10.351 kB` | `32.775 kB`<br />`10.379 kB` | `77 B` <img aria-hidden="true" src="https://microsoft.github.io/monosize/images/increase.png" /><br />`28 B` <img aria-hidden="true" src="https://microsoft.github.io/monosize/images/increase.png" />|
| <samp>react-radio</samp> <br /> <abbr title='bundle-size/RadioGroup.fixture.js'>RadioGroup</abbr> | `15.788 kB`<br />`6.431 kB` | `15.865 kB`<br />`6.464 kB` | `77 B` <img aria-hidden="true" src="https://microsoft.github.io/monosize/images/increase.png" /><br />`33 B` <img aria-hidden="true" src="https://microsoft.github.io/monosize/images/increase.png" />|
| <samp>react-select</samp> <br /> <abbr title='bundle-size/Select.fixture.js'>Select</abbr> | `27.758 kB`<br />`10.131 kB` | `27.835 kB`<br />`10.162 kB` | `77 B` <img aria-hidden="true" src="https://microsoft.github.io/monosize/images/increase.png" /><br />`31 B` <img aria-hidden="true" src="https://microsoft.github.io/monosize/images/increase.png" />|
| <samp>react-slider</samp> <br /> <abbr title='bundle-size/Slider.fixture.js'>Slider</abbr> | `38.255 kB`<br />`12.825 kB` | `38.332 kB`<br />`12.851 kB` | `77 B` <img aria-hidden="true" src="https://microsoft.github.io/monosize/images/increase.png" /><br />`26 B` <img aria-hidden="true" src="https://microsoft.github.io/monosize/images/increase.png" />|
| <samp>react-spinbutton</samp> <br /> <abbr title='bundle-size/SpinButton.fixture.js'>SpinButton</abbr> | `35.24 kB`<br />`11.754 kB` | `35.318 kB`<br />`11.787 kB` | `78 B` <img aria-hidden="true" src="https://microsoft.github.io/monosize/images/increase.png" /><br />`33 B` <img aria-hidden="true" src="https://microsoft.github.io/monosize/images/increase.png" />|
| <samp>react-spinner</samp> <br /> <abbr title='bundle-size/Spinner.fixture.js'>Spinner</abbr> | `25.271 kB`<br />`8.547 kB` | `25.35 kB`<br />`8.573 kB` | `79 B` <img aria-hidden="true" src="https://microsoft.github.io/monosize/images/increase.png" /><br />`26 B` <img aria-hidden="true" src="https://microsoft.github.io/monosize/images/increase.png" />|
| <samp>react-swatch-picker</samp> <br /> <abbr title='bundle-size/index.fixture.js'>@fluentui/react-swatch-picker - package</abbr> | `106.658 kB`<br />`30.815 kB` | `106.736 kB`<br />`30.84 kB` | `78 B` <img aria-hidden="true" src="https://microsoft.github.io/monosize/images/increase.png" /><br />`25 B` <img aria-hidden="true" src="https://microsoft.github.io/monosize/images/increase.png" />|
| <samp>react-switch</samp> <br /> <abbr title='bundle-size/Switch.fixture.js'>Switch</abbr> | `35.345 kB`<br />`11.322 kB` | `35.422 kB`<br />`11.344 kB` | `77 B` <img aria-hidden="true" src="https://microsoft.github.io/monosize/images/increase.png" /><br />`22 B` <img aria-hidden="true" src="https://microsoft.github.io/monosize/images/increase.png" />|
| <samp>react-table</samp> <br /> <abbr title='bundle-size/DataGrid.fixture.js'>DataGrid</abbr> | `161.223 kB`<br />`45.712 kB` | `161.31 kB`<br />`45.735 kB` | `87 B` <img aria-hidden="true" src="https://microsoft.github.io/monosize/images/increase.png" /><br />`23 B` <img aria-hidden="true" src="https://microsoft.github.io/monosize/images/increase.png" />|
| <samp>react-table</samp> <br /> <abbr title='bundle-size/PrimitivesOnly.fixture.js'>Table (Primitives only)</abbr> | `42.692 kB`<br />`13.862 kB` | `42.77 kB`<br />`13.889 kB` | `78 B` <img aria-hidden="true" src="https://microsoft.github.io/monosize/images/increase.png" /><br />`27 B` <img aria-hidden="true" src="https://microsoft.github.io/monosize/images/increase.png" />|
| <samp>react-table</samp> <br /> <abbr title='bundle-size/TableAsDataGrid.fixture.js'>Table as DataGrid</abbr> | `131.995 kB`<br />`36.583 kB` | `132.073 kB`<br />`36.612 kB` | `78 B` <img aria-hidden="true" src="https://microsoft.github.io/monosize/images/increase.png" /><br />`29 B` <img aria-hidden="true" src="https://microsoft.github.io/monosize/images/increase.png" />|
| <samp>react-table</samp> <br /> <abbr title='bundle-size/TableSelectionOnly.fixture.js'>Table (Selection only)</abbr> | `70.562 kB`<br />`20.007 kB` | `70.64 kB`<br />`20.037 kB` | `78 B` <img aria-hidden="true" src="https://microsoft.github.io/monosize/images/increase.png" /><br />`30 B` <img aria-hidden="true" src="https://microsoft.github.io/monosize/images/increase.png" />|
| <samp>react-table</samp> <br /> <abbr title='bundle-size/TableSortOnly.fixture.js'>Table (Sort only)</abbr> | `69.205 kB`<br />`19.618 kB` | `69.283 kB`<br />`19.651 kB` | `78 B` <img aria-hidden="true" src="https://microsoft.github.io/monosize/images/increase.png" /><br />`33 B` <img aria-hidden="true" src="https://microsoft.github.io/monosize/images/increase.png" />|
| <samp>react-tag-picker</samp> <br /> <abbr title='bundle-size/Package.fixture.js'>@fluentui/react-tag-picker - package</abbr> | `187.108 kB`<br />`56.187 kB` | `187.186 kB`<br />`56.223 kB` | `78 B` <img aria-hidden="true" src="https://microsoft.github.io/monosize/images/increase.png" /><br />`36 B` <img aria-hidden="true" src="https://microsoft.github.io/monosize/images/increase.png" />|
| <samp>react-tags</samp> <br /> <abbr title='bundle-size/InteractionTag.fixture.js'>InteractionTag</abbr> | `15.461 kB`<br />`6.224 kB` | `15.538 kB`<br />`6.249 kB` | `77 B` <img aria-hidden="true" src="https://microsoft.github.io/monosize/images/increase.png" /><br />`25 B` <img aria-hidden="true" src="https://microsoft.github.io/monosize/images/increase.png" />|
| <samp>react-tags</samp> <br /> <abbr title='bundle-size/tag.fixture.js'>Tag</abbr> | `30.261 kB`<br />`9.904 kB` | `30.339 kB`<br />`9.927 kB` | `78 B` <img aria-hidden="true" src="https://microsoft.github.io/monosize/images/increase.png" /><br />`23 B` <img aria-hidden="true" src="https://microsoft.github.io/monosize/images/increase.png" />|
| <samp>react-tags</samp> <br /> <abbr title='bundle-size/TagGroup.fixture.js'>TagGroup</abbr> | `83.783 kB`<br />`24.857 kB` | `83.861 kB`<br />`24.88 kB` | `78 B` <img aria-hidden="true" src="https://microsoft.github.io/monosize/images/increase.png" /><br />`23 B` <img aria-hidden="true" src="https://microsoft.github.io/monosize/images/increase.png" />|
| <samp>react-text</samp> <br /> <abbr title='bundle-size/Text.Default.fixture.js'>Text - Default</abbr> | `17.087 kB`<br />`6.731 kB` | `17.164 kB`<br />`6.756 kB` | `77 B` <img aria-hidden="true" src="https://microsoft.github.io/monosize/images/increase.png" /><br />`25 B` <img aria-hidden="true" src="https://microsoft.github.io/monosize/images/increase.png" />|
| <samp>react-text</samp> <br /> <abbr title='bundle-size/Text.Wrappers.fixture.js'>Text - Wrappers</abbr> | `20.268 kB`<br />`7.055 kB` | `20.345 kB`<br />`7.086 kB` | `77 B` <img aria-hidden="true" src="https://microsoft.github.io/monosize/images/increase.png" /><br />`31 B` <img aria-hidden="true" src="https://microsoft.github.io/monosize/images/increase.png" />|
| <samp>react-textarea</samp> <br /> <abbr title='bundle-size/Textarea.fixture.js'>Textarea</abbr> | `26.607 kB`<br />`9.762 kB` | `26.684 kB`<br />`9.786 kB` | `77 B` <img aria-hidden="true" src="https://microsoft.github.io/monosize/images/increase.png" /><br />`24 B` <img aria-hidden="true" src="https://microsoft.github.io/monosize/images/increase.png" />|
| <samp>react-timepicker-compat</samp> <br /> <abbr title='bundle-size/TimePicker.fixture.js'>TimePicker</abbr> | `109.186 kB`<br />`36.259 kB` | `109.264 kB`<br />`36.293 kB` | `78 B` <img aria-hidden="true" src="https://microsoft.github.io/monosize/images/increase.png" /><br />`34 B` <img aria-hidden="true" src="https://microsoft.github.io/monosize/images/increase.png" />|
| <samp>react-toast</samp> <br /> <abbr title='bundle-size/Toast.fixture.js'>Toast (including Toaster)</abbr> | `102.206 kB`<br />`30.75 kB` | `102.283 kB`<br />`30.776 kB` | `77 B` <img aria-hidden="true" src="https://microsoft.github.io/monosize/images/increase.png" /><br />`26 B` <img aria-hidden="true" src="https://microsoft.github.io/monosize/images/increase.png" />|
| <samp>react-tooltip</samp> <br /> <abbr title='bundle-size/Tooltip.fixture.js'>Tooltip</abbr> | `57.183 kB`<br />`19.943 kB` | `57.261 kB`<br />`19.971 kB` | `78 B` <img aria-hidden="true" src="https://microsoft.github.io/monosize/images/increase.png" /><br />`28 B` <img aria-hidden="true" src="https://microsoft.github.io/monosize/images/increase.png" />|
| <samp>react-tree</samp> <br /> <abbr title='bundle-size/FlatTree.fixture.js'>FlatTree</abbr> | `148.887 kB`<br />`42.688 kB` | `148.966 kB`<br />`42.733 kB` | `79 B` <img aria-hidden="true" src="https://microsoft.github.io/monosize/images/increase.png" /><br />`45 B` <img aria-hidden="true" src="https://microsoft.github.io/monosize/images/increase.png" />|
| <samp>react-tree</samp> <br /> <abbr title='bundle-size/PersonaFlatTree.fixture.js'>PersonaFlatTree</abbr> | `149.639 kB`<br />`42.826 kB` | `149.718 kB`<br />`42.868 kB` | `79 B` <img aria-hidden="true" src="https://microsoft.github.io/monosize/images/increase.png" /><br />`42 B` <img aria-hidden="true" src="https://microsoft.github.io/monosize/images/increase.png" />|
| <samp>react-tree</samp> <br /> <abbr title='bundle-size/PersonaTree.fixture.js'>PersonaTree</abbr> | `145.84 kB`<br />`41.681 kB` | `145.917 kB`<br />`41.715 kB` | `77 B` <img aria-hidden="true" src="https://microsoft.github.io/monosize/images/increase.png" /><br />`34 B` <img aria-hidden="true" src="https://microsoft.github.io/monosize/images/increase.png" />|
| <samp>react-tree</samp> <br /> <abbr title='bundle-size/Tree.fixture.js'>Tree</abbr> | `145.094 kB`<br />`41.551 kB` | `145.171 kB`<br />`41.579 kB` | `77 B` <img aria-hidden="true" src="https://microsoft.github.io/monosize/images/increase.png" /><br />`28 B` <img aria-hidden="true" src="https://microsoft.github.io/monosize/images/increase.png" />|

<details>
<summary>Unchanged fixtures</summary>

| Package & Exports | Size (minified/GZIP) |
| ----------------- | -------------------: |
| <samp>global-context</samp> <br /> <abbr title='bundle-size/createContext.fixture.js'>createContext</abbr> | `510 B`<br />`328 B` |
| <samp>global-context</samp> <br /> <abbr title='bundle-size/createContextSelector.fixture.js'>createContextSelector</abbr> | `537 B`<br />`339 B` |
| <samp>react-aria</samp> <br /> <abbr title='bundle-size/Default.fixture.js'>ARIA - Default</abbr> | `237 B`<br />`181 B` |
| <samp>react-calendar-compat</samp> <br /> <abbr title='bundle-size/Calendar.fixture.js'>Calendar Compat</abbr> | `150.084 kB`<br />`40.023 kB` |
| <samp>react-motion</samp> <br /> <abbr title='bundle-size/createMotionComponent.fixture.js'>@fluentui/react-motion - createMotionComponent()</abbr> | `4.005 kB`<br />`1.758 kB` |
| <samp>react-motion</samp> <br /> <abbr title='bundle-size/createPresenceComponent.fixture.js'>@fluentui/react-motion - createPresenceComponent()</abbr> | `4.857 kB`<br />`2.128 kB` |
| <samp>react-motion</samp> <br /> <abbr title='bundle-size/PresenceGroup.fixture.js'>@fluentui/react-motion - PresenceGroup</abbr> | `1.714 kB`<br />`819 B` |
| <samp>react-overflow</samp> <br /> <abbr title='bundle-size/hooks.fixture.js'>hooks only</abbr> | `12.832 kB`<br />`4.828 kB` |
| <samp>react-portal</samp> <br /> <abbr title='bundle-size/Portal.fixture.js'>Portal</abbr> | `14.563 kB`<br />`5.118 kB` |
| <samp>react-portal-compat</samp> <br /> <abbr title='bundle-size/PortalCompat.fixture.js'>PortalCompatProvider</abbr> | `8.39 kB`<br />`2.64 kB` |
| <samp>react-positioning</samp> <br /> <abbr title='bundle-size/usePopper.fixture.js'>usePositioning</abbr> | `28.166 kB`<br />`10.056 kB` |
| <samp>react-teaching-popover</samp> <br /> <abbr title='bundle-size/TeachingPopover.fixture.js'>TeachingPopover</abbr> | `101.649 kB`<br />`30.511 kB` |
| <samp>react-utilities</samp> <br /> <abbr title='bundle-size/SSRProvider.fixture.js'>SSRProvider</abbr> | `180 B`<br />`160 B` |
</details>
<sub>🤖 This report was generated against <a href='https://github.com/microsoft/fluentui/commit/8adf569ca9677a5d9bf0f726147fbfaf6587f0bd'>8adf569ca9677a5d9bf0f726147fbfaf6587f0bd</a></sub>

<!-- Sticky Pull Request Commentbundle-size-report -->
author_association: NONE
user: login: github-actions[bot]
created_at: 2025-04-03T18:42:58Z
body: Pull request demo site: [URL](https://fluentuipr.z22.web.core.windows.net/pull/34166/)
<!-- Sticky Pull Request Commentdeploy-pr-site -->
author_association: NONE
user: login: behowell
created_at: 2025-04-08T17:08:43Z
body: > **[Edit]** -- Based on a discussion in the tech sync, we went with option 1 below.

The biggest issue with the current approach is that there's no good package for the new `overrideSlotClasses` function to live, since it depends both on `react-utilities` and `griffel`. Here are some possible solutions:

1. Rather than `overrideSlotClasses`, just export `getSlotOriginalClassName` (or some better name), which returns the value that the user originally put for `className` on the slot. Custom style functions would be required to merge it on top of their classes, in order to work correctly: `state.foo.className = mergeClasses(state.foo.className, customStyles.foo, getSlotOriginalClassName(state.foo))`.

2. Add a dependency on `griffel/react` to `react-utilities`.

3. Move `overrideSlotClasses` into an existing package, even though it doesn't really belong there. E.g. `react-provider` might be the best option, since the FluentProvider does have a custom style provider prop.

4. Create a new package `react-custom-styles` or something, which for now would export a single function, `overrideSlotClasses`.

--

There is also another solution where we could build in this function "automatically" into `useCustomStyleHook` (via an opt-in flag in the context to avoid backwards compatibility issues). In effect, it would go through each slot in the state object, and reapply the original className to each slot after running the custom style hook. However, this would also require a new package `react-custom-styles`, as the current package where `useCustomStyleHook` lives is `react-shared-contexts`, which can't take extra dependencies.
author_association: CONTRIBUTOR
user: login: mltejera
created_at: 2025-04-08T17:16:20Z
body: Before this goes in, we should be sure to update this doc:

https://react.fluentui.dev/?path=/docs/concepts-developer-advanced-styling-techniques--docs
author_association: CONTRIBUTOR