ThemeProvider is a React Context Provider that provides the current theme to the application and a function to change it.
To use the ThemeProvider, wrap your application's entry point. This should be done as high in the component tree as possible.
You should also add the MantleThemeHeadContent component to the head of your application to prevent a Flash of Unstyled Content (FOUC) when the app first loads as well as preload all of our custom fonts. This is the recommended approach for most applications.
import {
MantleThemeHeadContent,
ThemeProvider,
useInitialHtmlThemeProps,
} from "@ngrok/mantle/theme";
export default function App() {
// 👇 get the initial theme props to prevent hydration errors
const initialHtmlThemeProps = useInitialHtmlThemeProps({
className: "h-full",
});
return (
// 👇 spread the theme props onto the <html> element
<html {...initialHtmlThemeProps} lang="en-US" dir="ltr">
<head>
{/* 👇 add this as high in the <head> as possible! */}
<MantleThemeHeadContent />
<meta charSet="utf-8" />
<meta name="author" content="ngrok" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<Meta />
<Links />
</head>
<body className="h-full min-h-full overflow-y-scroll bg-body">
{/* 👇 wrap your app entry in the ThemeProvider */}
<ThemeProvider>
<Outlet />
</ThemeProvider>
</body>
</html>
);
}Only use this section if you cannot use MantleThemeHeadContent.
Sometimes you cannot use the MantleThemeHeadContent component because your web server is not able to render React components. In this case, you can copy the following script and add it to your application's <head>:
<script>
(function () {
var storageKey = "mantle-ui-theme";
var theme = localStorage.getItem(storageKey) || "system";
var resolvedTheme = theme;
if (theme === "system") {
resolvedTheme = window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light";
}
document.documentElement.dataset.theme = theme;
document.documentElement.dataset.appliedTheme = resolvedTheme;
})();
</script>You will also need to ensure that you add the PreloadCoreFonts component to your app as well if you're using the custom setup.
<head>
<link rel="preload" href="/fonts/Roobert-Regular.woff2" as="font" type="font/woff2" crossorigin />
<link rel="preload" href="/fonts/Roobert-Medium.woff2" as="font" type="font/woff2" crossorigin />
<link
rel="preload"
href="/fonts/Roobert-SemiBold.woff2"
as="font"
type="font/woff2"
crossorigin
/>
<link rel="preload" href="/fonts/Roobert-Bold.woff2" as="font" type="font/woff2" crossorigin />
<link
rel="preload"
href="/fonts/JetBrainsMono-Regular.woff2"
as="font"
type="font/woff2"
crossorigin
/>
<link rel="preload" href="/fonts/Family-Regular.woff2" as="font" type="font/woff2" crossorigin />
</head>In your application, you can use the useTheme hook to get and change the current theme:
import {
Select,
SelectContent,
SelectGroup,
SelectItem,
SelectLabel,
SelectTrigger,
} from "@ngrok/mantle/select";
import { isTheme, theme, useTheme } from "@ngrok/mantle/theme";
function App() {
const [currentTheme, setTheme] = useTheme();
return (
<>
<Select
value={currentTheme}
onValueChange={(value) => {
const maybeNewTheme = isTheme(value) ? value : undefined;
if (maybeNewTheme) {
setTheme(maybeNewTheme);
}
}}
>
<div className="ml-auto">
<span className="sr-only">Theme Switcher</span>
<SelectTrigger className="w-min">
<Sun className="mr-1 h-6 w-6" />
</SelectTrigger>
</div>
<SelectContent>
<SelectGroup>
<SelectLabel>Choose a theme</SelectLabel>
<SelectItem value={theme("system")}>System</SelectItem>
<SelectItem value={theme("light")}>Light</SelectItem>
<SelectItem value={theme("dark")}>Dark</SelectItem>
<SelectItem value={theme("light-high-contrast")}>Light High Contrast</SelectItem>
<SelectItem value={theme("dark-high-contrast")}>Dark High Contrast</SelectItem>
</SelectGroup>
</SelectContent>
</Select>
{/* The rest of your app... */}
</>
);
}The ThemeProvider accepts the following props in addition to the PropsWithChildren.
Note: The ThemeProvider uses a hardcoded storage key of mantle-ui-theme and defaults to system theme. These values are managed internally and do not require configuration.
The MantleThemeHeadContent component prevents Flash of Unstyled Content (FOUC) and preloads core fonts. It accepts the following props:
Note: The FOUC prevention script uses hardcoded values for storage key (mantle-ui-theme) and default theme (system) to ensure consistency with ThemeProvider.
The PreventWrongThemeFlashScript component renders only the inline script to prevent FOUC. Use this when you want full control over font preloading. It accepts the following props:
The PreloadCoreFonts component renders preload links for the core fonts used in Mantle (Roobert, JetBrains Mono, and Family). This component takes no props and is automatically included in MantleThemeHeadContent.
The useInitialHtmlThemeProps hook returns props that should be applied to the <html> element to prevent React hydration errors. It accepts the following options:
Returns: An object with className, data-theme, and data-applied-theme props to spread onto your <html> element.
Note: On the server, when ssrCookie is provided, the hook reads the theme from the cookie to render the correct class. When omitted, it defaults to system theme (resolved to light). On the client, it reads the current theme from document.cookie to match what the FOUC prevention script applied, ensuring React hydration succeeds.