diff --git a/Sources/DocCHTML/CMakeLists.txt b/Sources/DocCHTML/CMakeLists.txt
index 590c395ef..4bf8da392 100644
--- a/Sources/DocCHTML/CMakeLists.txt
+++ b/Sources/DocCHTML/CMakeLists.txt
@@ -10,6 +10,7 @@ See https://swift.org/LICENSE.txt for license information
add_library(DocCHTML STATIC
LinkProvider.swift
MarkdownRenderer+Availability.swift
+ MarkdownRenderer+Breadcrumbs.swift
MarkdownRenderer.swift
WordBreak.swift
XMLNode+element.swift)
diff --git a/Sources/DocCHTML/MarkdownRenderer+Breadcrumbs.swift b/Sources/DocCHTML/MarkdownRenderer+Breadcrumbs.swift
new file mode 100644
index 000000000..ce374e2db
--- /dev/null
+++ b/Sources/DocCHTML/MarkdownRenderer+Breadcrumbs.swift
@@ -0,0 +1,67 @@
+/*
+ This source file is part of the Swift.org open source project
+
+ Copyright (c) 2025 Apple Inc. and the Swift project authors
+ Licensed under Apache License v2.0 with Runtime Library Exception
+
+ See https://swift.org/LICENSE.txt for license information
+ See https://swift.org/CONTRIBUTORS.txt for Swift project authors
+*/
+
+#if canImport(FoundationXML)
+// TODO: Consider other HTML rendering options as a future improvement (rdar://165755530)
+package import FoundationXML
+package import FoundationEssentials
+#else
+package import Foundation
+#endif
+
+package extension MarkdownRenderer {
+ /// Creates an HTML element for the breadcrumbs that lead to the renderer's current page.
+ func breadcrumbs(references: [URL], currentPageNames: LinkedElement.Names) -> XMLNode {
+ // Breadcrumbs handle symbols differently than most elements in that everything uses a default style (no "code voice")
+ func nameElements(for names: LinkedElement.Names) -> [XMLNode] {
+ switch names {
+ case .single(.conceptual(let name)), .single(.symbol(let name)):
+ return [.text(name)]
+
+ case .languageSpecificSymbol(let namesByLanguageID):
+ let names = RenderHelpers.sortedLanguageSpecificValues(namesByLanguageID)
+ return switch goal {
+ case .richness:
+ if names.count == 1 {
+ [.text(names.first!.value)]
+ } else {
+ names.map { language, name in
+ // Wrap the name in a span so that it can be given a language specific "class" attribute.
+ .element(named: "span", children: [.text(name)], attributes: ["class": "\(language.id)-only"])
+ }
+ }
+ case .conciseness:
+ // If the goal is conciseness, only display the primary language's name
+ names.first.map { _, name in [.text(name)] } ?? []
+ }
+ }
+ }
+
+ // Create links for each of the breadcrumbs
+ var items: [XMLNode] = references.compactMap {
+ linkProvider.element(for: $0).map { page in
+ .element(named: "li", children: [
+ .element(named: "a", children: nameElements(for: page.names), attributes: ["href": self.path(to: page.path)])
+ ])
+ }
+ }
+
+ // Add the name of the current page. It doesn't display as a link because it would refer to the current page.
+ items.append(
+ .element(named: "li", children: nameElements(for: currentPageNames))
+ )
+ let list = XMLNode.element(named: "ul", children: items)
+
+ return switch goal {
+ case .conciseness: list // If the goal is conciseness, don't wrap the list in a `