Breadcrumb
Free · MITHeadless, accessible, and styled entirely through the attribute surface below — bring your own CSS system, or start from the reference theme.
Install
Without a flag you get no styling at all — plain HTML carrying the attribute surface below. --theme copies the same theme in two dialects;--bridge lumos re-points either one onto Lumos for Astro's tokens (it needs a theme to re-point, and Lumos itself, already in your project).
Unstyled — behavior only, you write the CSS
npx astrocraft-ui add navigation/breadcrumbPlain-CSS theme — no build step
npx astrocraft-ui add navigation/breadcrumb --theme cssTailwind theme — needs Tailwind v4
npx astrocraft-ui add navigation/breadcrumb --theme tailwindPlain-CSS theme, re-pointed onto Lumos tokens
npx astrocraft-ui add navigation/breadcrumb --theme css --bridge lumosTailwind theme, re-pointed onto Lumos tokens
npx astrocraft-ui add navigation/breadcrumb --theme tailwind --bridge lumosLive demo
Breadcrumb
BreadcrumbEllipsis
The collapsed middle of a long trail, built on Dropdown — so _popover places it, roves it with the arrow keys and light-dismisses it, and none of that is written twice. The "…" is aria-hidden; the button's real name is announced instead.
- Home
- Disclosure
- Tree
Attribute surface
With no class-merging machinery, these attributes are the whole extension point. State is a data-attribute or a native attribute; visibility is the hiddenattribute. Generated from source by pnpm slots.
| Component | Slots | Variants | Runtime state | Native state |
|---|---|---|---|---|
| `Breadcrumb.astro` | `breadcrumb` | — | — | — |
| `BreadcrumbEllipsis.astro` | `breadcrumb-ellipsis` `breadcrumb-ellipsis-text` `breadcrumb-ellipsis-trigger` | `data-align`: `start` · `end` | — | `[aria-expanded]` `[hidden]` |
| `BreadcrumbItem.astro` | `breadcrumb-item` | — | — | — |
| `BreadcrumbLink.astro` | `breadcrumb-link` | — | — | — |
| `BreadcrumbPage.astro` | `breadcrumb-page` | — | — | `[aria-current]` `[aria-disabled]` |
| `BreadcrumbSeparator.astro` | `breadcrumb-separator` | — | — | — |
Source
What the command copies — 7 files, alias-free so the copy lands standing alone. This is the whole component; there is nothing else.
---
// src/components/ui/navigation/breadcrumb/Breadcrumb.astro — headless primitive (see ../../README.md).
// Static breadcrumb trail (<nav> + <ol>). Compose BreadcrumbItem / BreadcrumbLink / BreadcrumbPage /
// BreadcrumbSeparator.
import type { HTMLAttributes } from "astro/types";
type Props = HTMLAttributes<"nav">;
const { class: className, ...rest } = Astro.props;
---
<nav aria-label="Breadcrumb" data-slot="breadcrumb" {...rest}>
<ol class={className}>
<slot />
</ol>
</nav>
---
// src/components/ui/navigation/breadcrumb/BreadcrumbEllipsis.astro — Breadcrumb compound part (see ../../README.md).
// The collapsed middle of a long trail. PaginationEllipsis is the static version of this glyph —
// a gap you cannot open. A breadcrumb's gap is different: the crumbs it hides are real ancestors a
// user may well want, so this one is a button that opens them in a menu.
//
// It is built ON Dropdown rather than beside it: the panel IS a DropdownMenu, so `_popover.ts` places
// it under the trigger, reflows it on scroll, roves its items with the arrow keys and syncs
// `aria-expanded` — none of which is reimplemented here. Fill the slot with DropdownItems.
//
// <BreadcrumbEllipsis for="crumbs">
// <DropdownItem href="/docs">Docs</DropdownItem>
// </BreadcrumbEllipsis>
//
// The trigger's own name is a visually-hidden string, not the "…" glyph, which is `aria-hidden`:
// an ellipsis reads as "horizontal ellipsis" or as nothing at all depending on the screen reader,
// and neither tells you the button opens the crumbs it swallowed. Same mechanism, same reason, as
// PaginationEllipsis.
import "../../../../styles/structure.css";
import type { HTMLAttributes } from "astro/types";
import DropdownMenu from "../../overlays/dropdown/DropdownMenu.astro";
type Props = HTMLAttributes<"li"> & {
/** The menu's `id`. Must be unique on the page — the trigger points at it. */
for: string;
align?: "start" | "end";
};
const { for: target, align = "start", class: className, ...rest } = Astro.props;
---
<li class={className} data-slot="breadcrumb-ellipsis" {...rest}>
<button
type="button"
popovertarget={target}
aria-haspopup="menu"
aria-expanded="false"
data-slot="breadcrumb-ellipsis-trigger"
>
<span aria-hidden="true">…</span>
<span data-slot="breadcrumb-ellipsis-text">Show hidden breadcrumbs</span>
</button>
<DropdownMenu id={target} align={align}>
<slot />
</DropdownMenu>
</li>
<script>
import "../../_popover";
</script>
---
// src/components/ui/navigation/breadcrumb/BreadcrumbItem.astro — Breadcrumb compound part (see ../../README.md).
import type { HTMLAttributes } from "astro/types";
type Props = HTMLAttributes<"li">;
const { class: className, ...rest } = Astro.props;
---
<li class={className} data-slot="breadcrumb-item" {...rest}>
<slot />
</li>
---
// src/components/ui/navigation/breadcrumb/BreadcrumbLink.astro — Breadcrumb compound part (see ../../README.md).
import type { HTMLAttributes } from "astro/types";
type Props = HTMLAttributes<"a"> & { href?: string };
const { href, class: className, ...rest } = Astro.props;
---
<a href={href} class={className} data-slot="breadcrumb-link" {...rest}>
<slot />
</a>
---
// src/components/ui/navigation/breadcrumb/BreadcrumbPage.astro — Breadcrumb compound part (see ../../README.md).
// The current page: not a link; announces itself with aria-current="page".
import type { HTMLAttributes } from "astro/types";
type Props = HTMLAttributes<"span">;
const { class: className, ...rest } = Astro.props;
---
<span
role="link"
aria-current="page"
aria-disabled="true"
class={className}
data-slot="breadcrumb-page"
{...rest}
>
<slot />
</span>
---
// src/components/ui/navigation/breadcrumb/BreadcrumbSeparator.astro — Breadcrumb compound part (see ../../README.md).
// Decorative divider between items (aria-hidden); default chevron, override via the slot.
import type { HTMLAttributes } from "astro/types";
type Props = HTMLAttributes<"li">;
const { class: className, ...rest } = Astro.props;
---
<li
role="presentation"
aria-hidden="true"
class={className}
data-slot="breadcrumb-separator"
{...rest}
>
<slot>
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
aria-hidden="true"
>
<path d="m9 18 6-6-6-6"></path>
</svg>
</slot>
</li>
import Breadcrumb from "./Breadcrumb.astro";
import BreadcrumbEllipsis from "./BreadcrumbEllipsis.astro";
import BreadcrumbItem from "./BreadcrumbItem.astro";
import BreadcrumbLink from "./BreadcrumbLink.astro";
import BreadcrumbPage from "./BreadcrumbPage.astro";
import BreadcrumbSeparator from "./BreadcrumbSeparator.astro";
export {
Breadcrumb,
BreadcrumbEllipsis,
BreadcrumbItem,
BreadcrumbLink,
BreadcrumbPage,
BreadcrumbSeparator,
};
export default Breadcrumb;
What you get
The component source, copied into your project by npx astrocraft-ui add navigation/breadcrumb — no package dependency, no CSS to fight. Whatever the paid blocks compose, this is it.