Navbar & Side Drawer
Build a responsive navigation bar with ReactJS and add a React-driven Side Drawer. Learn how to combine functional and class-based React components.
What We'll Build
In this article (and video - see above), we'll build a responsive toolbar and side drawer with React.js.
Here's what it'll look like:
Adding a Toolbar
You can view the full video above this article and you also find the full, finished source code in the "Resources" section above this article".
As a first step, we'll use create-react-app to create a new React project. After installing the tool, you create a new project via this command:
create-react-app react-navbar
Let's start by adding a toolbar. This will be a functional component that is decoupled from the side drawer (which we'll add later). If a "functional component" is not something you know, you might interested in learning the basics about React.js first, before diving deeper into this article.
The <Toolbar />
component is stored in a components/Toolbar
folder in the project. Here's the code for the toolbar.
import React from 'react'import './Toolbar.css'const toolbar = props => (<header className="toolbar"><nav className="toolbar__navigation"><div /><div className="toolbar__logo"><a href="/">THE LOGO</a></div><div className="spacer" /><div className="toolbar_navigation-items"><ul><li><a href="/">Products</a></li><li><a href="/">Users</a></li></ul></div></nav></header>)export default toolbar
We got a wrapping <header>
element and then a couple of containers for various elements in the toolbar. The first, currently empty, <div>
will later be used for the toggle button for the side drawer.
The <div className="spacer">
element is important for the positioning of the containers on the toolbar. Since we use Flexbox for the positioning of child elements, the spacer (with the flex: 1
style) will ensure that the logo sits on the very left of the toolbar whilst the navigation items are pushed to the right.
The styling is added via the CSS import you see in the JS file (Toolbar.css
). That CSS file is stored in the same folder. The build workflow provided by create-react-app supports such CSS imports into JS files.
In case styling is something you want to dive deeper into, we got a course for you.
Adding the Side Drawer
With the toolbar being added, it's time to also add the side drawer and a button that allows us to open that drawer. Both is stored in the components/SideDrawer
folder in the project.
The <SideDrawer>
component is a very simply component with just a bunch of lines of JSX code.
import React from 'react'import './SideDrawer.css'const sideDrawer = props => {return (<nav className="side-drawer"><ul><li><a href="/">Products</a></li><li><a href="/">Users</a></li></ul></nav>)}export default sideDrawer
The most important part here is the SideDrawer.css
file which ensures that the drawer looks like a typical drawer and is positioned above the other page content.
This includes the <Backdrop />
which is also added as a component (components/Backdrop
).
This backdrop component also is just a <div>
which is styled to act as an overlay for all the page content except for the side drawer. It's separated from that drawer because you could use that overlay for other components (e.g. some modal), too.
The Drawer Toggle Button
A <Toolbar />
and <SideDrawer />
are great building blocks but worthless without a button that combines them.
The <DrawerToggleButton />
component is exactly that.
This is how its code looks like:
import React from 'react'import './DrawerToggleButton.css'const drawerToggleButton = props => (<button className="toggle-button"><div className="toggle-button__line" /><div className="toggle-button__line" /><div className="toggle-button__line" /></button>)export default drawerToggleButton
Also not really that fancy. The interesting part is the styling (stored in DrawerToggleButton.css
):
.toggle-button {display: flex;flex-direction: column;justify-content: space-around;height: 24px;width: 30px;background: transparent;border: none;cursor: pointer;padding: 0;box-sizing: border-box;}.toggle-button:focus {outline: none;}.toggle-button__line {width: 30px;height: 2px;background: white;}
There, Flexbox is used to position the three "hamburger bars" vertically with the help of justify-content: space-around
.
This yields a nice-looking hamburger icon. And we can now start connecting all the pieces!
Connecting the Building Blocks
The <Toolbar />
should hold the <DrawerToggleButton />
which should then show/ hide the <SideDrawer />
as well as the <Backdrop />
.
Therefore, the toolbar is adjusted to hold the <DrawerToggleButton />
:
<header className="toolbar"><nav className="toolbar__navigation"><div className="toolbar__toggle-button"><DrawerToggleButton click={props.drawerClickHandler} /></div><div className="toolbar__logo"><a href="/">THE LOGO</a></div>...</nav></header>
Note the click
prop which is passed down to the <DrawerToggleButton />
component. It passes the value which the <Toolbar />
component receives via props
itself: The drawerClickHandler
- whatever that may be!
The <DrawerToggleButton />
expects that click
property and assumes that it'll hold the reference to an executable function. This is the adjusted code for the button:
const drawerToggleButton = props => (<button className="toggle-button" onClick={props.click}><div className="toggle-button__line" /><div className="toggle-button__line" /><div className="toggle-button__line" /></button>)
With the help of onClick
, clicks on the <button>
are handled and will trigger the function reference received via props.click
.
This just leaves one question: What's inside of drawerClickHandler
- the value received in <Toolbar />
which is passed on to the button in the end?
The answer can be found in the App.js
file - in the root app component which is stored there.
import React, { Component } from 'react'import Toolbar from './components/Toolbar/Toolbar'import SideDrawer from './components/SideDrawer/SideDrawer'import Backdrop from './components/Backdrop/Backdrop'class App extends Component {state = {sideDrawerOpen: false,}drawerToggleClickHandler = () => {this.setState(prevState => {return { sideDrawerOpen: !prevState.sideDrawerOpen }})}backdropClickHandler = () => {this.setState({ sideDrawerOpen: false })}render() {let backdroplet sideDrawerif (this.state.sideDrawerOpen) {backdrop = <Backdrop click={this.backdropClickHandler} />sideDrawer = <SideDrawer />}return (<div style={{ height: '100%' }}><Toolbar drawerClickHandler={this.drawerToggleClickHandler} />{sideDrawer}{backdrop}<main style={{ marginTop: '64px' }}><p>This is the page content!</p></main></div>)}}export default App
The value passed to <Toolbar />
is indeed a function reference. An address pointing at the drawerToggleClickHandler
method. This method does switch the drawer state (should it be shown or not) upon every execution. It does so by using the function form of setState
. This form ensures that the old state which is used to derive the new state takes all previous state updates into account.
Please note that another method reference (to backdropClickHandler
) is passed to the <Backdrop />
. This is done to provide a method of closing the side drawer (i.e. upon a click on the backdrop).
The sideDrawerOpen
state is then used to either render the drawer + backdrop (via the if
statement in the render
method) or not.
Animating the Drawer
The current solution works but it does simply show/ hide the drawer. This is okay and fully functional but it's not necessarily the best possible UX we can offer.
Having some "slide-in" and "slide-out" animation for the side drawer would be nice.
Thankfully, this is easy to add!
Instead of using an if
check to render or not render the sideDrawer
variable, the <SideDrawer />
component should always be rendered. And then, the drawer itself should use CSS to show or hide itself.
Therefore, the information whether the drawer should be open (visible) or not, has to be passed down to the <SideDrawer />
.
Here's the adjusted code in App.js
:
render() {let backdrop;if (this.state.sideDrawerOpen) {backdrop = <Backdrop click={this.backdropClickHandler} />;}return (<div style={{height: '100%'}}><Toolbar drawerClickHandler={this.drawerToggleClickHandler} /><SideDrawer show={this.state.sideDrawerOpen} />{backdrop}...</div>);}
As you can see, this always renders <SideDrawer />
but the show
property is passed to that component.
Inside of the SideDrawer.js
file, the code now looks like this:
const sideDrawer = props => {let drawerClasses = 'side-drawer'if (props.show) {drawerClasses = 'side-drawer open'}return (<nav className={drawerClasses}><ul>...</ul></nav>)}
drawerClasses
is a string which depends on the show
prop. If show
is true
it'll hold both the side-drawer
and the open
class.
And here's how these two classes look like (in the SideDrawer.css
file):
.side-drawer {height: 100%;background: white;box-shadow: 1px 0px 7px rgba(0, 0, 0, 0.5);position: fixed;top: 0;left: 0;width: 70%;max-width: 400px;z-index: 200;transform: translateX(-100%);transition: transform 0.3s ease-out;}.side-drawer.open {transform: translateX(0);}
By default, the side drawer is moved to the left by 100%
of its width. That means, that it's basically invisible. This is achieved via transform: translateX(-100%)
.
But when .open
is added, the transformation is changed to not move it away from its original position anymore. Therefore, the side drawer becomes visible.
The transition
property is then used to smoothly animate that transition from "pushed to the left" to "at its normal position".
Want to learn more about CSS, its properties, animations and everything you could need for your next project? Give our CSS - The Complete Guide course a chance :-).
That's it! In the video, we then also add some @media
queries for improved responsiveness but even without that, we got a fully functional, animated side drawer, built 100% with React.js!