Gretel
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)
Installation
composer require glhd/gretel
Usage
Defining Breadcrumbs
Gretel adds a new Route macro that you can use when defining your routes:
Single Breadcrumb
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');
Nested Breadcrumb
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).
Parent Shorthand
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.
Shallow 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);
Resource Routes
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',
]);
Vendor Routes
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
);
Displaying Breadcrumbs
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 |
Supported Frameworks
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 Frameworks
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):
layouts/app.blade.php
:
<!DOCTYPE html>
<html>
<head>
<title>{{ $title }}</title>
<x-breadcrumbs jsonld />
</head>
<body>
<div class="container mx-auto">
<x-breadcrumbs framework="tailwind" />
...
</div>
</body>
</html>
Custom Breadcrumb View
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" />
Using Breadcrumbs in Some Other Way
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();
});
Accessibility
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
Caching Breadcrumbs
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.
Handling Errors
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'));
Integration With Third Party Packages
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.