diff --git a/packages/docs-app/src/components/blueprintDocs.tsx b/packages/docs-app/src/components/blueprintDocs.tsx
index bb85c31ffed..fbc25e24092 100644
--- a/packages/docs-app/src/components/blueprintDocs.tsx
+++ b/packages/docs-app/src/components/blueprintDocs.tsx
@@ -21,6 +21,7 @@ import { AnchorButton, BlueprintProvider, Classes, type Intent, Tag } from "@blu
import { type DocsCompleteData, type HeadingNode, npmData, type PageNode, SECTIONS } from "@blueprintjs/docs-data";
import {
Banner,
+ CopyPageMarkdownButton,
Documentation,
type DocumentationProps,
NavMenuItem,
@@ -143,15 +144,18 @@ export class BlueprintDocs extends Component;
};
- private renderPageActions = (page: { sourcePath: string }) => {
+ private renderPageActions = (page: { sourceMarkdown?: string; sourcePath: string }) => {
return (
-
+ <>
+
+
+ >
);
};
diff --git a/packages/docs-data/compile-docs-data.mts b/packages/docs-data/compile-docs-data.mts
index 450475206d9..2179c46a867 100755
--- a/packages/docs-data/compile-docs-data.mts
+++ b/packages/docs-data/compile-docs-data.mts
@@ -14,6 +14,7 @@ import semver from "semver";
import { Classes } from "@blueprintjs/core";
import { hooks, markedRenderer } from "./markdownRenderer.mjs";
+import { stripDocumentalistTags } from "./markdownExport.mts";
import { assignRoutes, buildNavTree, normalizeNavConfig } from "./navHelpers.mts";
import {
PACKAGES,
@@ -89,6 +90,10 @@ async function generateDocumentalistData(): Promise {
`../{${LIBRARY_PACKAGES}}/package.json`,
);
+ // Attach sourceMarkdown to each page so the docs UI can offer a "Copy page" button
+ // that hands an LLM-friendly markdown blob to the reader.
+ attachSourceMarkdown(docs.pages);
+
// Post-process: replace documentalist's nav with one built from nav.json
const rawConfig: RawNavStructure = JSON.parse(readFileSync(new URL("./nav.json", import.meta.url), "utf-8"));
validateNavConfig(rawConfig);
@@ -205,3 +210,18 @@ function applyNavConfig(docs: { pages: Record; nav: NavTreeNode
assignRoutes(navConfig, docs.pages);
docs.nav = buildNavTree(navConfig, docs.pages);
}
+
+function attachSourceMarkdown(pages: Record): void {
+ for (const page of Object.values(pages)) {
+ const sourcePath = (page as DocPage & { sourcePath?: string }).sourcePath;
+ if (sourcePath == null) {
+ continue;
+ }
+ try {
+ const raw = readFileSync(resolve(monorepoRootDir, sourcePath), "utf-8");
+ page.sourceMarkdown = stripDocumentalistTags(raw);
+ } catch {
+ // Source file disappeared between documentalist scan and now — non-fatal.
+ }
+ }
+}
diff --git a/packages/docs-data/markdownExport.mts b/packages/docs-data/markdownExport.mts
new file mode 100644
index 00000000000..45b2b5d8a49
--- /dev/null
+++ b/packages/docs-data/markdownExport.mts
@@ -0,0 +1,36 @@
+/* !
+ * (c) Copyright 2026 Palantir Technologies Inc. All rights reserved.
+ */
+
+/**
+ * Replace Documentalist's `@tag value` lines with markdown-friendly placeholders so the
+ * exported source can be pasted into LLM/IDE tooling without unfamiliar syntax. Lines
+ * inside fenced code blocks are left untouched (Sass `@use`/`@import` etc.).
+ */
+export function stripDocumentalistTags(source: string): string {
+ const lines = source.split("\n");
+ let inFence = false;
+ return lines
+ .map(line => {
+ if (/^\s*```/.test(line)) {
+ inFence = !inFence;
+ return line;
+ }
+ if (inFence) {
+ return line;
+ }
+ const match = /^@(reactDocs|reactExample|interface|css)\s+(.+)$/.exec(line);
+ if (match == null) {
+ return line;
+ }
+ const [, tag, value] = match;
+ const labels: Record = {
+ css: "CSS reference",
+ interface: "TypeScript interface",
+ reactDocs: "Interactive widget",
+ reactExample: "Interactive example",
+ };
+ return ``;
+ })
+ .join("\n");
+}
diff --git a/packages/docs-data/markdownExport.test.ts b/packages/docs-data/markdownExport.test.ts
new file mode 100644
index 00000000000..f938cd8f8af
--- /dev/null
+++ b/packages/docs-data/markdownExport.test.ts
@@ -0,0 +1,40 @@
+/* !
+ * (c) Copyright 2026 Palantir Technologies Inc. All rights reserved.
+ */
+
+import { describe, expect, it } from "@blueprintjs/test-commons/vitest";
+
+import { stripDocumentalistTags } from "./markdownExport.mts";
+
+describe("stripDocumentalistTags", () => {
+ it("replaces top-level @reactDocs / @reactExample / @interface / @css with placeholder comments", () => {
+ const input = [
+ "# Title",
+ "",
+ "@reactDocs Welcome",
+ "@reactExample ButtonExample",
+ "@interface ButtonProps",
+ "@css .bp6-button",
+ ].join("\n");
+ expect(stripDocumentalistTags(input)).toBe(
+ [
+ "# Title",
+ "",
+ "",
+ "",
+ "",
+ "",
+ ].join("\n"),
+ );
+ });
+
+ it("does not touch lines inside fenced code blocks", () => {
+ const input = ["```scss", '@use "@blueprintjs/core/lib/scss/variables";', "@import 'foo';", "```"].join("\n");
+ expect(stripDocumentalistTags(input)).toBe(input);
+ });
+
+ it("leaves unknown @-prefixed lines alone", () => {
+ const input = "@unknownTag value";
+ expect(stripDocumentalistTags(input)).toBe(input);
+ });
+});
diff --git a/packages/docs-data/navTypes.mts b/packages/docs-data/navTypes.mts
index a60303804c8..de4e39b51de 100644
--- a/packages/docs-data/navTypes.mts
+++ b/packages/docs-data/navTypes.mts
@@ -81,6 +81,8 @@ export interface DocPage {
title: string;
route: string;
contents: DocContentItem[];
+ /** Cleaned source markdown of the page, suitable for "Copy as markdown". */
+ sourceMarkdown?: string;
}
/** Fields common to all nav tree nodes. */
diff --git a/packages/docs-theme/src/components/copyPageMarkdownButton.tsx b/packages/docs-theme/src/components/copyPageMarkdownButton.tsx
new file mode 100644
index 00000000000..945c8de72cd
--- /dev/null
+++ b/packages/docs-theme/src/components/copyPageMarkdownButton.tsx
@@ -0,0 +1,51 @@
+/* !
+ * (c) Copyright 2026 Palantir Technologies Inc. All rights reserved.
+ */
+
+import { useCallback, useEffect, useRef, useState } from "react";
+
+import { AnchorButton, Tooltip } from "@blueprintjs/core";
+
+export interface CopyPageMarkdownButtonProps {
+ /** Markdown source of the current page. The button is hidden if undefined. */
+ sourceMarkdown?: string;
+}
+
+/**
+ * Action button for copying the current docs page as markdown to the clipboard.
+ * Useful for handing the page to an LLM, IDE, or codegen tool.
+ */
+export const CopyPageMarkdownButton: React.FC = ({ sourceMarkdown }) => {
+ const [copied, setCopied] = useState(false);
+ const timeoutRef = useRef>();
+
+ useEffect(() => {
+ return () => clearTimeout(timeoutRef.current);
+ }, []);
+
+ const handleClick = useCallback(() => {
+ if (sourceMarkdown == null) {
+ return;
+ }
+ void navigator.clipboard.writeText(sourceMarkdown);
+ setCopied(true);
+ clearTimeout(timeoutRef.current);
+ timeoutRef.current = setTimeout(() => setCopied(false), 1500);
+ }, [sourceMarkdown]);
+
+ if (sourceMarkdown == null) {
+ return null;
+ }
+
+ return (
+
+
+
+ );
+};
diff --git a/packages/docs-theme/src/index.ts b/packages/docs-theme/src/index.ts
index 2b2af5a7c01..78e4109cbc2 100644
--- a/packages/docs-theme/src/index.ts
+++ b/packages/docs-theme/src/index.ts
@@ -18,6 +18,7 @@ export * from "./components/banner";
export * from "./components/documentation";
export * from "./components/example";
export * from "./components/codeExample";
+export * from "./components/copyPageMarkdownButton";
export * from "./components/navMenuItem";
export * from "./components/navButton";
export * from "./common";