Skip to content

Add getSlotClassNameProp to allow custom style hooks to preserve the original className while overriding the component default className. #34166

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 19 commits into from
Apr 16, 2025

Conversation

behowell
Copy link
Contributor

@behowell behowell commented Apr 3, 2025

Previous Behavior

More details are available in #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)

behowell added 2 commits April 3, 2025 11:29
…override the default styles while preserving any user-provided classes.
@github-actions github-actions bot added this to the April Project Cycle Q2 2025 milestone Apr 3, 2025
Copy link

github-actions bot commented Apr 3, 2025

📊 Bundle size report

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

Copy link

github-actions bot commented Apr 3, 2025

Pull request demo site: URL

Copy link
Contributor

@sopranopillow sopranopillow left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

@behowell
Copy link
Contributor Author

behowell commented Apr 8, 2025

[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.

@mltejera
Copy link
Contributor

mltejera commented Apr 8, 2025

Before this goes in, we should be sure to update this doc:

https://react.fluentui.dev/?path=/docs/concepts-developer-advanced-styling-techniques--docs

@behowell behowell changed the title Add overrideSlotClasses function to allow custom style hooks to override slot styles while preserving user-provided styles Add getSlotOriginalClassNameProp to allow custom style hooks to preserve the original className while overriding the component default className. Apr 9, 2025
@behowell behowell marked this pull request as ready for review April 9, 2025 21:25
@behowell behowell requested review from a team as code owners April 9, 2025 21:25
@behowell behowell requested review from a team as code owners April 9, 2025 21:25
@behowell behowell requested a review from bsunderhus April 9, 2025 21:26
@behowell behowell self-assigned this Apr 9, 2025
@dmytrokirpa dmytrokirpa self-requested a review April 10, 2025 12:04
@behowell behowell requested review from ling1726 and GeoffCox April 14, 2025 17:04
@behowell behowell changed the title Add getSlotOriginalClassNameProp to allow custom style hooks to preserve the original className while overriding the component default className. Add getSlotClassNameProp to allow custom style hooks to preserve the original className while overriding the component default className. Apr 15, 2025
@behowell behowell enabled auto-merge (squash) April 16, 2025 16:56
@behowell behowell merged commit f4eaf10 into microsoft:master Apr 16, 2025
12 of 14 checks passed
dmytrokirpa pushed a commit to dmytrokirpa/fluentui that referenced this pull request May 27, 2025
…original className while overriding the component default className. (microsoft#34166)

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`.
mainframev pushed a commit to mainframev/fluentui that referenced this pull request Jun 9, 2025
…original className while overriding the component default className. (microsoft#34166)

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`.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

8 participants