Build a Stunning Breadcrumb Component in React with Plain CSS
Tags: CSS, Front End Development, Web Development, React, JavaScript

In this tutorial, we are going to quickly generate a react project with the create-react-app.

(If you want to get a copy of the repository from github, click here).

Go ahead and create a project using the command below. For this tutorial, I’ll call our project breadcrumb.

npx create-react-app breadcrumb

Once it’s done, go into the directory:

cd breadcrumb

Inside the main entry src/index.js we're going to clean it up a bit so we can focus on the component alone:

src/index.js

Now create src/App.js :

src/App.js

import React from 'react' const App = () => <div /> export default App

We will be using App.js to render the breadcrumb components. We will also use it to define some sample routes so we can see the breadcrumb in action later.

We’ll create a src/pages directory and add in the sample routes by creating files for each of them:

Next, we’ll import them into our App.js component and render them inside a Router so our breadcrumb component can act upon them. You can use any routing library, but for this tutorial i'm going to use @reach/router:

src/App.js

And here are my starting css styles:

src/styles.css

Next we’ll create the Breadcrumb component so we can render the breadcrumb items per route path inside:

src/Breadcrumb.js

Now you can import that from App.js and render it inside its render method.

I’ve also defined some routes as an array so that I can render them inside the breadcrumb component. (Note: in a real scenario, you would provide child routes that render as the browser’s location goes further down a hierarchy — but we’ll just use top level routes here just to show the implementation of a breadcrumb interface in effect)

src/App.js

You should now see something like this:

It’s starting to look like a breadcrumb! In its current state it might have been sufficient in the 90s — but we have to keep thinking of ways to improve it.

We can start by providing a wrapper around each breadcrumb item. Also, as we’re rendering these items as a list, we can convert the elements to an ordered list, so that we’re being a little more formal:

src/Breadcrumb.js

And for the BreadcrumbItem component, we can define this at the top:

const BreadcrumbItem = ({ children, ...props }) => (

<li className='breadcrumb-item' {...props}>

{children}

</li>

)

This is what it looks like now:

Since we applied an ordered list and a list of list item elements to our breadcrumb component, we have to change the direction that the items are facing. We also need to remove the numeric characters.

We can accomplish this with plain css:

src/styles.css

ol {

list-style: none;

display: flex;

align-items: center;

}

An odd thing you might have noticed in the breadcrumb component is this line:

let children = React.Children.toArray(props.children)

There’s a good reason for this. When we rendered our items as the direct child of the breadcrumb component, we did this:

React treats each component rendered directly under it as an array, as long as there are multiple components.

When we manipulate this “array” of components using various methods (such as slice), react needs a key to be passed into them each time. When we use React.Children.toArray to wrap our children, react automatically assigns and handles all of the key requirements to the original children for subsequent uses. Perfect! We don’t have to apply unnecessary ugly code to our component code. Thank you React!

Breadcrumbs often use a separator to separate each of their items so that they are easier to use and read. When you apply a separator (most commonly a slash like “/”) users will be able to tell which parts are separate from one another. Sometimes — on some screens — you’d find your words unexpectedly too close to one another, so this helps with those visibility issues.

Next we’ll go ahead and add a .reduce to the children so that we can apply the logic to get these separators in place:

src/Breadcrumb.js

(Note: We implemented a conditional check so that we don’t unnecessarily render a trailing slash at the end of the breadcrumb)

From the screenshot we can definitely see that we separated each breadcrumb item. However, going back to my previous comment about some users not being able to see words clearly due to how close the words can become in some screens, the same issue now reappeared with the separators.

So we’re going to apply a wrapper for the separator and attach a className prop right on the li element so we can manipulate the spacing a bit:

src/Breadcrumb.js

const BreadcrumbSeparator = ({ children, ...props }) => (

<li className='breadcrumb-separator' {...props}>

{children}

</li>

)

src/styles.css

.breadcrumb-separator {

color: #333;

margin: auto 6px;

user-select: none;

}

And now, we simply just need to wrap our separator with the new wrapper component:

Perfect!

Sometimes however, as a developer we might want to switch up what kind of separator to use — such as an arrow. We can give the option to customize that by allowing the caller to pass in a custom separator as a prop:

src/Breadcrumb.js

src/App.js

src/App.js

What happens when a breadcrumb gets too long?

Most common in a large scaled application, there can be an abundant amount of routes that can make a breadcrumb longer than it should be.

If we want to combat that, a known solution is to use a collapser.

We’ll create a collapser component.

First create src/BreadcrumbCollapser.js . This will be used to collapse items from the interface so that the breadcrumb component can hide some of its items:

src/BreadcrumbCollapser.js

src/styles.css

When we introduce any form of a “collapser”, we need it to know when or when not to collapse. Giving the user the ability to control that behavior is a great way to enhance the user experience of a collapser.

I’m going to create a custom hook called useBreadcrumb.js and set a state called expanded. The interface is going to need this information, so it is returned from the custom hook:

src/useBreadcrumb.js

Creating a custom react hook in a separate file might be overkill, but I like the extra organizational habits it brings to my development flow! It’s entirely optional and you can instead just set the state directly in the breadcrumb component.

Next, I imported both the custom hook and the breadcrumb collapser from the Breadcrumb.js file:

src/Breadcrumb.js

import React from 'react'

import useBreadcrumb from './useBreadcrumb'

import BreadcrumbCollapser from './BreadcrumbCollapser'

Using the custom hook:

const Breadcrumb = ({ separator, ...props }) => {

let children = React.Children.toArray(props.children) const { expanded, open } = useBreadcrumb()

It’s probably helpful to provide a custom prop that the caller can pass in to control the amount of items that are collapsed or shown, so we allow the caller to pass in a custom collapse prop that we can use to apply to the breadcrumb collapser:

This will ensure that the caller has the ability to control what to see, tailoring it to be a little more compatible for their UI if needed.

Finally, applying the collapser to the rendering children:

Here is our entire breadcrumb implementation so far:

src/Breadcrumb.js

The links need a little bit of customization since they’re pretty basic and old. Let’s change that:

a {

text-decoration: none;

font-weight: 400;

color: #424548;

} a:hover {

color: #1da2b3;

}

If you’re more of the functional type of person and our component isn’t very appealing to you, it’s perfectly fine to refactor it to be a little more appealing:

src/Breadcrumb.js

Something to keep in mind here is that if we didn’t use React.Children.toArray to wrap our children, we’d be getting key errors in the console for the rendering children lists without providing a key to them.

Wondering how to render icons in between? There are multiple ways to achieve this but one way would be something like:

src/App.js

Finally, I mentioned earlier that in a real world scenario we would have child routes being rendered in the breadcrumb component — through a hierarchy. In this tutorial we used top level routes, which isn’t ideal but I wanted to show how the interface could be implemented. A more realistic approach to the interface can be achieved using the tactics in this tutorial:

Beautiful!

Conclusion

That concludes this tutorial! If you’ve been reading my previous posts you have probably realized that I don’t say much in the conclusion — because everything was already said above!

See you guys next time and look out for more quality posts!