diff --git a/astro.config.ts b/astro.config.ts
index 54326c36ef9d24f..805a8147448b618 100644
--- a/astro.config.ts
+++ b/astro.config.ts
@@ -4,7 +4,6 @@ import tailwind from "@astrojs/tailwind";
import starlightDocSearch from "@astrojs/starlight-docsearch";
import starlightImageZoom from "starlight-image-zoom";
import liveCode from "astro-live-code";
-import rehypeSlug from "rehype-slug";
import rehypeMermaid from "rehype-mermaid";
import rehypeAutolinkHeadings, {
type Options as rehypeAutolinkHeadingsOptions,
@@ -17,6 +16,7 @@ import icon from "astro-icon";
import sitemap from "@astrojs/sitemap";
import react from "@astrojs/react";
import rehypeTitleFigure from "rehype-title-figure";
+import rehypeHeadingSlugs from "./plugins/rehype/heading-slugs";
const runLinkCheck = process.env.RUN_LINK_CHECK || false;
@@ -95,7 +95,7 @@ export default defineConfig({
rel: ["noopener"],
},
],
- rehypeSlug,
+ rehypeHeadingSlugs,
[rehypeAutolinkHeadings, autolinkConfig],
// @ts-expect-error TODO: fix types
rehypeTitleFigure,
diff --git a/package-lock.json b/package-lock.json
index 7beef3d52e7f5b7..b7df730b5bbd693 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -57,7 +57,6 @@
"rehype-autolink-headings": "^7.1.0",
"rehype-external-links": "^3.0.0",
"rehype-mermaid": "^2.1.0",
- "rehype-slug": "^6.0.0",
"rehype-title-figure": "^0.1.2",
"sharp": "^0.33.5",
"solarflare-theme": "^0.0.2",
@@ -69,6 +68,7 @@
"tippy.js": "^6.3.7",
"tsx": "^4.19.1",
"typescript": "^5.5.4",
+ "unist-util-visit": "^5.0.0",
"vitest": "2.0.5",
"wrangler": "^3.78.10",
"yaml": "^2.5.1"
@@ -13702,24 +13702,6 @@
"url": "https://opencollective.com/unified"
}
},
- "node_modules/rehype-slug": {
- "version": "6.0.0",
- "resolved": "https://registry.npmjs.org/rehype-slug/-/rehype-slug-6.0.0.tgz",
- "integrity": "sha512-lWyvf/jwu+oS5+hL5eClVd3hNdmwM1kAC0BUvEGD19pajQMIzcNUd/k9GsfQ+FfECvX+JE+e9/btsKH0EjJT6A==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@types/hast": "^3.0.0",
- "github-slugger": "^2.0.0",
- "hast-util-heading-rank": "^3.0.0",
- "hast-util-to-string": "^3.0.0",
- "unist-util-visit": "^5.0.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/unified"
- }
- },
"node_modules/rehype-stringify": {
"version": "10.0.0",
"resolved": "https://registry.npmjs.org/rehype-stringify/-/rehype-stringify-10.0.0.tgz",
@@ -16315,6 +16297,7 @@
"resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-5.0.0.tgz",
"integrity": "sha512-MR04uvD+07cwl/yhVuVWAtw+3GOR/knlL55Nd/wAdblk27GCVt3lqpTivy/tkJcZoNPzTwS1Y+KMojlLDhoTzg==",
"dev": true,
+ "license": "MIT",
"dependencies": {
"@types/unist": "^3.0.0",
"unist-util-is": "^6.0.0",
diff --git a/package.json b/package.json
index 689cc6e9fcdcfd7..0e1ce05e800be5d 100644
--- a/package.json
+++ b/package.json
@@ -69,7 +69,6 @@
"rehype-autolink-headings": "^7.1.0",
"rehype-external-links": "^3.0.0",
"rehype-mermaid": "^2.1.0",
- "rehype-slug": "^6.0.0",
"rehype-title-figure": "^0.1.2",
"sharp": "^0.33.5",
"solarflare-theme": "^0.0.2",
@@ -81,6 +80,7 @@
"tippy.js": "^6.3.7",
"tsx": "^4.19.1",
"typescript": "^5.5.4",
+ "unist-util-visit": "^5.0.0",
"vitest": "2.0.5",
"wrangler": "^3.78.10",
"yaml": "^2.5.1"
diff --git a/plugins/rehype/heading-slugs.ts b/plugins/rehype/heading-slugs.ts
new file mode 100644
index 000000000000000..42493dd7b643ec0
--- /dev/null
+++ b/plugins/rehype/heading-slugs.ts
@@ -0,0 +1,35 @@
+import { toString } from "hast-util-to-string";
+import { visit } from "unist-util-visit";
+import GithubSlugger from "github-slugger";
+
+const slugs = new GithubSlugger();
+
+// # foo {/*bar*/} = foo
+export default function () {
+ return function (tree: any) {
+ slugs.reset();
+
+ visit(tree, "element", function (element: any) {
+ if (/^h[1-6]$/.test(element.tagName)) {
+ const last = element.children.at(-1);
+
+ if (
+ last.type === "mdxTextExpression" &&
+ last.value.startsWith("/*") &&
+ last.value.endsWith("*/")
+ ) {
+ const id = last.value.slice(2, -2).trim();
+ element.properties.id = slugs.slug(id);
+
+ const text = element.children.at(-2);
+ text.value = text.value.trimEnd();
+ element.children.with(-2, text);
+ } else {
+ if (!element.properties.id) {
+ element.properties.id = slugs.slug(toString(element));
+ }
+ }
+ }
+ });
+ };
+}
diff --git a/src/content/docs/style-guide/components/anchor-heading.mdx b/src/content/docs/style-guide/components/anchor-heading.mdx
index 12635a69327c5c3..64b7a3d2110246f 100644
--- a/src/content/docs/style-guide/components/anchor-heading.mdx
+++ b/src/content/docs/style-guide/components/anchor-heading.mdx
@@ -17,7 +17,17 @@ import { AnchorHeading } from "~/components";
Markdown files (including partials) have this behavior by default, applied via rehype plugins. Therefore, the `AnchorHeading` component is usually only required when writing headings yourself inside components, or when working on non-markdown files.
-Additionally, `AnchorHeading` is useful when rendering partial files into one location where there are duplicate headings (for example, when there are multiple H3 corresponding to `/#create` in one page). `AnchorHeading` allows you to explicitly define fragments, ensuring that each heading can be referred correctly with unique anchors.
+To override the ID given to a heading within Markdown, add an MDX comment at the end of the line:
+
+```mdx
+# foo {/*bar*/}
+```
+
+It will result in the following HTML:
+
+```html
+foo
+```
:::note