To set up your site's blog, start by creating a blog directory.
-
Use the Fast Track to understand Docusaurus in 5 minutes ⏱!
Use docusaurus.new to test Docusaurus immediately in your browser!
-
++++tip
-
Admonition with different syntax
-
++++
"
+"The blog feature enables you to deploy in no time a full-featured blog.
+
+
+ Check the [Blog Plugin API Reference documentation](./api/plugins/plugin-content-blog.md) for an exhaustive list of options.
+
+
+## Initial setup /{#initial-setup}
+
+To set up your site's blog, start by creating a \`blog\` directory.
+
+
+ Use the **[Fast Track](introduction.md#fast-track)** to understand Docusaurus in **5 minutes ⏱**!
+
+ Use **[docusaurus.new](https://docusaurus.new)** to test Docusaurus immediately in your browser!
+
+
+++++tip
+
+Admonition with different syntax
+
+++++
+"
`;
exports[`admonitions remark plugin > attributes 1`] = `
-"
Admonitions with attributes
-
An info admonition with a className attribute.
-
An info admonition with multiple className attributes.
-
An info admonition with a custom id attribute.
-
An info admonition with both id and className attributes.
-
Arbitrary attributes are ignored.
"
+"Admonitions with attributes
+
+
+ An info admonition with a className attribute.
+
+
+
+ An info admonition with multiple className attributes.
+
+
+
+ An info admonition with a custom id attribute.
+
+
+
+ An info admonition with both id and className attributes.
+
+
+
+ Arbitrary attributes are ignored.
+
+"
`;
exports[`admonitions remark plugin > base 1`] = `
-"
The blog feature enables you to deploy in no time a full-featured blog.
To set up your site's blog, start by creating a blog directory.
-
Use the Fast Track to understand Docusaurus in 5 minutes ⏱!
Use docusaurus.new to test Docusaurus immediately in your browser!
-
++++tip
-
Admonition with different syntax
-
++++
"
+"The blog feature enables you to deploy in no time a full-featured blog.
+
+
+ Check the [Blog Plugin API Reference documentation](./api/plugins/plugin-content-blog.md) for an exhaustive list of options.
+
+
+## Initial setup /{#initial-setup}
+
+To set up your site's blog, start by creating a \`blog\` directory.
+
+
+ Use the **[Fast Track](introduction.md#fast-track)** to understand Docusaurus in **5 minutes ⏱**!
+
+ Use **[docusaurus.new](https://docusaurus.new)** to test Docusaurus immediately in your browser!
+
+
+++++tip
+
+Admonition with different syntax
+
+++++
+"
`;
exports[`admonitions remark plugin > default behavior for custom keyword 1`] = `
-"
The blog feature enables you to deploy in no time a full-featured blog.
To set up your site's blog, start by creating a blog directory.
-
Use the Fast Track to understand Docusaurus in 5 minutes ⏱!
Use docusaurus.new to test Docusaurus immediately in your browser!
-
++++tip
-
Admonition with different syntax
-
++++
"
+"The blog feature enables you to deploy in no time a full-featured blog.
+
+:::info[Sample Title]
+Check the [Blog Plugin API Reference documentation](./api/plugins/plugin-content-blog.md) for an exhaustive list of options.
+:::
+
+## Initial setup /{#initial-setup}
+
+To set up your site's blog, start by creating a \`blog\` directory.
+
+
+ Use the **[Fast Track](introduction.md#fast-track)** to understand Docusaurus in **5 minutes ⏱**!
+
+ Use **[docusaurus.new](https://docusaurus.new)** to test Docusaurus immediately in your browser!
+
+
+++++tip
+
+Admonition with different syntax
+
+++++
+"
`;
exports[`admonitions remark plugin > interpolation 1`] = `
-"
"
+"
+ Take care of snowstorms...
+
+
+:::fixed-unusedDirective
+:::
+
+/:::NotAContainerDirective with a phrase after
+
+/:::
+
+Phrase before :::NotAContainerDirective
+
+/:::
+"
`;
exports[`directives remark plugin - client compiler > onUnusedMarkdownDirectives > function form > if file contains unused leaf directive > result 1`] = `
-"
-
Leaf directive in a phrase ::NotALeafDirective
-
::NotALeafDirective with a phrase after
"
+"::fixed-unusedLeafDirective
+
+Leaf directive in a phrase ::NotALeafDirective
+
+/::NotALeafDirective with a phrase after
+"
`;
exports[`directives remark plugin - client compiler > onUnusedMarkdownDirectives > function form > if file contains unused text directive > result 1`] = `
-"
"
+"
+ Take care of snowstorms...
+
+
+:::unusedDirective
+unused directive content
+:::
+
+/:::NotAContainerDirective with a phrase after
+
+/:::
+
+Phrase before :::NotAContainerDirective
+
+/:::
+"
`;
exports[`directives remark plugin - client compiler > onUnusedMarkdownDirectives > ignore > if file contains unused leaf directive > result 1`] = `
-"
-
Leaf directive in a phrase ::NotALeafDirective
-
::NotALeafDirective with a phrase after
"
+"::unusedLeafDirective
+
+Leaf directive in a phrase ::NotALeafDirective
+
+/::NotALeafDirective with a phrase after
+"
`;
exports[`directives remark plugin - client compiler > onUnusedMarkdownDirectives > ignore > if file contains unused text directive > result 1`] = `
-"
"
+"
+ Take care of snowstorms...
+
+
+:::unusedDirective
+unused directive content
+:::
+
+/:::NotAContainerDirective with a phrase after
+
+/:::
+
+Phrase before :::NotAContainerDirective
+
+/:::
+"
`;
exports[`directives remark plugin - server compiler > default behavior for leaf directives > result 1`] = `
-"
-
Leaf directive in a phrase ::NotALeafDirective
-
::NotALeafDirective with a phrase after
"
+"::unusedLeafDirective
+
+Leaf directive in a phrase ::NotALeafDirective
+
+/::NotALeafDirective with a phrase after
+"
`;
exports[`directives remark plugin - server compiler > default behavior for text directives > result 1`] = `
-"
Simple: textDirective1
-
Simple: textDirectiveCode
-
-
Simple:textDirective2
-
Simple
label
-
Simple
-
Simple:textDirective5
-
Simple:textDirectiveCode
-
"
+"Simple: textDirective1
+
+\`\`\`sh
+Simple: textDirectiveCode
+\`\`\`
+
+Simple/:textDirective2
+
+Simple:textDirective3[label]
+
+Simple:textDirective4{age="42"}
+
+Simple/:textDirective5
+
+\`\`\`sh
+Simple:textDirectiveCode
+\`\`\`
+"
`;
diff --git a/packages/docusaurus-mdx-loader/src/remark/unusedDirectives/__tests__/index.test.ts b/packages/docusaurus-mdx-loader/src/remark/unusedDirectives/__tests__/index.test.ts
index 5b79a23a5018..3a1da3537b99 100644
--- a/packages/docusaurus-mdx-loader/src/remark/unusedDirectives/__tests__/index.test.ts
+++ b/packages/docusaurus-mdx-loader/src/remark/unusedDirectives/__tests__/index.test.ts
@@ -9,8 +9,7 @@ import {describe, expect, it, vi} from 'vitest';
import path from 'path';
import {remark} from 'remark';
import directives from 'remark-directive';
-import remark2rehype from 'remark-rehype';
-import stringify from 'rehype-stringify';
+import mdx from 'remark-mdx';
import {read} from 'to-vfile';
import plugin, {type PluginOptions} from '../index';
import admonition from '../../admonitions';
@@ -24,8 +23,7 @@ const getProcessor = (options?: Partial) => {
onUnusedMarkdownDirectives: 'warn',
...options,
})
- .use(remark2rehype)
- .use(stringify);
+ .use(mdx);
};
const processFixture = async (
diff --git a/packages/docusaurus-mdx-loader/src/remark/unusedDirectives/index.ts b/packages/docusaurus-mdx-loader/src/remark/unusedDirectives/index.ts
index be0820be7650..4296287f053f 100644
--- a/packages/docusaurus-mdx-loader/src/remark/unusedDirectives/index.ts
+++ b/packages/docusaurus-mdx-loader/src/remark/unusedDirectives/index.ts
@@ -126,7 +126,7 @@ function transformSimpleTextDirectiveToString(textDirective: Directives) {
}
function isUnusedDirective(directive: Directives) {
- // If directive data is set (notably hName/hProperties set by admonitions)
+ // If directive data is set (notably hName/hProperties)
// this usually means the directive has been handled by another plugin
return !directive.data;
}
@@ -144,7 +144,7 @@ const plugin: Plugin = function plugin(
// @ts-expect-error: TODO fix type
visit(tree, directiveTypes, (directive: Directives) => {
- // If directive data is set (hName/hProperties set by admonitions)
+ // If directive data is set (hName/hProperties)
// this usually means the directive has been handled by another plugin
if (isUnusedDirective(directive)) {
if (isSimpleTextDirective(directive)) {
diff --git a/packages/docusaurus-mdx-loader/src/types.d.mts b/packages/docusaurus-mdx-loader/src/types.d.mts
index 012c32ec73ef..118c121f2123 100644
--- a/packages/docusaurus-mdx-loader/src/types.d.mts
+++ b/packages/docusaurus-mdx-loader/src/types.d.mts
@@ -22,6 +22,7 @@ declare module 'vfile' {
declare module 'unist' {
interface Data {
+ admonitionTitle?: boolean;
hName?: string;
hProperties?: Record;
}
diff --git a/packages/docusaurus-theme-classic/src/theme-classic.d.ts b/packages/docusaurus-theme-classic/src/theme-classic.d.ts
index d1d80a5289a3..617c4c7d67f3 100644
--- a/packages/docusaurus-theme-classic/src/theme-classic.d.ts
+++ b/packages/docusaurus-theme-classic/src/theme-classic.d.ts
@@ -1203,6 +1203,8 @@ declare module '@theme/MDXComponents' {
readonly h4: (props: ComponentProps<'h4'>) => ReactNode;
readonly h5: (props: ComponentProps<'h5'>) => ReactNode;
readonly h6: (props: ComponentProps<'h6'>) => ReactNode;
+ readonly Admonition: typeof Admonition;
+ /** @deprecated Use Admonition instead. */
readonly admonition: typeof Admonition;
readonly mermaid: typeof Mermaid;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
diff --git a/packages/docusaurus-theme-classic/src/theme/Admonition/__tests__/index.test.tsx b/packages/docusaurus-theme-classic/src/theme/Admonition/__tests__/index.test.tsx
new file mode 100644
index 000000000000..3653d12ed1e3
--- /dev/null
+++ b/packages/docusaurus-theme-classic/src/theme/Admonition/__tests__/index.test.tsx
@@ -0,0 +1,54 @@
+/**
+ * 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 {describe, expect, it} from 'vitest';
+import React from 'react';
+import {renderToStaticMarkup} from 'react-dom/server';
+import Admonition from '../index';
+import MDXComponents from '../../MDXComponents';
+
+describe('Admonition', () => {
+ it('registers the component under its emitted MDX name', () => {
+ expect(MDXComponents.Admonition).toBe(Admonition);
+ });
+
+ it('renders a rich title separately from the body', () => {
+ const result = renderToStaticMarkup(
+ Custom title}
+ icon="💡"
+ id="custom-id"
+ className="custom-class">
+
');
+ expect(result).toContain('💡');
+ expect(result).toContain('id="custom-id"');
+ expect(result).toContain('custom-class');
+ });
+
+ it('keeps the default title when no title prop is provided', () => {
+ const result = renderToStaticMarkup(
+
+
Body
+ ,
+ );
+ expect(result).toContain('note');
+ });
+
+ it('does not render a content container for title-only admonitions', () => {
+ const result = renderToStaticMarkup(
+ Title only}>
+ {null}
+ ,
+ );
+ expect(result).toMatch(/Title only<\/strong><\/div><\/div>$/);
+ });
+});
diff --git a/packages/docusaurus-theme-classic/src/theme/Admonition/index.tsx b/packages/docusaurus-theme-classic/src/theme/Admonition/index.tsx
index a306721ed0a7..e5944bfa0ded 100644
--- a/packages/docusaurus-theme-classic/src/theme/Admonition/index.tsx
+++ b/packages/docusaurus-theme-classic/src/theme/Admonition/index.tsx
@@ -6,7 +6,6 @@
*/
import React, {type ComponentType, type ReactNode} from 'react';
-import {processAdmonitionProps} from '@docusaurus/theme-common';
import type {Props} from '@theme/Admonition';
import AdmonitionTypes from '@theme/Admonition/Types';
@@ -21,8 +20,7 @@ function getAdmonitionTypeComponent(type: string): ComponentType {
return AdmonitionTypes.info!;
}
-export default function Admonition(unprocessedProps: Props): ReactNode {
- const props = processAdmonitionProps(unprocessedProps);
+export default function Admonition(props: Props): ReactNode {
const AdmonitionTypeComponent = getAdmonitionTypeComponent(props.type);
// eslint-disable-next-line react-hooks/static-components
return ;
diff --git a/packages/docusaurus-theme-classic/src/theme/MDXComponents/index.tsx b/packages/docusaurus-theme-classic/src/theme/MDXComponents/index.tsx
index 49d438bc5f68..a028cf77fcdc 100644
--- a/packages/docusaurus-theme-classic/src/theme/MDXComponents/index.tsx
+++ b/packages/docusaurus-theme-classic/src/theme/MDXComponents/index.tsx
@@ -36,7 +36,8 @@ const MDXComponents: MDXComponentsObject = {
h4: (props: ComponentProps<'h4'>) => ,
h5: (props: ComponentProps<'h5'>) => ,
h6: (props: ComponentProps<'h6'>) => ,
- admonition: Admonition,
+ Admonition,
+ admonition: Admonition, // Legacy MDX component name
mermaid: Mermaid,
};
diff --git a/packages/docusaurus-theme-common/src/utils/admonitionUtils.tsx b/packages/docusaurus-theme-common/src/utils/admonitionUtils.tsx
index 38022009e33f..644c0eff4244 100644
--- a/packages/docusaurus-theme-common/src/utils/admonitionUtils.tsx
+++ b/packages/docusaurus-theme-common/src/utils/admonitionUtils.tsx
@@ -28,6 +28,10 @@ function extractMDXAdmonitionTitle(children: ReactNode): {
};
}
+/**
+ * @deprecated Rich admonition titles are now passed through the title prop.
+ * Kept for compatibility with previously swizzled Admonition components.
+ */
export function processAdmonitionProps<
Props extends {readonly children: ReactNode; readonly title?: ReactNode},
>(props: Props): Props {
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 612f91a38860..65c520d37450 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -841,6 +841,9 @@ importers:
fs-extra:
specifier: ^11.4.0
version: 11.4.0
+ hast-util-to-estree:
+ specifier: ^3.1.3
+ version: 3.1.3(supports-color@10.2.2)
image-size:
specifier: ^2.0.4
version: 2.0.4
@@ -902,6 +905,9 @@ importers:
'@types/estree':
specifier: ^1.0.9
version: 1.0.9
+ '@types/hast':
+ specifier: ^3.0.5
+ version: 3.0.5
'@types/mdast':
specifier: ^4.0.4
version: 4.0.4
diff --git a/vitest.config.ts b/vitest.config.ts
index d0aca6592d66..efa2c8ae0371 100644
--- a/vitest.config.ts
+++ b/vitest.config.ts
@@ -55,7 +55,7 @@ const resolveAliases: AliasOptions = [
// This won't always be true, but good enough for now
{
find: /^@theme\/(.*)$/,
- replacement: '@docusaurus/theme-classic/src/theme/$1',
+ replacement: `${rootDir}packages/docusaurus-theme-classic/src/theme/$1`,
},
// Resolve `@site/*` aliases against our own website
diff --git a/website/_dogfooding/_docs tests/tests/admonitions.mdx b/website/_dogfooding/_docs tests/tests/admonitions.mdx
index 4a80c46b2734..14f625b4acfd 100644
--- a/website/_dogfooding/_docs tests/tests/admonitions.mdx
+++ b/website/_dogfooding/_docs tests/tests/admonitions.mdx
@@ -14,6 +14,40 @@
:::
+## Rich titles
+
+:::tip[**Rich** title with `code`, a [link](/docs/markdown-features/admonitions), and :smile:]
+
+The title formatting should match the formatting in the body: **bold**, `code`, a [link](/docs/markdown-features/admonitions), and :smile:.
+
+:::
+
+:::info[JSX title: red and {1 + 1}]
+
+JSX and expressions also work in the title.
+
+:::
+
+:::note[**Title only**]
+
+:::
+
+:::note[]
+
+An empty label keeps the default title.
+
+:::
+
+::::info[**Outer title**]
+
+:::warning[*Nested title*]
+
+Nested admonitions keep their own titles.
+
+:::
+
+::::
+
## Large font icon
import Admonition from '@theme/Admonition';