diff --git a/src/components/docPage/index.tsx b/src/components/docPage/index.tsx
index 51e1c7fb995da..3c94ac73a783a 100644
--- a/src/components/docPage/index.tsx
+++ b/src/components/docPage/index.tsx
@@ -16,11 +16,11 @@ import {CopyMarkdownButton} from '../copyMarkdownButton';
import {DocFeedback} from '../docFeedback';
import {GitHubCTA} from '../githubCTA';
import {Header} from '../header';
+import {InlineTableOfContents} from '../inlineTableOfContents';
import Mermaid from '../mermaid';
import {PaginationNav} from '../paginationNav';
import {PlatformSdkDetail} from '../platformSdkDetail';
import {Sidebar} from '../sidebar';
-import {SidebarTableOfContents} from '../sidebarTableOfContents';
import {ReaderDepthTracker} from '../track-reader-depth';
type Props = {
@@ -96,6 +96,7 @@ export function DocPage({
{children}
@@ -122,7 +123,6 @@ export function DocPage({
className="sticky h-[calc(100vh-var(--header-height))] top-[var(--header-height)] overflow-y-auto hidden toc:block flex-none w-[250px] min-w-[250px]"
>
diff --git a/src/components/inlineTableOfContents/index.tsx b/src/components/inlineTableOfContents/index.tsx
new file mode 100644
index 0000000000000..4c6be2d1765c9
--- /dev/null
+++ b/src/components/inlineTableOfContents/index.tsx
@@ -0,0 +1,105 @@
+'use client';
+
+import {useEffect, useState} from 'react';
+
+import {isNotNil} from 'sentry-docs/utils';
+
+import styles from './style.module.scss';
+
+interface TocItem {
+ title: string;
+ url: string;
+}
+
+function getMainElement() {
+ if (typeof document === 'undefined') {
+ return null;
+ }
+ return document.getElementById('main');
+}
+
+function getTocItems(main: HTMLElement): TocItem[] {
+ return Array.from(main.querySelectorAll('h2'))
+ .map(el => {
+ const title = el.textContent?.trim();
+ if (!el.id || !title) {
+ return null;
+ }
+ // This is a relatively new API, that checks if the element is visible in the document
+ // With this, we filter out e.g. sections hidden via CSS
+ if (typeof el.checkVisibility === 'function' && !el.checkVisibility()) {
+ return null;
+ }
+ return {
+ url: `#${el.id}`,
+ title,
+ };
+ })
+ .filter(isNotNil);
+}
+
+// Inline table of contents that appears at the top of the page content
+// Shows only H2 headings, and only appears if there are 3 or more sections
+export function InlineTableOfContents() {
+ const [tocItems, setTocItems] = useState
([]);
+
+ // gather the toc items on mount
+ useEffect(() => {
+ const main = getMainElement();
+ if (!main) {
+ return;
+ }
+
+ const items = getTocItems(main);
+ setTocItems(items);
+ }, []);
+
+ // ensure toc items are kept up-to-date if the DOM changes
+ useEffect(() => {
+ const main = getMainElement();
+ if (!main) {
+ return () => {};
+ }
+
+ const observer = new MutationObserver(() => {
+ const newTocItems = getTocItems(main);
+
+ // Avoid flashing if nothing changes
+ if (
+ newTocItems.length === tocItems.length &&
+ newTocItems.every((item, index) => item.url === tocItems[index].url)
+ ) {
+ return;
+ }
+ setTocItems(newTocItems);
+ });
+
+ // Start observing the target node for any changes in its subtree
+ observer.observe(main, {
+ childList: true,
+ subtree: true,
+ attributes: true,
+ attributeFilter: ['class', 'id', 'style'],
+ });
+
+ return () => observer.disconnect();
+ }, [tocItems]);
+
+ // Only show if there are 3 or more sections
+ if (tocItems.length < 3) {
+ return null;
+ }
+
+ return (
+
+ );
+}
diff --git a/src/components/inlineTableOfContents/style.module.scss b/src/components/inlineTableOfContents/style.module.scss
new file mode 100644
index 0000000000000..57a83d0d6eccb
--- /dev/null
+++ b/src/components/inlineTableOfContents/style.module.scss
@@ -0,0 +1,62 @@
+.inline-toc {
+ --title-color: var(--gray-11);
+ --link-color: var(--gray-11);
+ --link-hover-color: var(--accent-purple);
+
+ border-left: 2px solid var(--gray-6);
+ padding-left: 1rem;
+ padding-right: 1rem;
+ margin: 1.5rem 0;
+ max-width: 100%;
+ width: 100%;
+}
+
+:global(.dark) {
+ .inline-toc {
+ --title-color: var(--gray-11);
+ --link-color: var(--gray-11);
+ }
+}
+
+.inline-toc-title {
+ font-weight: 600;
+ font-size: 0.925rem;
+ text-transform: uppercase;
+ letter-spacing: 0.5px;
+ color: var(--title-color);
+ margin-bottom: 0.5rem;
+ white-space: normal;
+ word-wrap: break-word;
+ overflow-wrap: break-word;
+}
+
+.inline-toc-list {
+ list-style-type: none;
+ padding-left: 0;
+ margin: 0;
+ max-width: 100%;
+ width: 100%;
+
+ li {
+ margin: 0;
+ max-width: 100%;
+ }
+
+ a {
+ color: var(--link-color);
+ text-decoration: none;
+ font-size: 0.875rem;
+ line-height: 1.8;
+ display: block;
+ transition: color 0.2s ease;
+ white-space: normal;
+ word-wrap: break-word;
+ overflow-wrap: break-word;
+ max-width: 100%;
+
+ &:hover {
+ color: var(--link-hover-color);
+ }
+ }
+}
+