Smart breadcrumb
A breadcrumb built from the matched route records. No per-page configuration.
<script setup lang="ts">
import SmartBreadcrumb from '@shared/components/common/SmartBreadcrumb.vue'
import { Button } from '@shared/components/ui/button'
import { onMounted } from 'vue'
import { useRouter } from 'vue-router'
const Blank = { render: () => null }
const router = useRouter()
onMounted(() => {
if (!router.hasRoute('docs-customers')) {
router.addRoute({
path: '/crm',
component: Blank,
meta: { title: 'CRM' },
children: [
{
path: 'customers',
name: 'docs-customers',
component: Blank,
meta: { title: 'Customers' },
children: [
{ path: ':id', name: 'docs-customer', component: Blank, meta: { breadcrumb: 'Northwind' } },
],
},
],
})
}
router.push('/crm/customers/42')
})
</script>
<template>
<div class="space-y-3">
<SmartBreadcrumb />
<div class="flex flex-wrap gap-2">
<Button size="sm" variant="outline" @click="router.push('/crm/customers')">
Customers
</Button>
<Button size="sm" variant="outline" @click="router.push('/crm/customers/42')">
A customer
</Button>
</div>
</div>
</template>Usage
Drop it in a layout's #breadcrumb slot and it reads the current route. Every matched record carrying a meta.title becomes a crumb, and the last one is plain text rather than a link, because that is where you already are.
{
path: '/crm/customers',
meta: { title: 'Customers' },
}meta.breadcrumb overrides meta.title for the crumb only, which is how a route can have a long page title and a short crumb.
Dynamic labels
meta.breadcrumb may be a function, and it may be async. That is how a record's own name becomes the crumb:
{
path: '/crm/customers/:id',
meta: {
breadcrumb: async route => (await fetchCustomer(route.params.id)).name,
},
}Labels resolve into a map keyed by route path, and the trail reacts when they land, so the crumb fills in without the page waiting for it.
A string meta.title or meta.breadcrumb goes through uiText, so a translation key is translated and a literal is left alone. A value returned by a function is a runtime value, a record's name, and is passed through untouched. See Internationalization.
Hiding it
Any matched record with meta.hideBreadcrumb hides the whole trail, so a section can opt out at its parent rather than on every child.
The component also renders nothing when no crumb has a label, so a route with no titles leaves no empty bar behind.
API
Source: src/shared/components/common/SmartBreadcrumb.vue
Props
None. It reads the route.
Emits
None.
Slots
None.
Route meta it reads
| Key | Type | Description |
|---|---|---|
meta.title | string | The crumb, unless meta.breadcrumb is set. Resolved through uiText. |
meta.breadcrumb | string | (route) => string | Promise<string> | Overrides the crumb. A function may be async. |
meta.hideBreadcrumb | boolean | On any matched record, hides the whole trail. |
Accessibility
- It renders a real breadcrumb: a
<nav aria-label="breadcrumb">around an ordered list. - The last crumb carries
aria-current="page", so the reader is told which one they are on. - Separators are
role="presentation"andaria-hidden, so the trail is not read as a run of slashes.