From 16e74e2c71e35df377368a4f01c426d78ca7c936 Mon Sep 17 00:00:00 2001 From: Daniel Esteves Date: Fri, 6 Nov 2026 23:31:49 -0400 Subject: [PATCH 1/2] feat(web): add Breadcrumb --- .../web-demos/breadcrumb/collapsed.tsx | 28 ++ .../web-demos/breadcrumb/custom-separator.tsx | 21 + .../web-demos/breadcrumb/long-labels.tsx | 27 ++ .../web-demos/breadcrumb/overview.tsx | 21 + .../components/web-demos/breadcrumb/thumb.tsx | 18 + .../web-demos/breadcrumb/with-menu.tsx | 51 ++ .../components/web-demos/index.generated.tsx | 6 + .../docs/web/components/breadcrumb.mdx | 382 +++++++++++++++ .../content/docs/web/components/meta.json | 1 + apps/docs/public/r/web/breadcrumb.json | 26 + apps/docs/public/r/web/registry.json | 23 + .../public/skills/panelui-web/components.md | 3 +- .../panelui-web/components/breadcrumb.md | 159 +++++++ apps/docs/scripts/web-meta.json | 9 + apps/docs/scripts/web-usage/breadcrumb.json | 45 ++ .../web/src/components/breadcrumb/collapse.ts | 59 +++ .../web/src/components/breadcrumb/index.tsx | 445 ++++++++++++++++++ packages/web/src/index.ts | 17 + .../web/test/breadcrumb-collapse.test.mjs | 69 +++ skills/panelui-web/components.md | 3 +- skills/panelui-web/components/breadcrumb.md | 159 +++++++ 21 files changed, 1568 insertions(+), 4 deletions(-) create mode 100644 apps/docs/components/web-demos/breadcrumb/collapsed.tsx create mode 100644 apps/docs/components/web-demos/breadcrumb/custom-separator.tsx create mode 100644 apps/docs/components/web-demos/breadcrumb/long-labels.tsx create mode 100644 apps/docs/components/web-demos/breadcrumb/overview.tsx create mode 100644 apps/docs/components/web-demos/breadcrumb/thumb.tsx create mode 100644 apps/docs/components/web-demos/breadcrumb/with-menu.tsx create mode 100644 apps/docs/content/docs/web/components/breadcrumb.mdx create mode 100644 apps/docs/public/r/web/breadcrumb.json create mode 100644 apps/docs/public/skills/panelui-web/components/breadcrumb.md create mode 100644 apps/docs/scripts/web-usage/breadcrumb.json create mode 100644 packages/web/src/components/breadcrumb/collapse.ts create mode 100644 packages/web/src/components/breadcrumb/index.tsx create mode 100644 packages/web/test/breadcrumb-collapse.test.mjs create mode 100644 skills/panelui-web/components/breadcrumb.md diff --git a/apps/docs/components/web-demos/breadcrumb/collapsed.tsx b/apps/docs/components/web-demos/breadcrumb/collapsed.tsx new file mode 100644 index 00000000..4244335f --- /dev/null +++ b/apps/docs/components/web-demos/breadcrumb/collapsed.tsx @@ -0,0 +1,28 @@ +'use client'; + +import { Breadcrumb } from 'panelui-web/breadcrumb'; + +const CRUMBS = [ + { label: 'Home', href: '/' }, + { label: 'Library', href: '/library' }, + { label: 'Components', href: '/library/components' }, + { label: 'Navigation', href: '/library/components/navigation' }, + { label: 'Guides', href: '/library/components/navigation/guides' }, +]; + +export default function BreadcrumbCollapsed() { + return ( + + + {CRUMBS.map((crumb) => ( + + {crumb.label} + + ))} + + Breadcrumb + + + + ); +} diff --git a/apps/docs/components/web-demos/breadcrumb/custom-separator.tsx b/apps/docs/components/web-demos/breadcrumb/custom-separator.tsx new file mode 100644 index 00000000..d2602785 --- /dev/null +++ b/apps/docs/components/web-demos/breadcrumb/custom-separator.tsx @@ -0,0 +1,21 @@ +'use client'; + +import { Breadcrumb } from 'panelui-web/breadcrumb'; + +export default function BreadcrumbCustomSeparator() { + return ( + + + + Docs + + + Guides + + + Theming + + + + ); +} diff --git a/apps/docs/components/web-demos/breadcrumb/long-labels.tsx b/apps/docs/components/web-demos/breadcrumb/long-labels.tsx new file mode 100644 index 00000000..608c25d9 --- /dev/null +++ b/apps/docs/components/web-demos/breadcrumb/long-labels.tsx @@ -0,0 +1,27 @@ +'use client'; + +import { Breadcrumb } from 'panelui-web/breadcrumb'; + +export default function BreadcrumbLongLabels() { + return ( +
+ + + + Home + + + + The autumn catalogue of new and returning titles + + + + + Field notes from the fourth proof, with every correction still open + + + + +
+ ); +} diff --git a/apps/docs/components/web-demos/breadcrumb/overview.tsx b/apps/docs/components/web-demos/breadcrumb/overview.tsx new file mode 100644 index 00000000..7a4a09ea --- /dev/null +++ b/apps/docs/components/web-demos/breadcrumb/overview.tsx @@ -0,0 +1,21 @@ +'use client'; + +import { Breadcrumb } from 'panelui-web/breadcrumb'; + +export default function BreadcrumbOverview() { + return ( + + + + Home + + + Projects + + + Autumn catalogue + + + + ); +} diff --git a/apps/docs/components/web-demos/breadcrumb/thumb.tsx b/apps/docs/components/web-demos/breadcrumb/thumb.tsx new file mode 100644 index 00000000..044717bc --- /dev/null +++ b/apps/docs/components/web-demos/breadcrumb/thumb.tsx @@ -0,0 +1,18 @@ +'use client'; + +import { Breadcrumb } from 'panelui-web/breadcrumb'; + +export default function BreadcrumbThumb() { + return ( + + + + Home + + + Breadcrumb + + + + ); +} diff --git a/apps/docs/components/web-demos/breadcrumb/with-menu.tsx b/apps/docs/components/web-demos/breadcrumb/with-menu.tsx new file mode 100644 index 00000000..c103576d --- /dev/null +++ b/apps/docs/components/web-demos/breadcrumb/with-menu.tsx @@ -0,0 +1,51 @@ +'use client'; + +import { useState } from 'react'; +import { Breadcrumb } from 'panelui-web/breadcrumb'; +import { Menu } from 'panelui-web/menu'; + +const CRUMBS = [ + { label: 'Home', href: '/' }, + { label: 'Library', href: '/library' }, + { label: 'Components', href: '/library/components' }, + { label: 'Navigation', href: '/library/components/navigation' }, + { label: 'Guides', href: '/library/components/navigation/guides' }, + { label: 'Breadcrumb', href: '/library/components/navigation/breadcrumb' }, +]; + +export default function BreadcrumbWithMenu() { + const [open, setOpen] = useState(false); + const [anchor, setAnchor] = useState(null); + const hidden = CRUMBS.slice(1, -1); + + return ( + { + setAnchor(event.currentTarget); + setOpen(true); + }} + > + + {CRUMBS.map((crumb, index) => ( + + {index === CRUMBS.length - 1 ? ( + {crumb.label} + ) : ( + {crumb.label} + )} + + ))} + + + + {hidden.map((crumb) => ( + }> + {crumb.label} + + ))} + + + + ); +} diff --git a/apps/docs/components/web-demos/index.generated.tsx b/apps/docs/components/web-demos/index.generated.tsx index 1a90d2e6..cf561606 100644 --- a/apps/docs/components/web-demos/index.generated.tsx +++ b/apps/docs/components/web-demos/index.generated.tsx @@ -51,6 +51,12 @@ export const WEB_DEMOS: Record = { 'bar-picker/coverage': dynamic(() => import('./bar-picker/coverage')), 'bar-picker/deploys': dynamic(() => import('./bar-picker/deploys')), 'bar-picker/thumb': dynamic(() => import('./bar-picker/thumb')), + 'breadcrumb/collapsed': dynamic(() => import('./breadcrumb/collapsed')), + 'breadcrumb/custom-separator': dynamic(() => import('./breadcrumb/custom-separator')), + 'breadcrumb/long-labels': dynamic(() => import('./breadcrumb/long-labels')), + 'breadcrumb/overview': dynamic(() => import('./breadcrumb/overview')), + 'breadcrumb/thumb': dynamic(() => import('./breadcrumb/thumb')), + 'breadcrumb/with-menu': dynamic(() => import('./breadcrumb/with-menu')), 'bubble-chart/changing-data': dynamic(() => import('./bubble-chart/changing-data')), 'bubble-chart/colors': dynamic(() => import('./bubble-chart/colors')), 'bubble-chart/loading': dynamic(() => import('./bubble-chart/loading')), diff --git a/apps/docs/content/docs/web/components/breadcrumb.mdx b/apps/docs/content/docs/web/components/breadcrumb.mdx new file mode 100644 index 00000000..84277092 --- /dev/null +++ b/apps/docs/content/docs/web/components/breadcrumb.mdx @@ -0,0 +1,382 @@ +--- +title: Breadcrumb +description: The trail of links back up a hierarchy to the page you are on, with the middle folded away when the path runs long. +status: new +--- + + + +```tsx lineNumbers +'use client'; + +import { Breadcrumb } from '@/components/panelui/breadcrumb'; + +export default function BreadcrumbOverview() { + return ( + + + + Home + + + Projects + + + Autumn catalogue + + + + ); +} +``` + + + +Use it to show where a page sits in a hierarchy, and to give the reader a way back up it: the project a file belongs to, the section a setting lives in, the steps that led to a record. The trail ends at the page that is open, which is a label rather than a link — you cannot navigate to where you already are. + +A breadcrumb describes a hierarchy, not a history. When the reader arrived through screens that are not nested, a back link is the honest control. When the trail would be the only way between a set of peers, use tabs or a sidebar instead. + +It earns its place on pages reached through several levels, where the title alone does not say where you are. A top-level page has nothing above it to name, so it usually goes without one. + +## Installation + + + + + + + +The source is copied into `components/panelui/`, where you can edit it. + + + + + + + +Import from `panelui-web/breadcrumb` instead of `@/components/panelui/breadcrumb`. Tailwind needs one line to read the package's classes, which is on the [installation page](/docs/web/installation#from-npm). + + + + + + + + + +Install the packages it uses: + + + + + + + +Copy the source into `components/panelui/breadcrumb/`, one file for each below: + + + + + + + + + + + +## Usage + +```tsx +import { Breadcrumb } from "@/components/panelui/breadcrumb" +``` + +```tsx + + + + Home + + + Projects + + + Autumn catalogue + + + +``` + +## Composition + +```tsx + + + + … + + + … + + + +``` + +- **`Breadcrumb.List`** — The crumb row. It owns the separators — one between every pair of crumbs, none at the ends — and the folding, so you only ever list items. +- **`Breadcrumb.Item`** — One crumb, as a list item. Wrap a `Link` or the `Page` in it; a trail is a list of these. +- **`Breadcrumb.Link`** — A navigable crumb: an ancestor to go back to. It renders an ``, so `href` and the rest work as they do anywhere, and `render` swaps in your router's own link. A string label truncates to the space available and carries the full text as a `title`. +- **`Breadcrumb.Page`** — The trailing crumb, where you are now. Not a link — it carries `aria-current="page"` and the full foreground, so the trail resolves to it. +- **`Breadcrumb.Separator`** — The glyph between crumbs, hidden from screen readers. `List` inserts it for you; it is public only for a hand-assembled trail, where it sits beside the items. +- **`Breadcrumb.Ellipsis`** — Stands in for the crumbs a folded trail hides. Static by default; give the root an `onEllipsisClick` and it becomes a button for a menu of the hidden steps. + +## Examples + +### Folded middle + +`maxItems` folds a deep trail into its first crumb, an ellipsis and its last. The ceiling is the point at which folding starts, not a hard limit: the ends always survive, and a fold that would hide only one crumb is skipped because it saves a slot without saving anything useful. + + + +```tsx lineNumbers +'use client'; + +import { Breadcrumb } from '@/components/panelui/breadcrumb'; + +const CRUMBS = [ + { label: 'Home', href: '/' }, + { label: 'Library', href: '/library' }, + { label: 'Components', href: '/library/components' }, + { label: 'Navigation', href: '/library/components/navigation' }, + { label: 'Guides', href: '/library/components/navigation/guides' }, +]; + +export default function BreadcrumbCollapsed() { + return ( + + + {CRUMBS.map((crumb) => ( + + {crumb.label} + + ))} + + Breadcrumb + + + + ); +} +``` + + + +### A separator of your own + +`separator` on the root changes the glyph between every pair of crumbs at once — a slash, a dot, a word. The list still owns where it goes, so there is never one left hanging after the current page. + + + +```tsx lineNumbers +'use client'; + +import { Breadcrumb } from '@/components/panelui/breadcrumb'; + +export default function BreadcrumbCustomSeparator() { + return ( + + + + Docs + + + Guides + + + Theming + + + + ); +} +``` + + + +### A menu of the hidden steps + +Pass `onEllipsisClick` and the ellipsis becomes a real button. Here it opens a Menu positioned at the button, with the steps the folded trail left out, so a short trail still reaches everything above it. + + + +```tsx lineNumbers +'use client'; + +import { useState } from 'react'; +import { Breadcrumb } from '@/components/panelui/breadcrumb'; +import { Menu } from '@/components/panelui/menu'; + +const CRUMBS = [ + { label: 'Home', href: '/' }, + { label: 'Library', href: '/library' }, + { label: 'Components', href: '/library/components' }, + { label: 'Navigation', href: '/library/components/navigation' }, + { label: 'Guides', href: '/library/components/navigation/guides' }, + { label: 'Breadcrumb', href: '/library/components/navigation/breadcrumb' }, +]; + +export default function BreadcrumbWithMenu() { + const [open, setOpen] = useState(false); + const [anchor, setAnchor] = useState(null); + const hidden = CRUMBS.slice(1, -1); + + return ( + { + setAnchor(event.currentTarget); + setOpen(true); + }} + > + + {CRUMBS.map((crumb, index) => ( + + {index === CRUMBS.length - 1 ? ( + {crumb.label} + ) : ( + {crumb.label} + )} + + ))} + + + + {hidden.map((crumb) => ( + }> + {crumb.label} + + ))} + + + + ); +} +``` + + + +### Long labels + +The row never wraps. A long label truncates to the space it is given instead of pushing the trail onto a second line, and a string label is given its full text as a `title`, so the cut is never silent. + + + +```tsx lineNumbers +'use client'; + +import { Breadcrumb } from '@/components/panelui/breadcrumb'; + +export default function BreadcrumbLongLabels() { + return ( +
+ + + + Home + + + + The autumn catalogue of new and returning titles + + + + + Field notes from the fourth proof, with every correction still open + + + + +
+ ); +} +``` + +
+ +## API Reference + +### Breadcrumb + +| Prop | Type | Default | Description | +| --- | --- | --- | --- | +| `className` | `string` | — | Extra classes for the landmark. | +| `size` | `BreadcrumbSize` | `'default'` | Text density for every crumb. `sm` suits a header bar. Default `default`. | +| `separator` | `ReactNode` | — | The glyph `Breadcrumb.List` places between crumbs. Defaults to a chevron; pass `'/'`, a dot, or any node to change every gap at once. | +| `maxItems` | `number` | — | Collapse the trail once it holds more than this many crumbs. The first `itemsBeforeCollapse` and last `itemsAfterCollapse` crumbs survive; the middle folds into a single ellipsis. Nothing folds when hiding the middle would not shorten the row. | +| `itemsBeforeCollapse` | `number` | `1` | How many leading crumbs survive a collapse, at least one. Default 1. | +| `itemsAfterCollapse` | `number` | `1` | How many trailing crumbs survive a collapse, at least one. Default 1. | +| `onEllipsisClick` | `MouseEventHandler` | — | Makes the collapsed ellipsis pressable — the handle for a menu listing the hidden steps. Without it the ellipsis is a static marker. | + +### Breadcrumb.List + +| Prop | Type | Default | Description | +| --- | --- | --- | --- | +| `className` | `string` | — | Extra classes for the crumb row. | + +### Breadcrumb.Item + +| Prop | Type | Default | Description | +| --- | --- | --- | --- | +| `className` | `string` | — | Extra classes for the crumb's list item. | + +### Breadcrumb.Link + +| Prop | Type | Default | Description | +| --- | --- | --- | --- | +| `className` | `string` | — | Extra classes for the anchor. | + +### Breadcrumb.Page + +| Prop | Type | Default | Description | +| --- | --- | --- | --- | +| `className` | `string` | — | Extra classes for the label. | + +### Breadcrumb.Separator + +| Prop | Type | Default | Description | +| --- | --- | --- | --- | +| `className` | `string` | — | Extra classes for the gap. | + +### Breadcrumb.Ellipsis + +| Prop | Type | Default | Description | +| --- | --- | --- | --- | +| `className` | `string` | — | Extra classes for the marker or button. | +| `onClick` | `MouseEventHandler` | — | Called when the ellipsis is pressed. Without it the ellipsis is a static marker; with it, it is a real button for a menu of the hidden steps. | +| `label` | `string` | — | Its accessible name. Default `More` when static, `Show hidden items` when pressable. | + +Every part also takes the props of the element it renders, and a `className`. + +## Notes + +### Accessibility + +The root is a `
\n \n );\n}\nBreadcrumbRoot.displayName = 'Breadcrumb';\n\nexport interface BreadcrumbListProps extends ComponentProps<'ol'> {\n /** Extra classes for the crumb row. */\n className?: string;\n}\n\n/**\n * The crumb row. It owns the separators — one between every pair of crumbs and\n * none at the ends — and the folding, so a caller only ever lists items.\n */\nfunction BreadcrumbList({ className, children, ...props }: BreadcrumbListProps) {\n const { size, maxItems, itemsBeforeCollapse, itemsAfterCollapse, onEllipsisClick } = useBreadcrumb();\n const crumbs = Children.toArray(children).filter(isValidElement);\n const slots = breadcrumbVariants({ size });\n const sequence = collapseBreadcrumbItems(crumbs.length, {\n maxItems,\n itemsBeforeCollapse,\n itemsAfterCollapse,\n });\n\n return (\n
    \n {sequence.map((slot, position) => (\n \n {position > 0 ? : null}\n {slot === BREADCRUMB_ELLIPSIS ? (\n \n ) : (\n crumbs[slot]\n )}\n \n ))}\n
\n );\n}\nBreadcrumbList.displayName = 'Breadcrumb.List';\n\nexport interface BreadcrumbItemProps extends ComponentProps<'li'> {\n /** Extra classes for the crumb's list item. */\n className?: string;\n}\n\n/** One crumb: wraps a link or the current page, and keeps its label on one line. */\nfunction BreadcrumbItem({ className, ...props }: BreadcrumbItemProps) {\n return
  • ;\n}\nBreadcrumbItem.displayName = 'Breadcrumb.Item';\n\nexport interface BreadcrumbLinkProps extends Omit, 'className'> {\n /** Extra classes for the anchor. */\n className?: string;\n}\n\n/**\n * A navigable crumb — an ancestor you can jump back to. Renders an ``, so\n * `href`, `target` and the rest work as they do anywhere; `render` draws it as\n * your router's own link instead. A string label is truncated to the space\n * available and carries its full text as a `title`.\n */\nfunction BreadcrumbLink({\n className,\n children,\n title,\n render,\n ...props\n}: BreadcrumbLinkProps & RenderProp<'a'>) {\n const { size } = useBreadcrumb();\n return useRender({\n defaultTagName: 'a',\n render,\n props: mergeProps(\n {\n 'data-slot': 'breadcrumb-link',\n className: breadcrumbVariants({ size }).link({ className }),\n title: title ?? (typeof children === 'string' ? children : undefined),\n children,\n },\n props\n ),\n });\n}\nBreadcrumbLink.displayName = 'Breadcrumb.Link';\n\nexport interface BreadcrumbPageProps extends Omit, 'className'> {\n /** Extra classes for the label. */\n className?: string;\n}\n\n/**\n * The trailing crumb: where you are now. Not a link — `aria-current=\"page\"`\n * marks it as the destination, and it is painted in the full foreground so the\n * trail resolves to it. A string label truncates and carries its full text as a\n * `title`, as `Breadcrumb.Link` does.\n */\nfunction BreadcrumbPage({ className, children, title, ...props }: BreadcrumbPageProps) {\n const { size } = useBreadcrumb();\n return (\n \n {children}\n \n );\n}\nBreadcrumbPage.displayName = 'Breadcrumb.Page';\n\nexport interface BreadcrumbSeparatorProps extends Omit, 'children'> {\n /** Extra classes for the gap. */\n className?: string;\n /** Override the glyph for this one gap. Falls back to the root's separator. */\n children?: ReactNode;\n}\n\n/**\n * The glyph between crumbs. `Breadcrumb.List` inserts it for you; it is public\n * only for the rare hand-assembled trail, where it is a sibling of the items.\n * Hidden from screen readers — the order of the crumbs already says everything.\n */\nfunction BreadcrumbSeparator({ className, children, ...props }: BreadcrumbSeparatorProps) {\n const { size, separator } = useBreadcrumb();\n const glyph = children ?? separator;\n\n return (\n \n {glyph ?? }\n
  • \n );\n}\nBreadcrumbSeparator.displayName = 'Breadcrumb.Separator';\n\nexport interface BreadcrumbEllipsisProps extends Omit, 'children' | 'onClick'> {\n /** Extra classes for the marker or button. */\n className?: string;\n /** A glyph of your own in place of the three dots. */\n children?: ReactNode;\n /**\n * Called when the ellipsis is pressed. Without it the ellipsis is a static\n * marker; with it, it is a real button for a menu of the hidden steps.\n */\n onClick?: MouseEventHandler;\n /** Its accessible name. Default `More` when static, `Show hidden items` when pressable. */\n label?: string;\n}\n\n/**\n * Stands in for the crumbs a folded trail hides. Static by default; give it an\n * `onClick` (through the root's `onEllipsisClick`) and it becomes the trigger\n * for a menu of the hidden steps. Either way it is labelled, so the fold is not\n * silent to assistive tech.\n */\nfunction BreadcrumbEllipsis({ className, children, onClick, label, ...props }: BreadcrumbEllipsisProps) {\n const { size } = useBreadcrumb();\n const slots = breadcrumbVariants({ size, interactive: Boolean(onClick) });\n const glyph = children ?? ;\n\n if (!onClick) {\n return (\n \n {glyph}\n \n );\n }\n\n return (\n `, so `href` and the rest work as they do anywhere, and `render` swaps in your router's own link. A string label truncates to the space available and carries the full text as a `title`. +- `Breadcrumb.Page` — The trailing crumb, where you are now. Not a link — it carries `aria-current="page"` and the full foreground, so the trail resolves to it. +- `Breadcrumb.Separator` — The glyph between crumbs, hidden from screen readers. `List` inserts it for you; it is public only for a hand-assembled trail, where it sits beside the items. +- `Breadcrumb.Ellipsis` — Stands in for the crumbs a folded trail hides. Static by default; give the root an `onEllipsisClick` and it becomes a button for a menu of the hidden steps. + +## Props + +Every part also takes the props of the element it renders, and a `className`. + +#### `BreadcrumbProps` + +| Prop | Type | Default | What it does | +| --- | --- | --- | --- | +| `className` | `string` | — | Extra classes for the landmark. | +| `size` | `BreadcrumbSize` | `'default'` | Text density for every crumb. `sm` suits a header bar. Default `default`. | +| `separator` | `ReactNode` | — | The glyph `Breadcrumb.List` places between crumbs. Defaults to a chevron; pass `'/'`, a dot, or any node to change every gap at once. | +| `maxItems` | `number` | — | Collapse the trail once it holds more than this many crumbs. The first `itemsBeforeCollapse` and last `itemsAfterCollapse` crumbs survive; the middle folds into a single ellipsis. Nothing folds when hiding the middle would not shorten the row. | +| `itemsBeforeCollapse` | `number` | `1` | How many leading crumbs survive a collapse, at least one. Default 1. | +| `itemsAfterCollapse` | `number` | `1` | How many trailing crumbs survive a collapse, at least one. Default 1. | +| `onEllipsisClick` | `MouseEventHandler` | — | Makes the collapsed ellipsis pressable — the handle for a menu listing the hidden steps. Without it the ellipsis is a static marker. | + +#### `BreadcrumbListProps` + +| Prop | Type | Default | What it does | +| --- | --- | --- | --- | +| `className` | `string` | — | Extra classes for the crumb row. | + +#### `BreadcrumbItemProps` + +| Prop | Type | Default | What it does | +| --- | --- | --- | --- | +| `className` | `string` | — | Extra classes for the crumb's list item. | + +#### `BreadcrumbLinkProps` + +| Prop | Type | Default | What it does | +| --- | --- | --- | --- | +| `className` | `string` | — | Extra classes for the anchor. | + +#### `BreadcrumbPageProps` + +| Prop | Type | Default | What it does | +| --- | --- | --- | --- | +| `className` | `string` | — | Extra classes for the label. | + +#### `BreadcrumbSeparatorProps` + +| Prop | Type | Default | What it does | +| --- | --- | --- | --- | +| `className` | `string` | — | Extra classes for the gap. | + +#### `BreadcrumbEllipsisProps` + +| Prop | Type | Default | What it does | +| --- | --- | --- | --- | +| `className` | `string` | — | Extra classes for the marker or button. | +| `onClick` | `MouseEventHandler` | — | Called when the ellipsis is pressed. Without it the ellipsis is a static marker; with it, it is a real button for a menu of the hidden steps. | +| `label` | `string` | — | Its accessible name. Default `More` when static, `Show hidden items` when pressable. | + +## In a Server Component + +`Breadcrumb` is a client component. A Server Component cannot read `Breadcrumb.List` off the import, so import each part by name: `BreadcrumbList`, `BreadcrumbItem`, `BreadcrumbLink`, `BreadcrumbPage`, `BreadcrumbSeparator`, `BreadcrumbEllipsis`. + +## Notes + +#### Accessibility + +The root is a `