FluentProvider / Theme: Attach css variables to root document and possible pure JS feature
Describe the feature that you would like added
While trying out FluentProvider, @MLoughry, on a partner team, was applying FluentProvider to multiple render roots and portals. He suggested that the css variables could be applied to the document root which would allow them to flow across render roots and portals without having to code a FluentProvider repeatedly.
@kelseyyoung and @jspurlin, another partner team, had previously brought up ideas for managing theme switches across multiple render roots. Another partner team that wants to theme components beyond React (e.g. Angular) mentioned they are investigating how to apply the theme using pure JS (although in this case it is a theme built using the v9 Teams customizer).
We should consider if we should have some utility methods or hook update to support applying css vars from a theme to the root document and if we can make this work outside React.
What component or utility would this be added to
FluentProvider or theme utilities
Have you discussed this feature with our team, and if so, who
@miroslavstastny @layershifter
Additional context/screenshots
Summary
This is doable with the current implementation, see the explanation below. This is not the default scenario and the approach depends on the application circumstances. Therefore instead of implementing it as part of the core this should go to application code (or a layer on top of core shared among different applications).
Details
Injecting the theme tokens in a form of CSS variables into the DOM and using the theme in component styles are currently two independent things.
If the application has some other way how to inject the CSS variables, the components will still work as expected, see this codesandbox: https://codesandbox.io/s/fui9-with-static-theme-k9z7g
- The CSS variables are added to
:rootin fui-theme.css. - The main
FluentProvideris created without passingthemeprop to it. - Styling works as expected in the components, even in a portal.
- (not shown in the codesandbox), it is still possible to render a nested
FluentProviderwith another theme to do scoped theming.
There are two things which need to be addressed.
- Where is the application supposed to take the theme from?
It depends on the application - it can import the theme from Fluent UI and process it similarly to what
useThemeStyleTaghttps://github.com/microsoft/fluentui/blob/ecfe4c4fdc306e9c484706f28457f440f55d3684/packages/react-provider/src/components/FluentProvider/useThemeStyleTag.ts#L11 With that we are copy&pasting the logic on how to generate the CSS variable names for tokens - that is straightforward right now but it is considered internal to the library and can change any time - to solve that, we should export the token name to CSS var name function.
Tokens are produced by token pipeline. This can also generate the CSS variables usable in this scenario? Either as part of the react pipeline or separately? (But currently only part of the tokens come from the pipeline. Others are hardcoded in the @fluentui/react-theme).
- If no theme is passed to the
FluentProviderand there is no parent theme on the context, it creates an empty css class without any tokens and this class is applied to theFluentProvider's div and portals. Maybe not optimal, but this should not cause any issues.
If no theme is passed to the FluentProvider and there is no parent theme on the context, it creates an empty css class without any tokens and this class is applied to the FluentProvider's div and portals.
This extra div is the problem.
In my migration, I’ve hacked up a version of FluentProvider that only wraps the children in the various React contexts, and then separately have the root App component apply the theme class name to the document root. What I’m looking for is an official component to wrap all the contexts (those used now and any added in the future) without adding any extra DOM nodes.
This extra div is the problem.
Discussed in https://github.com/microsoft/fluentui/issues/21289#issuecomment-1014826490
Given that OWA has a workaround, lowering priority as this shouldn't block the v9 initial release. We want to reduce the number of elements in the DOM and we can offer a div-less provider as an alternative.
Let's review applyTheBody from v8. Should we do anything similar for v9?
That would apply both theme CSS variables and provider styles to the body.
(Even with that we still need to handle the styles in portals to support scoped theming)
If this is done, we should also reconsider #21289 and potentially apply the dir to body as well?
Because this issue has not had activity for over 150 days, we're automatically closing it for house-keeping purposes.
Still require assistance? Please, create a new issue with up-to date details.
Because this issue has not had activity for over 150 days, we're automatically closing it for house-keeping purposes.
Still require assistance? Please, create a new issue with up-to date details.
This is still an outstanding request for Outlook.
🎊
Hey @MLoughry, I work at Microsoft FX Search and we have a similar problem where we have multiple render roots, and ideally we would like only one React context provider at the host app root level.
However, is my understanding that the only way to use Fluent with multiple React.render roots currently is to wrap every root in HeadlessProvider? Is that what you ended up doing for OWA? Feel free to chat me at @hnygaard on Teams if that's easier, I'd be interested to see the specific OWA implementation for guidance (I have access to the repo)
cc @GeoffCoxMSFT @miroslavstastny @layershifter in case you have any thoughts on this, or are aware of how other teams have solved it? Thanks all, and sorry for the ping
Hey @MLoughry, I work at Microsoft FX Search and we have a similar problem where we have multiple render roots, and ideally we would like only one React context provider at the host app root level.
@HermanNygaard if you have multiple React roots i.e. following:
ReactDOM.render(<AreaA />, document.getElementById('#area-a'));
ReactDOM.render(<AreaB />, document.getElementById('#area-b'));
You cannot have a single top level provider as from React's POV a React root is a root 🤷♂️
In this scenario, FluentProvider is not the best choice as it will inject themes multiple times for every root. For that reason, we introduced createCSSRuleFromTheme() (https://github.com/microsoft/fluentui/pull/29052) & HeadlessFluentProvider (https://github.com/microsoft/fluentui-contrib/pull/39) - this combination allows to define a theme in a single place & configure Fluent components properly.
P.S. OWA uses HeadlessProvider & createCSSRuleFromTheme().
Thanks a lot for your quick reply @layershifter, that's what I figured, it seems like the styles are applied fine here (except for the font in the Popover, https://codesandbox.io/p/sandbox/fui9-with-static-theme-forked-kkfcs9) in a new React render (I guess from the parent container's css vars?), but I assume there are other non-css var values which Fluent9 components relies on from the context? Is that right?
@HermanNygaard your sandbox is missing the usage of HeadlessFluentProvider and proper default styles applied by FluentProvider:
https://github.com/microsoft/fluentui/blob/c08dcb452b80fd5b7af02727c2b9f23297576f3b/packages/react-components/react-provider/src/components/FluentProvider/useFluentProviderStyles.styles.ts#L11-L18
I made a complete example of HeadlessFluentProvider, createCSSRuleFromTheme() & default styles: https://stackblitz.com/edit/t3gyzj