<BreadcrumbRoot>
<BreadcrumbList>
<BreadcrumbItem>
<BreadcrumbLink href="/docs">Docs</BreadcrumbLink>
</BreadcrumbItem>
<BreadcrumbSeparator />
<BreadcrumbItem>
<BreadcrumbLink href="/docs/vue">Components</BreadcrumbLink>
</BreadcrumbItem>
<BreadcrumbSeparator />
<BreadcrumbItem>
<BreadcrumbPage>Breadcrumb</BreadcrumbPage>
</BreadcrumbItem>
</BreadcrumbList>
</BreadcrumbRoot>
npx @left4code/cosmic-ui-cli@latest add breadcrumb
This writes the component and everything it imports, then installs the packages it needs. Run npx @left4code/cosmic-ui-cli@latest initfirst if you haven't already (it detects Vue automatically).
No state machine here - a breadcrumb trail doesn't need one, it's plain semantic markup (nav > ol > li). Vue SFCs can't export multiple components from one file, so Breadcrumb is a folder instead of a single file — copy all of it into your project.
<script setup lang="ts">
// Astro forwards a slot="preview" used to route this island into a named
// slot as a genuine prop on whatever Vue renders at the top of that island.
// Without opting out of attribute inheritance, Vue falls that unrecognized
// prop through onto this root DOM node - and SSR and the client hydration
// script disagree on whether to do that, so it becomes a hydration mismatch
// on every page that mounts this component directly (as every demo does).
defineOptions({ inheritAttrs: false });
defineProps<{ class?: string }>();
</script>
<template>
<nav aria-label="breadcrumb" :class="$props.class">
<slot />
</nav>
</template>
<script setup lang="ts">
import { twMerge } from "tailwind-merge";
const { class: className } = defineProps<{ class?: string }>();
</script>
<template>
<ol :class="twMerge(['flex flex-wrap items-center gap-2', className])">
<slot />
</ol>
</template>
<script setup lang="ts">
import { twMerge } from "tailwind-merge";
const { class: className } = defineProps<{ class?: string }>();
</script>
<template>
<li :class="twMerge(['flex items-center gap-2', className])">
<slot />
</li>
</template>
<script setup lang="ts">
import { twMerge } from "tailwind-merge";
const { href, class: className } = defineProps<{ href: string; class?: string }>();
</script>
<template>
<a :href="href" :class="twMerge(['text-foreground/50 hover:text-foreground', className])">
<slot />
</a>
</template>
<script setup lang="ts">
import { twMerge } from "tailwind-merge";
const { class: className } = defineProps<{ class?: string }>();
</script>
<template>
<span aria-current="page" :class="twMerge(['text-foreground', className])">
<slot />
</span>
</template>
<script setup lang="ts">
import { ChevronRight } from "@lucide/vue";
import { twMerge } from "tailwind-merge";
const { class: className } = defineProps<{ class?: string }>();
</script>
<template>
<li role="presentation" aria-hidden="true" :class="twMerge(['text-foreground/30', className])">
<ChevronRight class="size-3.5" />
</li>
</template>
export { default as BreadcrumbRoot } from "./BreadcrumbRoot.vue";
export { default as BreadcrumbList } from "./BreadcrumbList.vue";
export { default as BreadcrumbItem } from "./BreadcrumbItem.vue";
export { default as BreadcrumbLink } from "./BreadcrumbLink.vue";
export { default as BreadcrumbPage } from "./BreadcrumbPage.vue";
export { default as BreadcrumbSeparator } from "./BreadcrumbSeparator.vue";
Update the import paths to match your project setup.
import {
BreadcrumbRoot,
BreadcrumbList,
BreadcrumbItem,
BreadcrumbLink,
BreadcrumbPage,
BreadcrumbSeparator,
} from "@/components/ui/breadcrumb";
<BreadcrumbRoot>
<BreadcrumbList>
<BreadcrumbItem>
<BreadcrumbLink href="/docs">Docs</BreadcrumbLink>
</BreadcrumbItem>
<BreadcrumbSeparator />
<BreadcrumbItem>
<BreadcrumbPage>Breadcrumb</BreadcrumbPage>
</BreadcrumbItem>
</BreadcrumbList>
</BreadcrumbRoot>