diff --git a/packages/docusaurus-theme-classic/src/theme/ThemedImage/index.tsx b/packages/docusaurus-theme-classic/src/theme/ThemedImage/index.tsx
index b0b938350278..2090d14e09f7 100644
--- a/packages/docusaurus-theme-classic/src/theme/ThemedImage/index.tsx
+++ b/packages/docusaurus-theme-classic/src/theme/ThemedImage/index.tsx
@@ -6,44 +6,21 @@
*/
import React from 'react';
-import clsx from 'clsx';
-import useIsBrowser from '@docusaurus/useIsBrowser';
-import {useColorMode} from '@docusaurus/theme-common';
+import {ThemedComponent} from '@docusaurus/theme-common';
import type {Props} from '@theme/ThemedImage';
-import styles from './styles.module.css';
-
export default function ThemedImage(props: Props): JSX.Element {
- const isBrowser = useIsBrowser();
- const {colorMode} = useColorMode();
- const {sources, className, alt, ...propsRest} = props;
-
- type SourceName = keyof Props['sources'];
-
- const clientThemes: SourceName[] =
- colorMode === 'dark' ? ['dark'] : ['light'];
-
- const renderedSourceNames: SourceName[] = isBrowser
- ? clientThemes
- : // We need to render both images on the server to avoid flash
- // See https://github.com/facebook/docusaurus/pull/3730
- ['light', 'dark'];
-
+ const {sources, className: parentClassName, alt, ...propsRest} = props;
return (
- <>
- {renderedSourceNames.map((sourceName) => (
+
+ {({theme, className}) => (
- ))}
- >
+ )}
+
);
}
diff --git a/packages/docusaurus-theme-common/src/components/ThemedComponent/index.tsx b/packages/docusaurus-theme-common/src/components/ThemedComponent/index.tsx
new file mode 100644
index 000000000000..dcc631e04578
--- /dev/null
+++ b/packages/docusaurus-theme-common/src/components/ThemedComponent/index.tsx
@@ -0,0 +1,77 @@
+/**
+ * Copyright (c) Facebook, Inc. and its affiliates.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root directory of this source tree.
+ */
+
+import React from 'react';
+import clsx from 'clsx';
+import useIsBrowser from '@docusaurus/useIsBrowser';
+import {useColorMode} from '../../contexts/colorMode';
+
+import styles from './styles.module.css';
+
+const AllThemes = ['light', 'dark'] as const;
+
+type Theme = (typeof AllThemes)[number];
+
+type RenderFn = ({
+ theme,
+ className,
+}: {
+ theme: Theme;
+ className: string;
+}) => React.ReactNode;
+
+type Props = {
+ children: RenderFn;
+ className?: string;
+};
+
+/**
+ * Generic component to render anything themed in light/dark
+ * Note: it's preferable to use CSS for theming because this component
+ * will need to render all the variants during SSR to avoid a theme flash.
+ *
+ * Use this only when CSS customizations are not convenient or impossible.
+ * For example, rendering themed images or SVGs...
+ *
+ * @param className applied to all the variants
+ * @param children function to render a theme variant
+ * @constructor
+ */
+export default function ThemedComponent({
+ className,
+ children,
+}: Props): JSX.Element {
+ const isBrowser = useIsBrowser();
+ const {colorMode} = useColorMode();
+
+ function getThemesToRender(): Theme[] {
+ if (isBrowser) {
+ return colorMode === 'dark' ? ['dark'] : ['light'];
+ }
+ // We need to render both components on the server / hydration to avoid:
+ // - a flash of wrong theme before hydration
+ // - React hydration mismatches
+ // See https://github.com/facebook/docusaurus/pull/3730
+ return ['light', 'dark'];
+ }
+
+ return (
+ <>
+ {getThemesToRender().map((theme) => {
+ const themedElement = children({
+ theme,
+ className: clsx(
+ className,
+ styles.themedComponent,
+ styles[`themedComponent--${theme}`],
+ ),
+ });
+ return {themedElement};
+ })}
+ >
+ );
+}
diff --git a/packages/docusaurus-theme-common/src/components/ThemedComponent/styles.module.css b/packages/docusaurus-theme-common/src/components/ThemedComponent/styles.module.css
new file mode 100644
index 000000000000..ebf047bd6c4c
--- /dev/null
+++ b/packages/docusaurus-theme-common/src/components/ThemedComponent/styles.module.css
@@ -0,0 +1,18 @@
+/**
+ * Copyright (c) Facebook, Inc. and its affiliates.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root directory of this source tree.
+ */
+
+.themedComponent {
+ display: none;
+}
+
+[data-theme='light'] .themedComponent--light {
+ display: initial;
+}
+
+[data-theme='dark'] .themedComponent--dark {
+ display: initial;
+}
diff --git a/packages/docusaurus-theme-common/src/index.ts b/packages/docusaurus-theme-common/src/index.ts
index 0fdda7f3aa4f..0d69d161aeaf 100644
--- a/packages/docusaurus-theme-common/src/index.ts
+++ b/packages/docusaurus-theme-common/src/index.ts
@@ -24,6 +24,8 @@ export {
type ColorModeConfig,
} from './utils/useThemeConfig';
+export {default as ThemedComponent} from './components/ThemedComponent';
+
export {
createStorageSlot,
useStorageSlot,