apps/public-docsite-v9/src/Concepts/WebComponentsInterop/UsingFluentReactWithWebComponents.mdx
import { Meta } from '@storybook/addon-docs/blocks';
<Meta title="Concepts/Developer/Web Components Interop/Using Fluent React with Web Components" />Fluent React v9's extensible architecture and use of web platform features like CSS custom properties allow it to be extended to work well with Web Components, particularly shadow DOM.
It is necessary to modify Fluent React v9's default behavior to better interoperate with Web Components. The implementation lives in the Fluent UI Contrib repository.
Fluent React components can be rendered inside shadow DOM with styles being set on the shadow root's adoptedStyleSheets
property.
import { root } from '@fluentui-contrib/react-shadow';
import { FluentProvider, webLightTheme, Button } from '@fluentui/react-components';
<FluentProvider theme={webLightTheme}>
<root.div>
<Button>Fluent React Button in shadow DOM</Button>
</root.div>
</FluentProvider>;
In the above example, note that FluentProvider sits outside the shadow DOM. When FluentProvider is inside the shadow
DOM styling/theming will not work as expected.
⚠️
FluentProvidermust be in the light DOM for this method to work.
// ❌ This will not render correctly, for example purposes only ❌
import { root } from '@fluentui-contrib/react-shadow';
import { FluentProvider, webLightTheme, Button } from '@fluentui/react-components';
/* This is the shadow root */
<root.div>
<FluentProvider theme={webLightTheme}>
<Button>Fluent React Button in shadow DOM</Button>
</FluentProvider>
</root.div>;
To render a provider inside shadow DOM, use ThemelessFluentProvider instead.
ThemelessFluentProviderThemelessFluentProvider is a special version of FluentProvider that is designed to integrate with
shadow DOM. It supports the same features as FluentProvider except:
Because ThemelessFluentProvider provides no styles for the theme you must provide them yourself.
This means creating a CSS rule that defines all the CSS custom properties needed for a Fluent theme.
The theme can be created in React or another part of the application. The CSS custom properties just need to be
defined in such a way that they will pierce the shadow DOM (e.g., on the :root selector).
import { createCSSStyleSheetFromTheme, ThemelessFluentProvider } from '@fluentui-contrib/react-themeless-provider';
import { root } from '@fluentui-contrib/react-shadow';
import { webLightTheme, Button } from '@fluentui/react-components';
// Create theme styles outside of React component rendering
const themeSheet = createCSSStyleSheetFromTheme(':root', webLightTheme);
document.adoptedStyleSheets = [...document.adoptedStyleSheets, themeSheet];
// Render Fluent components
const ShadowDOMApp = () => {
return (
<ThemelessFluentProvider>
<root.div>
<Button>Fluent React Button in shadow DOM</Button>
</root.div>
</ThemelessFluentProvider>
);
};
Fluent React uses Tabster to control keyboarding and it must be able to "see" the entire DOM of a page to function correctly. Since shadow DOM "hides" parts of the DOM from Tabster, you must opt into shadow DOM support when using shadow DOM and Tabster together.
import { useShadowDOMSupport } from '@fluentui-contrib/pierce-dom';
import { root } from '@fluentui-contrib/react-shadow';
import { createCSSStyleSheetFromTheme, ThemelessFluentProvider } from '@fluentui-contrib/react-themeless-provider';
import { webLightTheme, Button } from '@fluentui/react-components';
const themeSheet = createCSSStyleSheetFromTheme(':root', webLightTheme);
document.adoptedStyleSheets = [...document.adoptedStyleSheets, themeSheet];
const AppComponent = () => {
// This must be called _before_ you render any Fluent React controls
useShadowDOMSupport();
<ThemelessFluentProvider>
<root.div>
<Button>First Button</Button>
<Button>Second Button</Button>
</root.div>
</ThemelessFluentProvider>;
};
Fluent uses Griffel for authoring styles and it provides the mergeClasses
function for controlling style specificity. mergeClasses can accept any CSS class name but it only performs rule ordering
for class names generated by Griffel. This can lead to CSS specificity issues when you want to use CSS classes that were
not generated by Griffel (for example, application utility classes).
For those cases, use the insertion point API to control the specificity of styles via document order.
import { createRoot } from '@fluentui-contrib/react-shadow';
import { createCSSStyleSheetFromTheme, ThemelessFluentProvider } from '@fluentui-contrib/react-themeless-provider';
import { webLightTheme, Button } from '@fluentui/react-components';
const themeSheet = createCSSStyleSheetFromTheme(':root', webLightTheme);
document.head.adoptedStyleSheets = [...document.adoptedStyleSheets, themeSheet];
// This `CSSStyleSheet` acts as a sentinel for inserting Griffel styles.
// Griffel styles are inserted after `insertionPoint`.
const insertionPoint = new CSSStyleSheet();
const root = createRoot({ insertionPoint });
// These styles are not generated by Griffel.
const nonGriffelStyles = new CSSStyleSheet();
nonGriffelStyles.insertRule('.my-style-from-outside-fluent: { color: red; }');
// Griffel styles will be inserted after `insertionPoint` and
// before `nonGriffelStyles`, allowing styles defined in `nonGriffelStyles`
// to "win" specificity by appearing later in the document order.
const externalStyleSheets = [insertionPoint, nonGriffelStyles];
<ThemelessFluentProvider>
<root.div styleSheets={externalStyleSheets}>
<Button className="my-style-from-outside-fluent">Button with external style</Button>
</root.div>
</ThemelessFluentProvider>;