From 4dcccae03741c5cd8b94abe15d850d8bb6017614 Mon Sep 17 00:00:00 2001 From: sebastien Date: Fri, 16 Oct 2026 18:01:56 +0200 Subject: [PATCH 1/3] break(theme-common): make ThemeConfig and UserThemeConfig interfaces that themes can augment (AI-assisted) Themes and plugins now add their theme config fields to the @docusaurus/theme-common ThemeConfig/UserThemeConfig interfaces with declaration merging, instead of casting useThemeConfig() to local types. Co-Authored-By: Claude Opus 5.5 --- .../docusaurus-preset-classic/src/options.ts | 11 ++--- .../docusaurus-theme-classic/src/options.ts | 3 +- .../docusaurus-theme-common/src/internal.ts | 2 + .../src/utils/useThemeConfig.ts | 29 ++++++++++--- .../src/theme-live-codeblock.d.ts | 21 ++++++++-- .../src/theme/Playground/Layout/index.tsx | 9 +--- .../src/client/index.ts | 6 +-- .../src/theme-mermaid.d.ts | 29 +++++++++---- .../src/theme-search-algolia.d.ts | 41 +++++++++++++------ .../api/plugin-methods/static-methods.mdx | 17 ++++++++ website/docusaurus.config.ts | 9 ++-- 11 files changed, 128 insertions(+), 49 deletions(-) diff --git a/packages/docusaurus-preset-classic/src/options.ts b/packages/docusaurus-preset-classic/src/options.ts index 6ac62497e7eb..4ad735530166 100644 --- a/packages/docusaurus-preset-classic/src/options.ts +++ b/packages/docusaurus-preset-classic/src/options.ts @@ -5,6 +5,9 @@ * LICENSE file in the root directory of this source tree. */ +// Adds the Algolia fields to the theme-common UserThemeConfig interface +/// + import type {Options as DocsPluginOptions} from '@docusaurus/plugin-content-docs'; import type {Options as BlogPluginOptions} from '@docusaurus/plugin-content-blog'; import type {Options as PagesPluginOptions} from '@docusaurus/plugin-content-pages'; @@ -15,8 +18,7 @@ import type {Options as GTMPluginOptions} from '@docusaurus/plugin-google-tag-ma import type {Options as CssCascadeLayersPluginOptions} from '@docusaurus/plugin-css-cascade-layers'; import type {Options as ThemeOptions} from '@docusaurus/theme-classic'; import type {ThemeConfig as BaseThemeConfig} from '@docusaurus/types'; -import type {UserThemeConfig as ClassicThemeConfig} from '@docusaurus/theme-common'; -import type {UserThemeConfig as AlgoliaThemeConfig} from '@docusaurus/theme-search-algolia'; +import type {UserThemeConfig} from '@docusaurus/theme-common'; export type Options = { /** @@ -49,6 +51,5 @@ export type Options = { cssCascadeLayers?: false | CssCascadeLayersPluginOptions; }; -export type ThemeConfig = BaseThemeConfig & - ClassicThemeConfig & - AlgoliaThemeConfig; +// Theme config fields of all the themes of this preset, including Algolia's +export type ThemeConfig = BaseThemeConfig & UserThemeConfig; diff --git a/packages/docusaurus-theme-classic/src/options.ts b/packages/docusaurus-theme-classic/src/options.ts index 52501b718ab9..d310544ef1ab 100644 --- a/packages/docusaurus-theme-classic/src/options.ts +++ b/packages/docusaurus-theme-classic/src/options.ts @@ -13,6 +13,7 @@ import type { } from '@theme/NavbarItem/DocsVersionDropdownNavbarItem'; import type {Options, PluginOptions} from '@docusaurus/theme-classic'; import type {ThemeConfig} from '@docusaurus/theme-common'; +import type {ClassicThemeConfig} from '@docusaurus/theme-common/internal'; import type { ThemeConfigValidationContext, OptionValidationContext, @@ -58,7 +59,7 @@ const DEFAULT_COLOR_MODE_CONFIG: ThemeConfig['colorMode'] = { respectPrefersColorScheme: false, }; -export const DEFAULT_CONFIG: ThemeConfig = { +export const DEFAULT_CONFIG: ClassicThemeConfig = { colorMode: DEFAULT_COLOR_MODE_CONFIG, docs: DEFAULT_DOCS_CONFIG, blog: DEFAULT_BLOG_CONFIG, diff --git a/packages/docusaurus-theme-common/src/internal.ts b/packages/docusaurus-theme-common/src/internal.ts index fa8979347891..ece229aeb8cb 100644 --- a/packages/docusaurus-theme-common/src/internal.ts +++ b/packages/docusaurus-theme-common/src/internal.ts @@ -31,6 +31,8 @@ export { } from './utils/tabsUtils'; export type {TabValue, TabsProps, TabItemProps} from './utils/tabsUtils'; +export type {ClassicThemeConfig} from './utils/useThemeConfig'; + export {useNavbarMobileSidebar} from './contexts/navbarMobileSidebar'; export {useNavbarSecondaryMenu} from './contexts/navbarSecondaryMenu/display'; diff --git a/packages/docusaurus-theme-common/src/utils/useThemeConfig.ts b/packages/docusaurus-theme-common/src/utils/useThemeConfig.ts index d15df936d1fa..9777675db524 100644 --- a/packages/docusaurus-theme-common/src/utils/useThemeConfig.ts +++ b/packages/docusaurus-theme-common/src/utils/useThemeConfig.ts @@ -104,9 +104,8 @@ type TableOfContents = { maxHeadingLevel: number; }; -// TODO Docusaurus v4: use interface + declaration merging to enhance -// Theme config after validation/normalization -export type ThemeConfig = { +// Theme config fields of the classic theme, after validation/normalization +export type ClassicThemeConfig = { docs: { versionPersistence: DocsVersionPersistence; sidebar: { @@ -136,8 +135,28 @@ export type ThemeConfig = { tableOfContents: TableOfContents; }; -// User-provided theme config, unnormalized -export type UserThemeConfig = DeepPartial; +/** + * Theme config, after validation/normalization. + * + * Other themes and plugins can add their own fields with declaration merging: + * + * ```ts + * declare module '@docusaurus/theme-common' { + * interface ThemeConfig { + * myTheme: {option: string}; + * } + * } + * ``` + */ +export interface ThemeConfig extends ClassicThemeConfig {} + +/** + * User-provided theme config, unnormalized. + * + * Other themes and plugins can add their own fields with declaration merging, + * like for {@link ThemeConfig}. + */ +export interface UserThemeConfig extends DeepPartial {} /** * A convenient/more semantic way to get theme config from context. diff --git a/packages/docusaurus-theme-live-codeblock/src/theme-live-codeblock.d.ts b/packages/docusaurus-theme-live-codeblock/src/theme-live-codeblock.d.ts index 550623f1fa31..9c24ebaeb493 100644 --- a/packages/docusaurus-theme-live-codeblock/src/theme-live-codeblock.d.ts +++ b/packages/docusaurus-theme-live-codeblock/src/theme-live-codeblock.d.ts @@ -11,11 +11,26 @@ declare module '@docusaurus/theme-live-codeblock' { import type {PlaygroundPosition} from '@theme/Playground'; + export type ThemeConfigLiveCodeBlock = { + playgroundPosition: PlaygroundPosition; + }; + export type ThemeConfig = { - liveCodeBlock: { - playgroundPosition: PlaygroundPosition; - }; + liveCodeBlock: ThemeConfigLiveCodeBlock; }; + + // Adds the theme config fields to the @docusaurus/theme-common interfaces + // Their names intentionally shadow the ThemeConfig types of this module + /* eslint-disable @typescript-eslint/no-shadow */ + module '@docusaurus/theme-common' { + interface ThemeConfig { + liveCodeBlock: ThemeConfigLiveCodeBlock; + } + interface UserThemeConfig { + liveCodeBlock?: Partial; + } + } + /* eslint-enable @typescript-eslint/no-shadow */ } declare module '@theme/LiveCodeBlock' { diff --git a/packages/docusaurus-theme-live-codeblock/src/theme/Playground/Layout/index.tsx b/packages/docusaurus-theme-live-codeblock/src/theme/Playground/Layout/index.tsx index 157b7993e887..1d9757754bcb 100644 --- a/packages/docusaurus-theme-live-codeblock/src/theme/Playground/Layout/index.tsx +++ b/packages/docusaurus-theme-live-codeblock/src/theme/Playground/Layout/index.tsx @@ -11,15 +11,8 @@ import PlaygroundPreview from '@theme/Playground/Preview'; import PlaygroundEditor from '@theme/Playground/Editor'; import type {Props} from '@theme/Playground/Layout'; -import type {ThemeConfig} from '@docusaurus/theme-live-codeblock'; - -function useLiveCodeBlockThemeConfig() { - const themeConfig = useThemeConfig() as unknown as ThemeConfig; - return themeConfig.liveCodeBlock; -} - export default function PlaygroundLayout(props: Props): ReactNode { - const themeConfig = useLiveCodeBlockThemeConfig(); + const themeConfig = useThemeConfig().liveCodeBlock; const position = props.position ?? themeConfig.playgroundPosition; return ( <> diff --git a/packages/docusaurus-theme-mermaid/src/client/index.ts b/packages/docusaurus-theme-mermaid/src/client/index.ts index c850cce7cad4..2d67b51f2ec0 100644 --- a/packages/docusaurus-theme-mermaid/src/client/index.ts +++ b/packages/docusaurus-theme-mermaid/src/client/index.ts @@ -10,13 +10,13 @@ import {useColorMode, useThemeConfig} from '@docusaurus/theme-common'; import {loadMermaid} from './loadMermaid'; import type {RenderResult, MermaidConfig} from 'mermaid'; -import type {ThemeConfig} from '@docusaurus/theme-mermaid'; +import type {ThemeConfigMermaid} from '@docusaurus/theme-mermaid'; // Stable className to allow users to easily target with CSS export const MermaidContainerClassName = 'docusaurus-mermaid-container'; -export function useMermaidThemeConfig(): ThemeConfig['mermaid'] { - return (useThemeConfig() as unknown as ThemeConfig).mermaid; +export function useMermaidThemeConfig(): ThemeConfigMermaid { + return useThemeConfig().mermaid; } export function useMermaidConfig(): MermaidConfig { diff --git a/packages/docusaurus-theme-mermaid/src/theme-mermaid.d.ts b/packages/docusaurus-theme-mermaid/src/theme-mermaid.d.ts index 33142adbd423..54707080e53a 100644 --- a/packages/docusaurus-theme-mermaid/src/theme-mermaid.d.ts +++ b/packages/docusaurus-theme-mermaid/src/theme-mermaid.d.ts @@ -12,17 +12,32 @@ declare module '@docusaurus/theme-mermaid' { import type {MermaidConfig} from 'mermaid'; import type {Plugin} from '@docusaurus/types'; - export type ThemeConfig = { - mermaid: { - theme: { - light: NonNullable; - dark: NonNullable; - }; - options: MermaidConfig; + export type ThemeConfigMermaid = { + theme: { + light: NonNullable; + dark: NonNullable; }; + options: MermaidConfig; + }; + + export type ThemeConfig = { + mermaid: ThemeConfigMermaid; }; export type UserThemeConfig = DeepPartial; + // Adds the theme config fields to the @docusaurus/theme-common interfaces + // Their names intentionally shadow the ThemeConfig types of this module + /* eslint-disable @typescript-eslint/no-shadow */ + module '@docusaurus/theme-common' { + interface ThemeConfig { + mermaid: ThemeConfigMermaid; + } + interface UserThemeConfig { + mermaid?: DeepPartial; + } + } + /* eslint-enable @typescript-eslint/no-shadow */ + export default function themeMermaid(): Plugin; } diff --git a/packages/docusaurus-theme-search-algolia/src/theme-search-algolia.d.ts b/packages/docusaurus-theme-search-algolia/src/theme-search-algolia.d.ts index c9360342fd93..e26ab337f9ab 100644 --- a/packages/docusaurus-theme-search-algolia/src/theme-search-algolia.d.ts +++ b/packages/docusaurus-theme-search-algolia/src/theme-search-algolia.d.ts @@ -59,25 +59,40 @@ declare module '@docusaurus/theme-search-algolia' { }; }; + export type UserThemeConfigAlgolia = Overwrite< + DeepPartial, + { + // Required fields: + appId: ThemeConfigAlgolia['appId']; + apiKey: ThemeConfigAlgolia['apiKey']; + indexName: ThemeConfigAlgolia['indexName']; + // askAi also accepts a shorter string form + askAi?: string | Optional; + } + >; + export type ThemeConfig = { algolia: ThemeConfigAlgolia; }; export type UserThemeConfig = { - algolia?: Overwrite< - DeepPartial, - { - // Required fields: - appId: ThemeConfigAlgolia['appId']; - apiKey: ThemeConfigAlgolia['apiKey']; - indexName: ThemeConfigAlgolia['indexName']; - // askAi also accepts a shorter string form - askAi?: - | string - | Optional; - } - >; + algolia?: UserThemeConfigAlgolia; }; + + // Adds the theme config fields to the @docusaurus/theme-common interfaces + // Their names intentionally shadow the ThemeConfig types of this module + /* eslint-disable @typescript-eslint/no-shadow */ + module '@docusaurus/theme-common' { + interface ThemeConfig { + // Optional: the classic preset only enables this theme when the + // "algolia" field is provided + algolia?: ThemeConfigAlgolia; + } + interface UserThemeConfig { + algolia?: UserThemeConfigAlgolia; + } + } + /* eslint-enable @typescript-eslint/no-shadow */ } declare module '@theme/SearchPage' { diff --git a/website/docs/api/plugin-methods/static-methods.mdx b/website/docs/api/plugin-methods/static-methods.mdx index 6cd5e5124e58..81ff632e2a10 100644 --- a/website/docs/api/plugin-methods/static-methods.mdx +++ b/website/docs/api/plugin-methods/static-methods.mdx @@ -81,3 +81,20 @@ export function validateThemeConfig({themeConfig, validate}) { } // highlight-end ``` + +### TypeScript types {/* #validateThemeConfig-types */} + +The `@docusaurus/theme-common` package declares the theme config types with 2 interfaces: `ThemeConfig` after validation and normalization, and `UserThemeConfig` as written in `docusaurus.config.js`. Your theme can add its own fields to them with TypeScript [declaration merging](https://www.typescriptlang.org/docs/handbook/declaration-merging.html#module-augmentation): + +```ts title="my-theme/src/index.ts" +declare module '@docusaurus/theme-common' { + interface ThemeConfig { + myTheme: {position: 'top' | 'bottom'}; + } + interface UserThemeConfig { + myTheme?: {position?: 'top' | 'bottom'}; + } +} +``` + +Once your theme types are loaded, `useThemeConfig().myTheme` is typed, and site authors get autocompletion for `myTheme` in their `themeConfig` with `satisfies Preset.ThemeConfig`. diff --git a/website/docusaurus.config.ts b/website/docusaurus.config.ts index 2c1b5f4c769e..81466d301a48 100644 --- a/website/docusaurus.config.ts +++ b/website/docusaurus.config.ts @@ -4,6 +4,10 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ + +// Adds the liveCodeBlock fields to the Preset.ThemeConfig type +/// + import path from 'node:path'; import npm2yarn from '@docusaurus/remark-plugin-npm2yarn'; @@ -34,9 +38,6 @@ import type {Options as BlogOptions} from '@docusaurus/plugin-content-blog'; import type {Options as PageOptions} from '@docusaurus/plugin-content-pages'; import type {Options as IdealImageOptions} from '@docusaurus/plugin-ideal-image'; import type {Options as ClientRedirectsOptions} from '@docusaurus/plugin-client-redirects'; -import type {ThemeConfig as LiveCodeBlockThemeConfig} from '@docusaurus/theme-live-codeblock'; - -type ThemeConfig = Preset.ThemeConfig & LiveCodeBlockThemeConfig; const ArchivedVersionsDropdownItems = Object.entries(VersionsArchived).splice( 0, @@ -887,6 +888,6 @@ export default async function createConfigAsync() { }, copyright: `Copyright © ${new Date().getFullYear()} Meta Platforms, Inc. Built with Docusaurus.`, }, - } satisfies ThemeConfig, + } satisfies Preset.ThemeConfig, } satisfies Config; } From 772006a51498441d5a2acf64062b70f9825ae5b8 Mon Sep 17 00:00:00 2001 From: sebastien Date: Fri, 16 Oct 2026 20:15:46 +0200 Subject: [PATCH 2/3] refactor: move the augmentable ThemeConfig interfaces to @docusaurus/types, add core useThemeConfig() hook (AI-assisted) ThemeConfig and UserThemeConfig are now generic interfaces of @docusaurus/types that every theme and plugin can augment, including theme-common for the classic theme fields. Config.themeConfig uses UserThemeConfig, and the new @docusaurus/useThemeConfig core hook returns the typed ThemeConfig. Co-Authored-By: Claude Opus 5.5 --- .../src/index.d.ts | 6 ++++ .../docusaurus-preset-classic/src/options.ts | 6 ++-- .../src/utils/useThemeConfig.ts | 34 ++++++------------- .../src/__tests__/validateThemeConfig.test.ts | 12 ++++--- .../src/theme-live-codeblock.d.ts | 4 +-- .../src/theme/Playground/Layout/index.tsx | 2 +- .../src/validateThemeConfig.ts | 6 ++-- .../src/client/index.ts | 3 +- .../src/theme-mermaid.d.ts | 4 +-- .../src/theme-search-algolia.d.ts | 4 +-- packages/docusaurus-types/src/config.d.ts | 28 +++++++++++++-- packages/docusaurus-types/src/index.d.ts | 1 + .../src/client/exports/useThemeConfig.ts | 13 +++++++ .../docusaurus/src/server/configValidation.ts | 4 ++- .../__tests__/__snapshots__/base.test.ts.snap | 1 + .../__snapshots__/index.test.ts.snap | 1 + .../api/plugin-methods/static-methods.mdx | 6 ++-- website/docs/docusaurus-core.mdx | 19 +++++++++++ 18 files changed, 104 insertions(+), 50 deletions(-) create mode 100644 packages/docusaurus/src/client/exports/useThemeConfig.ts diff --git a/packages/docusaurus-module-type-aliases/src/index.d.ts b/packages/docusaurus-module-type-aliases/src/index.d.ts index ff62e02e2832..1762316fde77 100644 --- a/packages/docusaurus-module-type-aliases/src/index.d.ts +++ b/packages/docusaurus-module-type-aliases/src/index.d.ts @@ -280,6 +280,12 @@ declare module '@docusaurus/useDocusaurusContext' { export default function useDocusaurusContext(): DocusaurusContext; } +declare module '@docusaurus/useThemeConfig' { + import type {ThemeConfig} from '@docusaurus/types'; + + export default function useThemeConfig(): ThemeConfig; +} + declare module '@docusaurus/useRouteContext' { import type {PluginRouteContext} from '@docusaurus/types'; diff --git a/packages/docusaurus-preset-classic/src/options.ts b/packages/docusaurus-preset-classic/src/options.ts index 4ad735530166..6363bc478f6c 100644 --- a/packages/docusaurus-preset-classic/src/options.ts +++ b/packages/docusaurus-preset-classic/src/options.ts @@ -5,7 +5,7 @@ * LICENSE file in the root directory of this source tree. */ -// Adds the Algolia fields to the theme-common UserThemeConfig interface +// Adds the Algolia fields to the @docusaurus/types UserThemeConfig interface /// import type {Options as DocsPluginOptions} from '@docusaurus/plugin-content-docs'; @@ -17,7 +17,7 @@ import type {Options as GtagPluginOptions} from '@docusaurus/plugin-google-gtag' import type {Options as GTMPluginOptions} from '@docusaurus/plugin-google-tag-manager'; import type {Options as CssCascadeLayersPluginOptions} from '@docusaurus/plugin-css-cascade-layers'; import type {Options as ThemeOptions} from '@docusaurus/theme-classic'; -import type {ThemeConfig as BaseThemeConfig} from '@docusaurus/types'; +// Importing from theme-common also adds the classic theme fields import type {UserThemeConfig} from '@docusaurus/theme-common'; export type Options = { @@ -52,4 +52,4 @@ export type Options = { }; // Theme config fields of all the themes of this preset, including Algolia's -export type ThemeConfig = BaseThemeConfig & UserThemeConfig; +export type ThemeConfig = UserThemeConfig; diff --git a/packages/docusaurus-theme-common/src/utils/useThemeConfig.ts b/packages/docusaurus-theme-common/src/utils/useThemeConfig.ts index 9777675db524..d3eed612ef0e 100644 --- a/packages/docusaurus-theme-common/src/utils/useThemeConfig.ts +++ b/packages/docusaurus-theme-common/src/utils/useThemeConfig.ts @@ -5,9 +5,10 @@ * LICENSE file in the root directory of this source tree. */ -import useDocusaurusContext from '@docusaurus/useDocusaurusContext'; +import useCoreThemeConfig from '@docusaurus/useThemeConfig'; import type {PrismTheme} from 'prism-react-renderer'; import type {DeepPartial} from 'utility-types'; +import type {ThemeConfig, UserThemeConfig} from '@docusaurus/types'; import type {MagicCommentConfig} from './codeBlockUtils'; import type {ColorMode} from '../contexts/colorMode'; @@ -135,32 +136,17 @@ export type ClassicThemeConfig = { tableOfContents: TableOfContents; }; -/** - * Theme config, after validation/normalization. - * - * Other themes and plugins can add their own fields with declaration merging: - * - * ```ts - * declare module '@docusaurus/theme-common' { - * interface ThemeConfig { - * myTheme: {option: string}; - * } - * } - * ``` - */ -export interface ThemeConfig extends ClassicThemeConfig {} +// Adds the classic theme fields to the @docusaurus/types interfaces +declare module '@docusaurus/types' { + interface ThemeConfig extends ClassicThemeConfig {} + interface UserThemeConfig extends DeepPartial {} +} -/** - * User-provided theme config, unnormalized. - * - * Other themes and plugins can add their own fields with declaration merging, - * like for {@link ThemeConfig}. - */ -export interface UserThemeConfig extends DeepPartial {} +export type {ThemeConfig, UserThemeConfig}; /** - * A convenient/more semantic way to get theme config from context. + * Same as the `@docusaurus/useThemeConfig` hook of Docusaurus core. */ export function useThemeConfig(): ThemeConfig { - return useDocusaurusContext().siteConfig.themeConfig as ThemeConfig; + return useCoreThemeConfig(); } diff --git a/packages/docusaurus-theme-live-codeblock/src/__tests__/validateThemeConfig.test.ts b/packages/docusaurus-theme-live-codeblock/src/__tests__/validateThemeConfig.test.ts index 8ad793e64055..91c3b062070c 100644 --- a/packages/docusaurus-theme-live-codeblock/src/__tests__/validateThemeConfig.test.ts +++ b/packages/docusaurus-theme-live-codeblock/src/__tests__/validateThemeConfig.test.ts @@ -8,12 +8,10 @@ import {describe, expect, it} from 'vitest'; import {validateThemeConfig, DEFAULT_CONFIG} from '../validateThemeConfig'; import type {Joi} from '@docusaurus/utils-validation'; +import type {ThemeConfig} from '@docusaurus/theme-live-codeblock'; function testValidateThemeConfig(themeConfig: {[key: string]: unknown}) { - function validate( - schema: Joi.ObjectSchema<{[key: string]: unknown}>, - cfg: {[key: string]: unknown}, - ) { + function validate(schema: Joi.ObjectSchema, cfg: ThemeConfig) { const {value, error} = schema.validate(cfg, { convert: false, }); @@ -23,7 +21,11 @@ function testValidateThemeConfig(themeConfig: {[key: string]: unknown}) { return value; } - return validateThemeConfig({validate, themeConfig}); + // Some tests pass invalid theme configs on purpose + return validateThemeConfig({ + validate, + themeConfig: themeConfig as ThemeConfig, + }); } describe('validateThemeConfig', () => { diff --git a/packages/docusaurus-theme-live-codeblock/src/theme-live-codeblock.d.ts b/packages/docusaurus-theme-live-codeblock/src/theme-live-codeblock.d.ts index 9c24ebaeb493..0fe0b13b87b3 100644 --- a/packages/docusaurus-theme-live-codeblock/src/theme-live-codeblock.d.ts +++ b/packages/docusaurus-theme-live-codeblock/src/theme-live-codeblock.d.ts @@ -19,10 +19,10 @@ declare module '@docusaurus/theme-live-codeblock' { liveCodeBlock: ThemeConfigLiveCodeBlock; }; - // Adds the theme config fields to the @docusaurus/theme-common interfaces + // Adds the theme config fields to the @docusaurus/types interfaces // Their names intentionally shadow the ThemeConfig types of this module /* eslint-disable @typescript-eslint/no-shadow */ - module '@docusaurus/theme-common' { + module '@docusaurus/types' { interface ThemeConfig { liveCodeBlock: ThemeConfigLiveCodeBlock; } diff --git a/packages/docusaurus-theme-live-codeblock/src/theme/Playground/Layout/index.tsx b/packages/docusaurus-theme-live-codeblock/src/theme/Playground/Layout/index.tsx index 1d9757754bcb..2d563d7a278f 100644 --- a/packages/docusaurus-theme-live-codeblock/src/theme/Playground/Layout/index.tsx +++ b/packages/docusaurus-theme-live-codeblock/src/theme/Playground/Layout/index.tsx @@ -6,7 +6,7 @@ */ import type {ReactNode} from 'react'; -import {useThemeConfig} from '@docusaurus/theme-common'; +import useThemeConfig from '@docusaurus/useThemeConfig'; import PlaygroundPreview from '@theme/Playground/Preview'; import PlaygroundEditor from '@theme/Playground/Editor'; import type {Props} from '@theme/Playground/Layout'; diff --git a/packages/docusaurus-theme-live-codeblock/src/validateThemeConfig.ts b/packages/docusaurus-theme-live-codeblock/src/validateThemeConfig.ts index d642214987de..7a3421df8eb9 100644 --- a/packages/docusaurus-theme-live-codeblock/src/validateThemeConfig.ts +++ b/packages/docusaurus-theme-live-codeblock/src/validateThemeConfig.ts @@ -6,10 +6,8 @@ */ import {Joi} from '@docusaurus/utils-validation'; -import type { - ThemeConfig, - ThemeConfigValidationContext, -} from '@docusaurus/types'; +import type {ThemeConfig} from '@docusaurus/theme-live-codeblock'; +import type {ThemeConfigValidationContext} from '@docusaurus/types'; export const DEFAULT_CONFIG = { playgroundPosition: 'bottom', diff --git a/packages/docusaurus-theme-mermaid/src/client/index.ts b/packages/docusaurus-theme-mermaid/src/client/index.ts index 2d67b51f2ec0..50c7cec8edb3 100644 --- a/packages/docusaurus-theme-mermaid/src/client/index.ts +++ b/packages/docusaurus-theme-mermaid/src/client/index.ts @@ -6,7 +6,8 @@ */ import {useState, useEffect, useMemo, useId, useRef} from 'react'; -import {useColorMode, useThemeConfig} from '@docusaurus/theme-common'; +import useThemeConfig from '@docusaurus/useThemeConfig'; +import {useColorMode} from '@docusaurus/theme-common'; import {loadMermaid} from './loadMermaid'; import type {RenderResult, MermaidConfig} from 'mermaid'; diff --git a/packages/docusaurus-theme-mermaid/src/theme-mermaid.d.ts b/packages/docusaurus-theme-mermaid/src/theme-mermaid.d.ts index 54707080e53a..395e34460a48 100644 --- a/packages/docusaurus-theme-mermaid/src/theme-mermaid.d.ts +++ b/packages/docusaurus-theme-mermaid/src/theme-mermaid.d.ts @@ -25,10 +25,10 @@ declare module '@docusaurus/theme-mermaid' { }; export type UserThemeConfig = DeepPartial; - // Adds the theme config fields to the @docusaurus/theme-common interfaces + // Adds the theme config fields to the @docusaurus/types interfaces // Their names intentionally shadow the ThemeConfig types of this module /* eslint-disable @typescript-eslint/no-shadow */ - module '@docusaurus/theme-common' { + module '@docusaurus/types' { interface ThemeConfig { mermaid: ThemeConfigMermaid; } diff --git a/packages/docusaurus-theme-search-algolia/src/theme-search-algolia.d.ts b/packages/docusaurus-theme-search-algolia/src/theme-search-algolia.d.ts index e26ab337f9ab..d4b5778add2e 100644 --- a/packages/docusaurus-theme-search-algolia/src/theme-search-algolia.d.ts +++ b/packages/docusaurus-theme-search-algolia/src/theme-search-algolia.d.ts @@ -79,10 +79,10 @@ declare module '@docusaurus/theme-search-algolia' { algolia?: UserThemeConfigAlgolia; }; - // Adds the theme config fields to the @docusaurus/theme-common interfaces + // Adds the theme config fields to the @docusaurus/types interfaces // Their names intentionally shadow the ThemeConfig types of this module /* eslint-disable @typescript-eslint/no-shadow */ - module '@docusaurus/theme-common' { + module '@docusaurus/types' { interface ThemeConfig { // Optional: the classic preset only enables this theme when the // "algolia" field is provided diff --git a/packages/docusaurus-types/src/config.d.ts b/packages/docusaurus-types/src/config.d.ts index 96dff8c2ac0a..4c63b9d6eb62 100644 --- a/packages/docusaurus-types/src/config.d.ts +++ b/packages/docusaurus-types/src/config.d.ts @@ -15,9 +15,32 @@ import type {MarkdownConfig} from './markdown'; export type RouterType = 'browser' | 'hash'; -export type ThemeConfig = { +/** + * Theme config, after validation and normalization by themes and plugins. + * + * Themes and plugins can declare their own fields with declaration merging: + * + * ```ts + * declare module '@docusaurus/types' { + * interface ThemeConfig { + * myTheme: {position: 'top' | 'bottom'}; + * } + * } + * ``` + */ +export interface ThemeConfig { [key: string]: unknown; -}; +} + +/** + * Theme config, as written by users in their site config, before validation. + * + * Themes and plugins can declare their own fields with declaration merging, + * like for {@link ThemeConfig}. + */ +export interface UserThemeConfig { + [key: string]: unknown; +} export type StorageConfig = { type: SiteStorage['type']; @@ -403,5 +426,6 @@ export type Config = Overwrite< */ webpack?: boolean | WebpackConfig; future?: DeepPartial; + themeConfig?: UserThemeConfig; } >; diff --git a/packages/docusaurus-types/src/index.d.ts b/packages/docusaurus-types/src/index.d.ts index c523c79e4fb8..07abf4e4cd0b 100644 --- a/packages/docusaurus-types/src/index.d.ts +++ b/packages/docusaurus-types/src/index.d.ts @@ -8,6 +8,7 @@ export { RouterType, ThemeConfig, + UserThemeConfig, DocusaurusConfig, FutureConfig, WebpackConfig, diff --git a/packages/docusaurus/src/client/exports/useThemeConfig.ts b/packages/docusaurus/src/client/exports/useThemeConfig.ts new file mode 100644 index 000000000000..e99e4aed8070 --- /dev/null +++ b/packages/docusaurus/src/client/exports/useThemeConfig.ts @@ -0,0 +1,13 @@ +/** + * 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 useDocusaurusContext from './useDocusaurusContext'; +import type {ThemeConfig} from '@docusaurus/types'; + +export default function useThemeConfig(): ThemeConfig { + return useDocusaurusContext().siteConfig.themeConfig; +} diff --git a/packages/docusaurus/src/server/configValidation.ts b/packages/docusaurus/src/server/configValidation.ts index cbd96ef3595f..8d656833477d 100644 --- a/packages/docusaurus/src/server/configValidation.ts +++ b/packages/docusaurus/src/server/configValidation.ts @@ -27,6 +27,7 @@ import type { MarkdownConfig, MarkdownHooks, StorageConfig, + ThemeConfig, VcsConfig, VcsPreset, WebpackConfig, @@ -142,7 +143,8 @@ export const DEFAULT_CONFIG: Pick< scripts: [], clientModules: [], customFields: {}, - themeConfig: {}, + // Themes add their own fields when validating the theme config + themeConfig: {} as ThemeConfig, titleDelimiter: '|', noIndex: false, tagline: '', diff --git a/packages/docusaurus/src/webpack/__tests__/__snapshots__/base.test.ts.snap b/packages/docusaurus/src/webpack/__tests__/__snapshots__/base.test.ts.snap index 997ed1b3f24f..ea927f171235 100644 --- a/packages/docusaurus/src/webpack/__tests__/__snapshots__/base.test.ts.snap +++ b/packages/docusaurus/src/webpack/__tests__/__snapshots__/base.test.ts.snap @@ -22,6 +22,7 @@ exports[`base webpack config > creates webpack aliases 1`] = ` "@docusaurus/useIsBrowser": "../../../../client/exports/useIsBrowser.ts", "@docusaurus/useIsomorphicLayoutEffect": "../../../../client/exports/useIsomorphicLayoutEffect.tsx", "@docusaurus/useRouteContext": "../../../../client/exports/useRouteContext.tsx", + "@docusaurus/useThemeConfig": "../../../../client/exports/useThemeConfig.ts", "@generated": "../../../../../../..", "@mdx-js/react": "../../../../../../../node_modules/@mdx-js/react", "@site": "", diff --git a/packages/docusaurus/src/webpack/aliases/__tests__/__snapshots__/index.test.ts.snap b/packages/docusaurus/src/webpack/aliases/__tests__/__snapshots__/index.test.ts.snap index 50f0f435fe47..137839bad8f2 100644 --- a/packages/docusaurus/src/webpack/aliases/__tests__/__snapshots__/index.test.ts.snap +++ b/packages/docusaurus/src/webpack/aliases/__tests__/__snapshots__/index.test.ts.snap @@ -22,6 +22,7 @@ exports[`getDocusaurusAliases > returns appropriate webpack aliases 1`] = ` "@docusaurus/useIsBrowser": "/packages/docusaurus/src/client/exports/useIsBrowser.ts", "@docusaurus/useIsomorphicLayoutEffect": "/packages/docusaurus/src/client/exports/useIsomorphicLayoutEffect.tsx", "@docusaurus/useRouteContext": "/packages/docusaurus/src/client/exports/useRouteContext.tsx", + "@docusaurus/useThemeConfig": "/packages/docusaurus/src/client/exports/useThemeConfig.ts", } `; diff --git a/website/docs/api/plugin-methods/static-methods.mdx b/website/docs/api/plugin-methods/static-methods.mdx index 81ff632e2a10..e29f1cab5309 100644 --- a/website/docs/api/plugin-methods/static-methods.mdx +++ b/website/docs/api/plugin-methods/static-methods.mdx @@ -84,10 +84,10 @@ export function validateThemeConfig({themeConfig, validate}) { ### TypeScript types {/* #validateThemeConfig-types */} -The `@docusaurus/theme-common` package declares the theme config types with 2 interfaces: `ThemeConfig` after validation and normalization, and `UserThemeConfig` as written in `docusaurus.config.js`. Your theme can add its own fields to them with TypeScript [declaration merging](https://www.typescriptlang.org/docs/handbook/declaration-merging.html#module-augmentation): +The `@docusaurus/types` package declares the theme config types with 2 interfaces: `ThemeConfig` after validation and normalization, and `UserThemeConfig` as written in `docusaurus.config.js`. Your theme can add its own fields to them with TypeScript [declaration merging](https://www.typescriptlang.org/docs/handbook/declaration-merging.html#module-augmentation): ```ts title="my-theme/src/index.ts" -declare module '@docusaurus/theme-common' { +declare module '@docusaurus/types' { interface ThemeConfig { myTheme: {position: 'top' | 'bottom'}; } @@ -97,4 +97,4 @@ declare module '@docusaurus/theme-common' { } ``` -Once your theme types are loaded, `useThemeConfig().myTheme` is typed, and site authors get autocompletion for `myTheme` in their `themeConfig` with `satisfies Preset.ThemeConfig`. +Once your theme types are loaded, [`useThemeConfig()`](../../docusaurus-core.mdx#useThemeConfig) returns a typed `myTheme` field, and site authors get autocompletion for `myTheme` in their `themeConfig`. diff --git a/website/docs/docusaurus-core.mdx b/website/docs/docusaurus-core.mdx index f5f15b989e89..bcdff16ce328 100644 --- a/website/docs/docusaurus-core.mdx +++ b/website/docs/docusaurus-core.mdx @@ -407,6 +407,25 @@ The `siteConfig` object only contains **serializable values** (values that are p ::: +### `useThemeConfig` {/* #useThemeConfig */} + +React hook to access the [`themeConfig`](api/docusaurus.config.js.mdx#themeConfig) object of the site, after themes and plugins have [validated and normalized it](api/plugin-methods/static-methods.mdx#validateThemeConfig). + +Its TypeScript type includes the fields declared by the themes and plugins you installed. + +Usage example: + +```jsx +import React from 'react'; +import useThemeConfig from '@docusaurus/useThemeConfig'; + +const MyComponent = () => { + // highlight-next-line + const {navbar} = useThemeConfig(); + return
{navbar.title}
; +}; +``` + ### `useIsBrowser` {/* #useIsBrowser */} Returns `true` after initial hydration completes in the browser. From 71544cd813717968b1f5fbf2eaa101ab05885035 Mon Sep 17 00:00:00 2001 From: sebastien Date: Fri, 30 Oct 2026 12:10:39 +0100 Subject: [PATCH 3/3] fix(theme-search-algolia): type the SearchBar theme config with useAlgoliaThemeConfig() (AI-assisted) siteConfig.themeConfig.algolia is now typed, so the former cast from unknown no longer compiles. Co-Authored-By: Claude Opus 5.5 --- .../src/theme/SearchBar/index.tsx | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/packages/docusaurus-theme-search-algolia/src/theme/SearchBar/index.tsx b/packages/docusaurus-theme-search-algolia/src/theme/SearchBar/index.tsx index 6b46c11930ad..aa0cd473396e 100644 --- a/packages/docusaurus-theme-search-algolia/src/theme/SearchBar/index.tsx +++ b/packages/docusaurus-theme-search-algolia/src/theme/SearchBar/index.tsx @@ -15,6 +15,7 @@ import {useHistory} from '@docusaurus/router'; import {isRegexpStringMatch} from '@docusaurus/theme-common'; import { useAlgoliaContextualFacetFilters, + useAlgoliaThemeConfig, useSearchResultUrlProcessor, useAlgoliaAskAi, mergeFacetFilters, @@ -310,14 +311,14 @@ function DocSearch({externalUrlRegex, ...props}: DocSearchV4Props) { } export default function SearchBar(props: Partial): ReactNode { - const {siteConfig} = useDocusaurusContext(); + const {algolia} = useAlgoliaThemeConfig(); - const docSearchProps: DocSearchV4Props = { - ...(siteConfig.themeConfig.algolia as DocSearchV4Props), + const docSearchProps = { + ...algolia, // Let props override theme config // See https://github.com/facebook/docusaurus/pull/11581 ...props, - }; + } as DocSearchV4Props; return ; }