diff --git a/packages/docusaurus-theme-classic/src/theme/Heading/index.tsx b/packages/docusaurus-theme-classic/src/theme/Heading/index.tsx
index 8f7c4fc2bf..415cd65f2e 100644
--- a/packages/docusaurus-theme-classic/src/theme/Heading/index.tsx
+++ b/packages/docusaurus-theme-classic/src/theme/Heading/index.tsx
@@ -8,18 +8,16 @@
import React, {type ReactNode} from 'react';
import clsx from 'clsx';
import {translate} from '@docusaurus/Translate';
-import {useThemeConfig} from '@docusaurus/theme-common';
+import {useAnchorTargetClassName} from '@docusaurus/theme-common';
import Link from '@docusaurus/Link';
import useBrokenLinks from '@docusaurus/useBrokenLinks';
import type {Props} from '@theme/Heading';
-
-import styles from './styles.module.css';
+import './styles.module.css';
export default function Heading({as: As, id, ...props}: Props): ReactNode {
const brokenLinks = useBrokenLinks();
- const {
- navbar: {hideOnScroll},
- } = useThemeConfig();
+ const anchorTargetClassName = useAnchorTargetClassName(id);
+
// H1 headings do not need an id because they don't appear in the TOC.
if (As === 'h1' || !id) {
return ;
@@ -41,13 +39,7 @@ export default function Heading({as: As, id, ...props}: Props): ReactNode {
return (
{props.children}
- );
-}
export default function MDXA(props: Props): ReactNode {
- if (isFootnoteRef(props)) {
- return ;
- }
- return ;
+ // MDX Footnotes have ids such as
+ const anchorTargetClassName = useAnchorTargetClassName(props.id);
+
+ return (
+
+ );
}
diff --git a/packages/docusaurus-theme-classic/src/theme/MDXComponents/A/styles.module.css b/packages/docusaurus-theme-classic/src/theme/MDXComponents/A/styles.module.css
deleted file mode 100644
index 295757363a..0000000000
--- a/packages/docusaurus-theme-classic/src/theme/MDXComponents/A/styles.module.css
+++ /dev/null
@@ -1,20 +0,0 @@
-/**
- * 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.
- */
-
-/*
-When the navbar is sticky, ensure that on footnote click,
-the browser does not scroll to the ref behind the navbar
-See https://github.com/facebook/docusaurus/issues/11232
-See also headings case https://x.com/JoshWComeau/status/1332015868725891076
- */
-.footnoteRefStickyNavbar {
- scroll-margin-top: calc(var(--ifm-navbar-height) + 0.5rem);
-}
-
-.footnoteRefHideOnScrollNavbar {
- scroll-margin-top: 0.5rem;
-}
diff --git a/packages/docusaurus-theme-classic/src/theme/MDXComponents/Li.tsx b/packages/docusaurus-theme-classic/src/theme/MDXComponents/Li.tsx
index 74a8a4add4..bb2a3d6497 100644
--- a/packages/docusaurus-theme-classic/src/theme/MDXComponents/Li.tsx
+++ b/packages/docusaurus-theme-classic/src/theme/MDXComponents/Li.tsx
@@ -6,12 +6,17 @@
*/
import React, {type ReactNode} from 'react';
+import clsx from 'clsx';
import useBrokenLinks from '@docusaurus/useBrokenLinks';
+import {useAnchorTargetClassName} from '@docusaurus/theme-common';
import type {Props} from '@theme/MDXComponents/Li';
export default function MDXLi(props: Props): ReactNode | undefined {
// MDX Footnotes have ids such as
useBrokenLinks().collectAnchor(props.id);
+ const anchorTargetClassName = useAnchorTargetClassName(props.id);
- return ;
+ return (
+
+ );
}
diff --git a/packages/docusaurus-theme-common/src/index.ts b/packages/docusaurus-theme-common/src/index.ts
index 7243780701..1c9e15f1dc 100644
--- a/packages/docusaurus-theme-common/src/index.ts
+++ b/packages/docusaurus-theme-common/src/index.ts
@@ -109,6 +109,8 @@ export {
useSearchLinkCreator,
} from './hooks/useSearchPage';
+export {useAnchorTargetClassName} from './utils/anchorUtils';
+
export {isMultiColumnFooterLinks} from './utils/footerUtils';
export {isRegexpStringMatch} from './utils/regexpUtils';
diff --git a/packages/docusaurus-theme-common/src/utils/anchorUtils.module.css b/packages/docusaurus-theme-common/src/utils/anchorUtils.module.css
new file mode 100644
index 0000000000..1904425c03
--- /dev/null
+++ b/packages/docusaurus-theme-common/src/utils/anchorUtils.module.css
@@ -0,0 +1,14 @@
+/**
+ * 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.
+ */
+
+.anchorTargetStickyNavbar {
+ scroll-margin-top: calc(var(--ifm-navbar-height) + 0.5rem);
+}
+
+.anchorTargetHideOnScrollNavbar {
+ scroll-margin-top: 0.5rem;
+}
diff --git a/packages/docusaurus-theme-common/src/utils/anchorUtils.ts b/packages/docusaurus-theme-common/src/utils/anchorUtils.ts
new file mode 100644
index 0000000000..64c2832644
--- /dev/null
+++ b/packages/docusaurus-theme-common/src/utils/anchorUtils.ts
@@ -0,0 +1,31 @@
+/**
+ * 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 {useThemeConfig} from './useThemeConfig';
+import styles from './anchorUtils.module.css';
+
+/**
+ * When the navbar is sticky, this ensures that when clicking a hash link,
+ * we do not navigate to an anchor that will appear below the navbar.
+ * This happens in particular for MDX headings and footnotes.
+ *
+ * See https://github.com/facebook/docusaurus/issues/11232
+ * See also headings case https://x.com/JoshWComeau/status/1332015868725891076
+ */
+export function useAnchorTargetClassName(
+ id: string | undefined,
+): string | undefined {
+ const {
+ navbar: {hideOnScroll},
+ } = useThemeConfig();
+ if (typeof id === 'undefined') {
+ return undefined;
+ }
+ return hideOnScroll
+ ? styles.anchorTargetHideOnScrollNavbar
+ : styles.anchorTargetStickyNavbar;
+}
diff --git a/website/_dogfooding/_docs tests/tests/footnotes.mdx b/website/_dogfooding/_docs tests/tests/footnotes.mdx
new file mode 100644
index 0000000000..da28ea2d66
--- /dev/null
+++ b/website/_dogfooding/_docs tests/tests/footnotes.mdx
@@ -0,0 +1,146 @@
+# Footnotes
+
+Lorem ipsum dolor sit amet [^1] [^2]
+
+Lorem ipsum dolor sit amet [^3]
+
+Lorem ipsum dolor sit amet [^4]
+
+Lorem ipsum dolor sit amet [^5]
+
+Lorem ipsum dolor sit amet [^6]
+
+Lorem ipsum dolor sit amet [^7]
+
+Lorem ipsum dolor sit amet [^8]
+
+Lorem ipsum dolor sit amet [^9]
+
+Lorem ipsum dolor sit amet [^10]
+
+Lorem ipsum dolor sit amet [^11]
+
+Lorem ipsum dolor sit amet [^12]
+
+Lorem ipsum dolor sit amet [^13]
+
+Lorem ipsum dolor sit amet [^14]
+
+Lorem ipsum dolor sit amet [^15]
+
+Lorem ipsum dolor sit amet [^16]
+
+Lorem ipsum dolor sit amet [^17]
+
+Lorem ipsum dolor sit amet [^18]
+
+Lorem ipsum dolor sit amet [^19]
+
+Lorem ipsum dolor sit amet [^20]
+
+Lorem ipsum dolor sit amet [^21] [^22]
+
+Lorem ipsum dolor sit amet [^23]
+
+Lorem ipsum dolor sit amet [^24]
+
+Lorem ipsum dolor sit amet [^25]
+
+Lorem ipsum dolor sit amet [^26]
+
+Lorem ipsum dolor sit amet [^27]
+
+Lorem ipsum dolor sit amet [^28]
+
+Lorem ipsum dolor sit amet [^29]
+
+Lorem ipsum dolor sit amet [^30]
+
+Lorem ipsum dolor sit amet [^31]
+
+Lorem ipsum dolor sit amet [^32]
+
+Lorem ipsum dolor sit amet [^33]
+
+Lorem ipsum dolor sit amet [^34]
+
+Lorem ipsum dolor sit amet [^35]
+
+Lorem ipsum dolor sit amet [^36]
+
+Lorem ipsum dolor sit amet [^37]
+
+Lorem ipsum dolor sit amet [^38]
+
+Lorem ipsum dolor sit amet [^39]
+
+Lorem ipsum dolor sit amet [^40]
+
+Lorem ipsum dolor sit amet [^41]
+
+Lorem ipsum dolor sit amet [^42]
+
+Lorem ipsum dolor sit amet [^43]
+
+Lorem ipsum dolor sit amet [^44]
+
+Lorem ipsum dolor sit amet [^45]
+
+Lorem ipsum dolor sit amet [^46]
+
+Lorem ipsum dolor sit amet [^47]
+
+Lorem ipsum dolor sit amet [^48]
+
+Lorem ipsum dolor sit amet [^49] [^50]
+
+[^1]: footnote 1
+[^2]: footnote 2
+[^3]: footnote 3
+[^4]: footnote 4
+[^5]: footnote 5
+[^6]: footnote 6
+[^7]: footnote 7
+[^8]: footnote 8
+[^9]: footnote 9
+[^10]: footnote 10
+[^11]: footnote 11
+[^12]: footnote 12
+[^13]: footnote 13
+[^14]: footnote 14
+[^15]: footnote 15
+[^16]: footnote 16
+[^17]: footnote 17
+[^18]: footnote 18
+[^19]: footnote 19
+[^20]: footnote 20
+[^21]: footnote 21
+[^22]: footnote 22
+[^23]: footnote 23
+[^24]: footnote 24
+[^25]: footnote 25
+[^26]: footnote 26
+[^27]: footnote 27
+[^28]: footnote 28
+[^29]: footnote 29
+[^30]: footnote 30
+[^31]: footnote 31
+[^32]: footnote 32
+[^33]: footnote 33
+[^34]: footnote 34
+[^35]: footnote 35
+[^36]: footnote 36
+[^37]: footnote 37
+[^38]: footnote 38
+[^39]: footnote 39
+[^40]: footnote 40
+[^41]: footnote 41
+[^42]: footnote 42
+[^43]: footnote 43
+[^44]: footnote 44
+[^45]: footnote 45
+[^46]: footnote 46
+[^47]: footnote 47
+[^48]: footnote 48
+[^49]: footnote 49
+[^50]: footnote 50