Laravel breadcrumbs right out of a fairy tale.
Gretel is a Laravel package for adding route-based breadcrumbs to your application.
- Defining Breadcrumbs
- Displaying Breadcrumbs
- Using Gretel With Your CSS Framework of Choice
- Using a Custom Template (while maintaining accessibility)
- Caching Breadcrumbs (required if using
route:cache
) - Handling Errors
- Integration With Third Party Packages (Inertia.js)
composer require glhd/gretel
Gretel adds a new Route macro that you can use when defining your routes:
In the simplest case, chain the breadcrumb()
function onto your existing route to define a breadcrumb:
Route::get('/', HomeController::class)
->name('home')
->breadcrumb('Home');
If you need to dynamically control the title, pass in a closure instead:
Route::get('/dashboard', DashboardController::class)
->name('dashboard')
->breadcrumb(fn() => Auth::user()->name.'’s dashboard');
Breadcrumbs aren't very useful unless you string them together. Gretel handles nested breadcrumbs by pointing to a previously-defined parent breadcrumb:
Route::get('/users', [UserController::class, 'index'])
->name('users.index')
->breadcrumb('Users');
Route::get('/users/{user}', [UserController::class, 'show'])
->name('users.show')
->breadcrumb(fn(User $user) => $user->name, 'users.index');
Route::get('/users/{user}/edit', [UserController::class, 'edit'])
->name('users.edit')
->breadcrumb('Edit', 'users.show');
Here, you can see that our users.show
route references users.index
as its parent. This way, when you render
breadcrumbs for users.show
it will also show the breadcrumb for users.index
.
Gretel assumes that the parameters in nested routes can be safely used for their parent routes. In this example,
users.edit
will render the users.show
breadcrumb using the User
value that was resolved for the edit action.
In the vast majority of cases, this is exactly what you want. If not, you can override this behavior (see below).
Often, a child route will reference a parent with the same name prefix. In our above example, users.show
references
users.index
and users.edit
references users.show
. In this case, you can use the parent shorthand:
Route::get('/admin/users/{user}/notes/create', [NotesController::class, 'create'])
->name('admin.users.notes.create')
->breadcrumb('Add Note', '.index'); // shorthand for "admin.users.notes.index"
This is particularly useful for large apps that have many deeply nested routes.
If your nested routes do not contain the route parameters necessary for the parent route, you will need to provide the values to Gretel. You can do this using a third callback:
Route::get('/companies/{company}', [CompanyController::class, 'show'])
->name('companies.show')
->breadcrumb(fn(Company $company) => $company->name);
Route::get('/users/{user}', [UserController::class, 'show'])
->name('users.show')
->breadcrumb(fn(User $user) => $user->name, 'companies.show', fn(User $user) => $user->company);
You can also define breadcrumbs for resource controllers. The index()
, create()
,
show()
, and edit()
methods behave exactly like the regular breadcrumb helper except that
they automatically set up the parent for you if you don’t provide one.
Route::resource('users', UserController::class)
->breadcrumbs(function(ResourceBreadcrumbs $breadcrumbs) {
$breadcrumbs
->index('Users')
->create('New User')
->show(fn(User $user) => $user->name)
->edit('Edit');
});
If you prefer, you can also use an array syntax for simple resource routes:
Route::resource('users', UserController::class)
->breadcrumbs([
'index' => 'Users',
'create' => 'New User',
'show' => fn(User $user) => $user->name,
'edit' => 'Edit',
]);
Sometimes you want to register breadcrumbs for routes that are defined in 3rd-party packages.
In this case, you can use the Gretel
facade directly. The API is exactly the same as the
Route::breadcrumb()
method, except that you must pass the route name as the first parameter:
Gretel::breadcrumb(
'teams.show', // Route name
fn(Team $team) => $team->name, // Title callback
'profile.show', // Parent
);
You can display the breadcrumbs for the current route with the <x-breadcrumbs />
Blade component. The Blade component
accepts a few optional attributes:
Attribute | |
---|---|
framework |
Render to match a UI framework ("tailwind" by default) |
view |
Render a custom view (supersedes the framework attribute) |
jsonld |
Render as a JSON-LD <script> tag |
Gretel supports most common CSS frameworks. We've taken the CSS framework's documented markup and
added additional aria-
tags where appropriate for better accessibility. Currently supported frameworks:
Tailwind use "tailwind"
(default)
Materialize use "materialize"
Bootstrap 5 use "bootstrap5"
Bulma use "bulma"
Semantic UI use "semantic-ui"
Primer use "primer"
Foundation 6 use "foundation6"
UIKit use "uikit"
Older versions of some frameworks are also available:
- Bootstrap 3 use
"bootstrap3"
- Bootstrap 4 use
"bootstrap4"
- Foundation 5 use
"foundation5"
You'll typically want to include the <x-breadcrumbs />
tag somewhere in your application layout
(maybe twice if you're using JSON-LD):
<!DOCTYPE html>
<html>
<head>
<title>{{ $title }}</title>
<x-breadcrumbs jsonld />
</head>
<body>
<div class="container mx-auto">
<x-breadcrumbs framework="tailwind" />
...
</div>
</body>
</html>
You can render a custom view either by publishing the gretel.php
config file via
php artisan vendor:publish
or by passing a view
attribute to the Blade component:
<x-breadcrumbs view="app.breadcrumbs" />
If you need to use the breadcrumbs in some other way—maybe for rending via a client-side
framework that Gretel doesn’t already integrate with—you can always just get the current
breadcrumbs as a Collection
or array
off the route:
Route::breadcrumbs()->toCollection(); // Collection of `Breadcrumb` objects
Route::breadcrumbs()->toArray(); // Array of `Breadcrumb` objects
Route::breadcrumbs()->jsonSerialize(); // Array of breadcrumb arrays (title, url, is_current_page)
Route::breadcrumbs()->toJson(); // JSON string of breadcrumbs matching jsonSerialize
For example, our Inertia.js integration could easily be implemented as:
Inertia::share('breadcrumbs', function(Request $request) {
return $request->route()->breadcrumbs()->jsonSerialize();
});
If you choose to render your own view, please be sure to follow the current WAI-ARIA accessibility best practices. Gretel provides some helpers to make this easier:
@unless ($breadcrumbs->isEmpty())
<!-- Wrap your breadcrumbs in a <nav> element with an aria-label attribute -->
<nav aria-label="Breadcrumb">
<!-- Use an <ol> (ordered list) for the breadcrumb items -->
<ol>
@foreach ($breadcrumbs as $breadcrumb)
<!-- You can use $activeClass() or $inactiveClass() to conditionally apply classes -->
<li class="{{ $activeClass('active-breadcrumb') }}">
<!-- Use $ariaCurrent() to apply aria-current="page" to the active breadcrumb -->
<a href="{{ $breadcrumb->url }}" {{ $ariaCurrent() }}>
{{ $breadcrumb->title }}
</a>
</li>
@endforeach
</ol>
</nav>
@endunless
Because Gretel breadcrumbs are registered alongside your routes, you need to cache your breadcrumbs if you cache your routes. You can do so with the two commands:
# Cache breadcrumbs
php artisan breadcrumbs:cache
# Clear cached breadcrumbs
php artisan breadcrumbs:clear
Please note that you must cache your breadcrumbs before you cache your routes.
Sometimes you'll mis-configure a breadcrumb or forget to define one. You can register handlers
on the Gretel
facade to handle these cases:
// Log or report a missing breadcrumb (will always receive a MissingBreadcrumbException instance)
Gretel::handleMissingBreadcrumbs(function(MissingBreadcrumbException $exception) {
Log::warning($exception->getMessage());
});
// Throw an exception locally if there's a missing breadcrumb
Gretel::throwOnMissingBreadcrumbs(! App::environment('production'));
// Log or report a mis-configured breadcrumb (i.e. a parent route that doesn't exist).
// This handler will pick up any other exception that is triggered while trying to configure
// or render your breadcrumbs, so the type is unknown.
Gretel::handleMisconfiguredBreadcrumbs(function(Throwable $exception) {
Log::warning($exception->getMessage());
});
// Throw an exception locally if there's a mis-configured breadcrumb
Gretel::throwOnMisconfiguredBreadcrumbs(! App::environment('production'));
Gretel automatically shares your breadcrumbs with Inertia.js if you have that package installed. You don't need to do anything to enable this integration. (If you do not want this behavior for some reason, you can disable it by publishing the Gretel config.)
Your breadcrumbs will be available in your client code as breadcrumbs
and look something like:
const breadcrumbs = [
{
title: 'Home',
url: 'https://www.yourapp.com',
is_current_page: false,
}, {
title: 'Users',
url: 'https://www.yourapp.com/users',
is_current_page: false,
}, {
title: 'Add a User',
url: 'https://www.yourapp.com/users/create',
is_current_page: true,
}
];
You can then render the breadcrumbs in the client however you see fit. Be sure to review the custom breadcrumbs section for information about how to ensure that your client-side breadcrumbs are fully accessible.