From 57dde08672b1f95405b2f666f2804e64c00755ae Mon Sep 17 00:00:00 2001 From: Danny White <3104761+dnywh@users.noreply.github.com> Date: Tue, 3 Mar 2026 09:40:35 +1100 Subject: [PATCH] chore(design-system): better breadcrumbs (#42310) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## What kind of change does this PR introduce? Minor update ## What is the current behavior? Our design system’s breadcrumbs: 1. Are not interactive 2. ~~Repeat the page title, which is directly below~~ ## What is the new behavior? These breadcrumbs: - Are hyperlinked - Show all parents, if nested - ~~Don’t repeat the page title~~ | Before | After | | --- | --- | | Forms-FBDF180F-F101-407C-A8EB-A06BBDB3509A | Forms-95348190-C8B2-4A3A-AE55-A4A5A16CD4B4 | ## Additional context I wonder if we should either drop the `/docs/` subdirectory or rename it. The latter would make sense if we split, say, `/product/` from `/icons/` and/or `/brand`. [Primer](https://primer.style/octicons/design-guidelines/) does this well. --- .../app/(app)/docs/[[...slug]]/page.tsx | 37 ++++++++++++++++--- apps/design-system/components/pager.tsx | 24 ++++++++++++ 2 files changed, 55 insertions(+), 6 deletions(-) diff --git a/apps/design-system/app/(app)/docs/[[...slug]]/page.tsx b/apps/design-system/app/(app)/docs/[[...slug]]/page.tsx index f4e4c6694d9..f3cdccc24d5 100644 --- a/apps/design-system/app/(app)/docs/[[...slug]]/page.tsx +++ b/apps/design-system/app/(app)/docs/[[...slug]]/page.tsx @@ -1,5 +1,5 @@ import { Mdx } from '@/components/mdx-components' -import { DocsPager } from '@/components/pager' +import { DocsPager, getBreadcrumbSegments } from '@/components/pager' import { SourcePanel } from '@/components/source-panel' import { DashboardTableOfContents } from '@/components/toc' import { siteConfig } from '@/config/site' @@ -12,6 +12,7 @@ import '@/styles/mdx.css' import { allDocs } from 'contentlayer/generated' import { ChevronRight } from 'lucide-react' import type { Metadata } from 'next' +import Link from 'next/link' import { notFound } from 'next/navigation' import Balancer from 'react-wrap-balancer' import { ScrollArea, Separator } from 'ui' @@ -83,15 +84,39 @@ export default async function DocPage(props: DocPageProps) { } const toc = await getTableOfContents(doc.body.raw) + const breadcrumbSegments = getBreadcrumbSegments(doc) return (
-
-
Docs
- -
{doc.title}
-
+

{doc.title}

{doc.description && ( diff --git a/apps/design-system/components/pager.tsx b/apps/design-system/components/pager.tsx index b5c6eb39a0d..b7b1620b8ea 100644 --- a/apps/design-system/components/pager.tsx +++ b/apps/design-system/components/pager.tsx @@ -71,3 +71,27 @@ export function flatten(links: NavItemWithChildren[]): NavItem[] { }, []) .filter((link) => !link?.disabled) } + +export interface BreadcrumbSegment { + title: string + href?: string +} + +export function getBreadcrumbSegments(doc: Doc): BreadcrumbSegment[] { + const segments: BreadcrumbSegment[] = [{ title: 'Docs', href: '/docs' }] + + for (const section of docsConfig.sidebarNav) { + const flatItems = flatten(section.items ?? []) + const inSection = flatItems.some((item) => item.href === doc.slug) + if (!inSection || !section.title) continue + + const sectionLanding = flatItems.find((item) => item.priority) ?? flatItems[0] + const isOnLanding = sectionLanding?.href === doc.slug + if (sectionLanding?.href && !isOnLanding) { + segments.push({ title: section.title, href: sectionLanding.href }) + } + break + } + + return segments +}