Breadcrumbs for humans and robots

Small sites such as this one are easy to navigate. With every page merely one or two clicks away, it doesn’t take visitors much effort to keep track of where they are. But as a website’s structure gets more complex and its hierarchy deepens, it becomes increasingly difficult to understand how one page ties to the next and where the current page fits within the overall structure. The visitor can get lost in the labyrinth.

Breadcrumbs are an effective tool to show the location of a page within the site’s hierarchy. They mark the trail from the current page all the way back to the homepage, facilitating wayfinding and quick navigation to parent pages. In this article we’ll have a look at a semantic markup pattern for breadcrumbs, some basic styling, and ways to make the breadcrumbs machine-readable for enhanced search engine results.

Breadcrumbs for humans

Let’s say we made a website for the chemistry novice. Atoms and isotopes, molecules and mixtures, reactions and bonds; with so much material to cover, it’s only natural that our site grew tall and now contains some deeply nested pages. You can consider a page deeply nested if it's not part of the main navigation and isn't a direct descendant of a page which is. To prevent the student chemist from getting lost in the complex hierarchy of sections and subsections, we decide to implement breadcrumbs on those pages.

For what follows we'll take the page on the element cobalt as an example, which can be reached from the homepage through the trail Home → Periodic table → Elements → Cobalt.

The markup

Breadcrumbs mostly act as a secondary navigation on a page, alongside the site's main navigation. The tried-and-tested markup pattern for a navigation bar looks something like this:

html
<nav>
<ul>
<li><a href="/">Home</a></li>
<li><a href="/about">About</a></li>
<li><a href="/contact">Contact</a></li>
</ul>
</nav>

We can structure our breadcrumbs much in the same way, but with two differences:

Keeping that in mind, our markup becomes:

html
<nav aria-label="Breadcrumbs">
<ol>
<li><a href="/">Home</a></li>
<li><a href="/periodic-table">Periodic table</a></li>
<li><a href="/periodic-table/elements">Elements</a></li>
</ol>
</nav>

Simple, semantic markup. Notice that we didn't include the current page, the one about cobalt, in the breadcrumbs. Repeating the page title in the trail is redundant. If for some reason you do want to include it, simply add it to the list without a link, like so:

html
<nav aria-label="Breadcrumbs">
<ol>
<li><a href="/">Home</a></li>
<li><a href="/periodic-table">Periodic table</a></li>
<li><a href="/periodic-table/elements">Elements</a></li>
<li>Cobalt</li>
</ol>
</nav>

Styling

Our breadcrumbs need a visual separator to communicate a sense of hierarchy among the items. Commonly a right-pointing arrow or caret > is used, creating the suggestion of a forward-going trail. Many a breadcrumb you'll encounter has those carets baked into the markup, either in a <span> within each list item, or even as separate <li> elements. We want none of that, as these carets only serve a visual purpose, and can jumble screen reader output when contained in the markup. Instead, we'll resort to the ::before pseudo-element in our CSS.

Let's get to it then. First, we'll add some classes to our markup:

html
<nav class="breadcrumbs" aria-label="Breadcrumbs">
<ol class="breadcrumbs__list">
<li class="breadcrumbs__item"><a href="/">Home</a></li>
<li class="breadcrumbs__item"><a href="/periodic-table">Periodic table</a></li>
<li class="breadcrumbs__item"><a href="/periodic-table/elements">Elements</a></li>
</ol>
</nav>

We want to display our list items in a row and allow them to wrap to multiple lines if we run out of space. We also remove some of the default list styles:

css
.breadcrumbs__list {
display: flex;
flex-wrap: wrap;
list-style: none;
padding-left: 0;
}

Then we add the separators between our list items and allow them to breathe by adding some margin around them:

css
.breadcrumbs__item + .breadcrumbs__item::before {
content: '>';
margin-left: 0.5em;
margin-left: 0.5em;
}

Using a caret as pseudo-content is a valid option, but visual results may vary depending on the font you use. We can use an svg icon as the separator instead. Add the svg as a data URI by encoding it, set it as an inline-block, give it a width, and play around with the top value to align the icon with the text:

css
.breadcrumbs__item + .breadcrumbs__item::before {
content: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='9 18 15 12 9 6'%3E%3C/polyline%3E%3C/svg%3E");
display: inline-block;
width: 1em;
margin-left: 0.5em;
margin-right: 0.5em;
position: relative;
top: 0.2em;
}

For basic breadcrumbs, that is all we need. Here's how it looks:

Example

Saving space on small screens

We made sure our breadcrumbs wrap over several lines when they don't fit on a single one. As a result, a long trail of breadcrumbs might take up a lot of vertical space on smaller screens. To strike a balance between screen real estate and usability, we can collapse the list on mobile devices and only link back to the parent page.

Simply hide all items but the last one. Optionally, swap out the icon for a left caret to give the single remaining link more of a "back to index"-look:

css
@media (max-width: 36em) {
.breadcrumbs__item:not(:last-child) {
display: none;
}

.breadcrumbs__item:last-child::before {
content: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='15 18 9 12 15 6'%3E%3C/polyline%3E%3C/svg%3E");
}
}
Example

Breadcrumbs for robots

Humans aren't the only ones visiting our website on chemistry. Search engines might crawl the site to index its pages. Once again, breadcrumbs can help search engines figure out how the website is structured and how to categorize the content. This is especially useful when the URL structure for some reason doesn't directly mirror the structure of the content. E.g.: https://www.example.com/pages/2021/02/periodic-table/e/27/cobalt.

We can give hints to robots by adding structured data to our pages. Most search engines accept and understand 3 different formats: microdata, RFDa and JSON-LD. We'll work with JSON-LD because Google prefers it, and because it allows us to decouple the structured data from our markup, which in this case is a good thing. Sometimes we may wish to signal the site structure to crawlers without providing visible breadcrumbs to our visitors.

Going back to our page on cobalt, we add the following to the <head>:

html
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "BreadcrumbList",
"itemListElement": [
{
"@type": "ListItem",
"position": 1,
"item": {
"@id": "https://www.example.com/",
"name": "Home"
}
},
{
"@type": "ListItem",
"position": 2,
"item": {
"@id": "https://www.example.com/periodic-table",
"name": "Periodic table"
}
},
{
"@type": "ListItem",
"position": 3,
"item": {
"@id": "https://www.example.com/periodic-table/elements",
"name": "Elements"
}
},
{
"@type": "ListItem",
"position": 4,
"item": {
"@id": "https://www.example.com/periodic-table/elements/cobalt",
"name": "Cobalt"
}
}
]
}
</script>

As a bonus, the structured data allows us to exert some control over the appearance of our page in Google's search results. It's possible, but not guaranteed, that Google displays our breadcrumbs in place of the regular URL-based trail.

A comparison between the Google search result of a page before and after adding structured data breadcrumbs
Google can use the information in our structured data to offer better search results.

You can use Google's structured data testing tool to test your implementation and check for errors.

Conclusion

Breadcrumbs are a simple tool to help visitors and crawlers understand and navigate a complex website. With very little markup, we're able to boost usability and search engine results. Basic breadcrumbs for happy humans and robots.