I have a fairly complex React App (v18) using FluentUI (v8) and building with Vite (v5). There are a collection of CSS variables which are influenced by the theme, but for my app they seem to be largely undefined. I do have a ThemeProvider
defined. I'll try to include the relevant code.
const App = () => {
...
const theme = createTheme({
palette: {
themePrimary: '#ff462d',
themeLighterAlt: '#fff8f7',
themeLighter: '#ffe1de',
themeLight: '#ffc8c0',
themeTertiary: '#ff9082',
themeSecondary: '#ff5c47',
themeDarkAlt: '#e63f29',
themeDark: '#c23523',
themeDarker: '#8f271a',
neutralLighterAlt: '#faf9f8',
neutralLighter: '#f3f2f1',
neutralLight: '#edebe9',
neutralQuaternaryAlt: '#e1dfdd',
neutralQuaternary: '#d0d0d0',
neutralTertiaryAlt: '#c8c6c4',
neutralTertiary: '#a19f9d',
neutralSecondary: '#605e5c',
neutralPrimaryAlt: '#3b3a39',
neutralPrimary: '#323130',
neutralDark: '#201f1e',
black: '#000000',
white: '#ffffff',
},
components: {
IconButton: {
styles: {
rootDisabled: {
opacity: 0.5
},
root: {
opacity: 1,
backgroundColor: '#fff'
}
}
}
}
});
...
return (
<FluentProvider theme={webLightTheme}>
<ThemeProvider theme={theme}>
<RouterProvider router={router} />
</ThemeProvider>
</FluentProvider>
);
};
And then I have a Callout
with some Checkbox
components inside of it:
const filterDialog = (
<Callout
className={styles.filterDialog}
role="dialog"
target={`#${filterButtonId}`}
onDismiss={hideFilters}
>
<Text as="h1" block variant="large">Filters</Text>
<Stack horizontal horizontalAlign="space-between" tokens={{ childrenGap: '5px' }}>
<Checkbox size="large" label={ filterCheckboxLabel("Feedback") } />
<Checkbox disabled={!filterByFeedback} label={ filterCheckboxLabel("Liked?") } />
<Checkbox disabled={!filterByFeedback} label={ filterCheckboxLabel("Disiked?") } />
</Stack>
</Callout>
);
return (
<Stack className={styles.top}>
...
<IconButton id={filterButtonId} disabled={false} onClick={toggleFilters} iconProps={{ iconName: "filter" }} />
{showFilterDialog ? filterDialog : null}
<IconButton disabled={true} iconProps={{ iconName: "trash" }} />
</StackItem>
</Stack>
</StackItem>
...
The checkboxes didn't render correctly (the spacing was off and the check box border was too thick, so I looked at the dev console and saw this:
I'm at a bit of a loss as to how to diagnose what the issue might be... I'm no expert on the inner workings of FluentUI, hopefully someone here is!
UPDATE: I can see that the FluentProvider
is setting those variables... but for some reason the checkbox styles aren't able to access them?
UPDATE: I've made some progress here... it looks like surfaces such as Callout
are rendered in a separate hierarchy from the one the FluentProvider
is wrapping called the the "Default Layer Host" context. So the question has become "How do I wrap the default layer host context with the FluentProvider
?"
The issue was caused by the Callout
being rendered in a separate hierarchy. By creating a custom LayerHost
component and pointing the callout at that using the layerProps.hostId
property, I was able to render it under the ThemeProvider
:
...
return (
<FluentProvider theme={teamsLightTheme}>
<ThemeProvider theme={theme}>
<RouterProvider router={router} />
<LayerHost id="custom-layer-host" style={{ position: 'fixed', top: 0, left: 0, width: '100%' }} />
</ThemeProvider>
</FluentProvider>
);
...
At first the callout was horizontally flattened, but adding those styles seems to have fixed the issue.
Here's what the Callout
looks like now:
...
const filterDialog = (
<Callout
className={styles.filterDialog}
role="dialog"
target={`#${filterButtonId}`}
onDismiss={hideFilters}
layerProps={{ hostId: "custom-layer-host", }}
>
<Text as="h1" block variant="large">Filters</Text>
<Stack horizontal horizontalAlign="space-between" tokens={{ childrenGap: '5px' }}>
<Checkbox checked={filterByFeedback} size="large" label={ filterCheckboxLabel("Feedback") } onChange={(_ev, data) => { data.checked ? setFilterByFeedback() : clearFilterByFeedback() }} />
<Checkbox checked={filterByFeedbackPos} disabled={!filterByFeedback} label={ filterCheckboxLabel("Liked?") } onChange={(_ev, data) => { data.checked ? setFilterByFeedbackPos() : clearFilterByFeedbackPos() }} />
<Checkbox checked={filterByFeedbackNeg} disabled={!filterByFeedback} label={ filterCheckboxLabel("Disliked?") } onChange={(_ev, data) => { data.checked ? setFilterByFeedbackNeg() : clearFilterByFeedbackNeg() }} />
</Stack>
</Callout>
);
...