From c6a86ff717199354e8511395adaea446c9fa8beb Mon Sep 17 00:00:00 2001 From: Balthasar Hofer Date: Thu, 27 Nov 2025 11:19:53 +0100 Subject: [PATCH] feat(core): support custom html elements in head tags (#11571) Co-authored-by: sebastien --- packages/docusaurus-types/src/plugin.d.ts | 2 ++ .../src/server/__tests__/configValidation.test.ts | 13 +++++++++++++ .../src/server/__tests__/htmlTags.test.ts | 2 +- packages/docusaurus/src/server/configValidation.ts | 11 ++++++++--- packages/docusaurus/src/server/htmlTags.ts | 13 +++++++++---- website/docs/api/docusaurus.config.js.mdx | 4 ++-- 6 files changed, 35 insertions(+), 10 deletions(-) diff --git a/packages/docusaurus-types/src/plugin.d.ts b/packages/docusaurus-types/src/plugin.d.ts index 7d8a91e8a6..25458e9fd8 100644 --- a/packages/docusaurus-types/src/plugin.d.ts +++ b/packages/docusaurus-types/src/plugin.d.ts @@ -104,6 +104,8 @@ export type HtmlTagObject = { tagName: string; /** The inner HTML */ innerHTML?: string; + /** Allow custom html elements, e.g. `` */ + customElement?: boolean; }; export type HtmlTags = string | HtmlTagObject | (string | HtmlTagObject)[]; diff --git a/packages/docusaurus/src/server/__tests__/configValidation.test.ts b/packages/docusaurus/src/server/__tests__/configValidation.test.ts index 98aea0370a..379ce8af28 100644 --- a/packages/docusaurus/src/server/__tests__/configValidation.test.ts +++ b/packages/docusaurus/src/server/__tests__/configValidation.test.ts @@ -315,6 +315,19 @@ describe('headTags', () => { `); }); + it('accepts headTags with a custom element without attributes', () => { + expect(() => + normalizeConfig({ + headTags: [ + { + tagName: 'my-custom-element', + customElement: true, + }, + ], + }), + ).not.toThrow(); + }); + it("throws error if headTags doesn't have string attributes", () => { expect(() => { normalizeConfig({ diff --git a/packages/docusaurus/src/server/__tests__/htmlTags.test.ts b/packages/docusaurus/src/server/__tests__/htmlTags.test.ts index 717e5ca79e..e46e60d595 100644 --- a/packages/docusaurus/src/server/__tests__/htmlTags.test.ts +++ b/packages/docusaurus/src/server/__tests__/htmlTags.test.ts @@ -189,7 +189,7 @@ describe('loadHtmlTags', () => { }, ]), ).toThrowErrorMatchingInlineSnapshot( - `"Error loading {"tagName":"endiliey","attributes":{"this":"is invalid"}}, "endiliey" is not a valid HTML tag."`, + `"Error loading {"tagName":"endiliey","attributes":{"this":"is invalid"}}, "endiliey" is not a valid HTML tag. Either use a valid "tagName" or set "customElement: true"."`, ); }); diff --git a/packages/docusaurus/src/server/configValidation.ts b/packages/docusaurus/src/server/configValidation.ts index f826788adf..e57a7ba45a 100644 --- a/packages/docusaurus/src/server/configValidation.ts +++ b/packages/docusaurus/src/server/configValidation.ts @@ -437,9 +437,14 @@ export const ConfigSchema = Joi.object({ .items( Joi.object({ tagName: Joi.string().required(), - attributes: Joi.object() - .pattern(/[\w-]+/, Joi.string()) - .required(), + attributes: Joi.object().when('customElement', { + is: Joi.valid(true), + then: Joi.optional(), + otherwise: Joi.object() + .pattern(/[\w-]+/, Joi.string()) + .required(), + }), + customElement: Joi.bool().default(false), }).unknown(), ) .messages({ diff --git a/packages/docusaurus/src/server/htmlTags.ts b/packages/docusaurus/src/server/htmlTags.ts index 46bd39ab75..52cbee8258 100644 --- a/packages/docusaurus/src/server/htmlTags.ts +++ b/packages/docusaurus/src/server/htmlTags.ts @@ -17,22 +17,27 @@ import type { RouterType, } from '@docusaurus/types'; +// TODO this should be done at config validation time, not here function assertIsHtmlTagObject(val: unknown): asserts val is HtmlTagObject { if (typeof val !== 'object' || !val) { throw new Error(`"${val}" is not a valid HTML tag object.`); } - if (typeof (val as HtmlTagObject).tagName !== 'string') { + const htmlTag = val as HtmlTagObject; + if (typeof htmlTag.tagName !== 'string') { throw new Error( `${JSON.stringify( val, )} is not a valid HTML tag object. "tagName" must be defined as a string.`, ); } - if (!(htmlTags as string[]).includes((val as HtmlTagObject).tagName)) { + if ( + !htmlTag.customElement && + !(htmlTags as string[]).includes(htmlTag.tagName) + ) { throw new Error( `Error loading ${JSON.stringify(val)}, "${ - (val as HtmlTagObject).tagName - }" is not a valid HTML tag.`, + htmlTag.tagName + }" is not a valid HTML tag. Either use a valid "tagName" or set "customElement: true".`, ); } } diff --git a/website/docs/api/docusaurus.config.js.mdx b/website/docs/api/docusaurus.config.js.mdx index a376628fb0..a21ebbcf17 100644 --- a/website/docs/api/docusaurus.config.js.mdx +++ b/website/docs/api/docusaurus.config.js.mdx @@ -767,9 +767,9 @@ export default { ### `headTags` {#headTags} -An array of tags that will be inserted in the HTML ``. The values must be objects that contain two properties; `tagName` and `attributes`. `tagName` must be a string that determines the tag being created; eg `"link"`. `attributes` must be an attribute-value map. +An array of tags that will be inserted in the HTML ``. The values must be objects that contain two properties; `tagName` and `attributes`. `tagName` must be a string that determines the tag being created; eg `"link"`. `attributes` must be an attribute-value map. When custom html elements are needed, set `customElement: true`. -- Type: `{ tagName: string; attributes: Object; }[]` +- Type: `{ tagName: string; attributes: Object; customElement?: boolean; }[]` Example: