Build a Responsive Slideout Drawer

Matthew Polizzotti
14 min readOct 24, 2018

--

Introduction

A slideout drawer is a menu that lives outside your application’s viewport and slides in and out of view based upon some user interaction (e.g. a user clicking or tapping a hamburger menu button). Slideout drawers are fantastic patterns to follow when screen real estate is limited and you need to provide additional features and functionality to your end user. This tutorial will walk you through how to setup a simple slideout drawer primarily using CSS3 and a little JavaScript. We’ll then take it a step further and show you how you can easily make your slideout drawer responsively adjust to changing viewports.

Take a look at this CodePen Demo for a preview of what we’ll be building.

Before We Begin

To get the most of out this tutorial you should be familiar with basic web development practices. Specifically, you should have some experience with HTML5, LESS for CSS development and the latest versions of the JavaScriptlanguage (e.g., ES6, ES2015, etc.). If your not comfortable setting up a development workspace you can easily setup a quick environment using codepen, plunkr or jsfiddle to follow along with this tutorial.

The HTML

For our slideout drawer we are going to create a navigation menu that slides out from the left-hand side of the page after a hamburger menu button is clicked from a navbarcomponent. To make our setup a little easier, faster and nicer to look at we’ll be using Bootstrap 4, jQuery, and Font Awesome. So, let’s get started.

Setup Your Page

The first thing we need to do is setup a simple page and include all of our previously mentioned dependencies. The structure listed below pretty much comes right out of the Bootstrap 4 Getting Started documentation with the exception of Font Awesome.

<!DOCTYPE html>
<html lang="en">
<head>
<!-- Required meta tags -->
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">

<!-- Bootstrap CSS -->
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-beta/css/bootstrap.min.css">

<!-- Font Awesome CSS -->
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css">
</head>
<body>

<!-- Bootstrap JS Dependencies -->
<script src="https://code.jquery.com/jquery-3.2.1.slim.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.11.0/umd/popper.min.js"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-beta/js/bootstrap.min.js"></script>
</body>
</html>

We then need to add a #siteWrapper div container just inside the body tag. The #siteWrapper will house our navbar and our slideout drawer.

<body>
<div id="siteWrapper" class="site-wrapper"></div>
</body>

Adding the Navbar

Just inside the #siteWrapper container add the navelement with the .navbar navbar-dark bg-dark class names. This snippet of code, again, comes right from Bootstrap 4's navbar component and it will create and position for us a navbar with a hamburger menu button at the top of our page. The hamburger menu button will be used to open our slideout drawer.

<body>
<div id="siteWrapper" class="site-wrapper">
<!--Navbar-->
<nav class="navbar navbar-dark bg-dark">
<a id="hamburgerMenuButton" class="navbar-brand" href="#" role="button">
<i class="fa fa-bars" aria-hidden="true">
<span class="sr-only">Open sidebar menu.</span>
</i>
</a>
</nav>
</div>
</body>

Adding the Slideout Drawer

Underneath our nav element we'll now add the #slideoutand #slideoutMask containers. Our #slideout container consists of both a header (.slideout-header) and content (.slideout-content) areas. The .slideout-headercontains a close button for our slideout drawer and the .slideout-content section contains our site's navigation links. Lastly, our #slideoutMask, which sits at the bottom of the page, will cover or mask our entire site when the slideout drawer is enabled. This will direct the user's attention to the open slideout drawer and reduce any noise or distraction from the rest of the page.

<body>
<nav>
...
</nav>
<div id="slideout" class="slideout">
<div class="slideout-header">
<button id="slideoutCloseButton" href="#" class="btn btn-link slideout-close" title="Close">
<i class="fa fa-times fa-lg">
<span class="sr-only">Close slideout menu.</span>
</i>
</button>
</div>
<div class="slideout-content">
<section class="slideout-section">
<h2 class="section-title">Explore</h2>
<nav>
<ul class="list-unstyled section-list">
<li class="section-list-item" role="presentation">
<a href="#">Home</a>
</li>
<li class="section-list-item" role="presentation">
<a href="#">About</a>
</li>
<li class="section-list-item" role="presentation">
<a href="#">Contact</a>
</li>
</ul>
</nav>
</section>
</div>
</div>
<div id="slideoutMask" class="slideout-mask"></div>
</body>

Now that we have our markup in place let’s move forward and style our slideout drawer.

The CSS

As mentioned above we’ll be using the LESS preprocessor for our CSS code development. Before we start coding we’ll define a color palette for our slideout drawer and define some global variables and mixins that will be used throughout our implementation.

Add a Color Palette

In using any CSS preprocessor, whether it’s LESS or SASS, one of the first things we should do is setup a basic color palette for the feature. We’ll keep our color palette for the slideout drawer really simple and just define colors for white, black, red and three tones of gray.

/* Color Palette
----------------------------------------------*/
@white: #FFFFFF;
@black: #000000;
@red: #DB0900;
@gray: #868E96;
@gray-light: lighten(@gray, 10%);
@gray-lighter: lighten(@gray-light, 10%);

Add Variables

In addition to a color palette, it’s also a good idea to create a variables section and list out any global values for properties that we’ll need to reference throughout our feature. For our slideout drawer we’ll need to manage and adjust the z-index property of both our slideout drawer and the drawer's mask, which covers and darkens the screen when our slideout drawer is opened.

/* Variables
----------------------------------------*/
// Base for z-index property.
@zindex: 1000;

Add Mixins

We’ll also add three lightweight mixins for convenience. The first two mixins, .drop-shadow and .opacity should be somewhat self-explanatory. We'll be using the .drop-shadow mixin to add a shadow to our slideout drawer and the .opacity mixin to control the opacity of our slideout drawer's mask when the slideout drawer is opened and closed. The last mixin, .hover, will be used to apply a subtle color fade on hovered link elements and anchor tags.

/* Mixins
-----------------------------------------------*/
.drop-shadow (@x: 0, @y: 1px, @blur: 2px, @spread: 0, @alpha: 0.25) {
box-shadow: @x @y @blur @spread rgba(0, 0, 0, @alpha);
}
.opacity (@opacity: 0.5) {
opacity: @opacity;
}
.hover (@color: @black, @hover-color: @red, @text-decoration: none) {
color: @color;
text-decoration: none;
cursor: pointer;
transition: color 0.3s ease;
&:hover, &:focus {
color: @hover-color;
text-decoration: @text-decoration;
}
}

Style the Slideout Container

We want our .slideout drawer to stretch from the top of the page to the bottom of the page and we only want it to be about 560px or (35em) wide for desktop users. To make this happen we'll make use of the position, top, left, bottom and width properties defined in the below code snippet. We'll adjust the width to be a bit more responsive later on.

.slideout {
position: fixed;
top: 0;
left: 0;
bottom: 0;
width: 35em;
}

We also want to style our slideout drawer with a white background and a gray border with a slight drop shadow on the right-hand side of drawer to make it really look like the drawer is hovering above the main page. To achieve this, take a look at the below code, where we’ve added background-color and border-right properties as well as added our drop shadow effect using our .drop-shadow mixin that we defined earlier.

.slideout {
position: fixed;
top: 0;
left: 0;
bottom: 0;
width: 35em;
background-color: @white;
border-right: 0.063em solid lighten(@gray-light, 30%);
.drop-shadow(@x: 1px, @y: 0, @blur: 8px, @spread: 0, @alpha: 0.50);
}

We want our .slideout container to explicitly sit above other positioned elements. To make this happen we've added the z-index property and set it to a 1000 with our previously defined @zindex variable. This will force our .slideout container above other positioned elements as long as those elements also receive a z-index property smaller then a 1000. This is something we'll do later on when styling the slideout mask.

.slideout {
position: fixed;
top: 0;
left: 0;
bottom: 0;
width: 35em;
background-color: @white;
border-right: 0.063em solid lighten(@gray-light, 30%);
.drop-shadow(@x: 1px, @y: 0, @blur: 8px, @spread: 0, @alpha: 0.50);
z-index: @zindex;
}

Setup the Slideout Container for Animation

Adding to the .slideout style definition we also want the ability to move our slideout drawer on and off screen. We can accomplish this with the transform property, which applies a 2D or 3D transformation to an element. Transformations include rotating, scaling, moving or skewing an element and this is exactly what we'll need to move our slideout drawer on and off the screen. To make transformations easier the transform property provides several transform-functions such as translateX(x), translateY(y), scale(x,y) and many others (check out the MDN web docs on transform-functions). As a starting point for our slideout drawer we'll give the transform property a value oftranslateX(-100%). This will position our slideout drawer completely off-screen. Later on in the tutorial we'll update this value to translateX(0) to move the slideout drawer into view. For a nice explanation of the transform property checkout this CSS-Tricks article on CSS transforms.

.slideout {
position: fixed;
top: 0;
left: 0;
bottom: 0;
width: 35em;
background-color: @white;
border-right: 0.063em solid lighten(@gray-light, 30%);
.drop-shadow(@x: 1px, @y: 0, @blur: 8px, @spread: 0, @alpha: 0.50);
z-index: @zindex;
transform: translateX(-100%);
}

The transform property will help us move our .slideoutcontainer on and off the screen, however, our goal is to animate our .slideout container from one position to another. The transform property alone is not enough to wire up a smooth animation. We need to tell the browser that we want a smooth transition or animation from one position to the next. To do that we'll use the transition property, which allows you the ability to smoothly change or animate property values. The below code example highlights the syntax for the transition property. For a full explanation checkout this CSS-Tricks article on CSS transitions.

transition: <property> <duration> <timing-function>

To animate our slideout drawer along the x-axis, we'll define the transition property as transition: transform 0.3s ease. This means that any update to the transformproperty value will result in a smooth animation that takes .3 seconds where the speed of the animation is calculated by the ease timing-function (checkout the MDN web docs on transition-timing-functions for more information). For example, if we were to change transform: translateX(-100%) to transform: translateX(0) our .slideout container would smoothly slide into view. Likewise, changing the values back again from transform: translateX(0) to transform: translateX(-100%) would animate the .slideout container off screen.

In addition to animating the transform property, you've probably noticed that we have also added width 0.3s easeto our transition. You can define multiple transitions, separated by a comma, to the transition property. In our case, extending the transition property with width 0.3s ease states that we'd also like to animate the widthproperty with a duration of .3 seconds using the ease timing function. We'll explain why we're animating the width property later on in the tutorial.

.slideout {
position: fixed;
top: 0;
left: 0;
bottom: 0;
width: 35em;
background-color: @white;
border-right: 0.063em solid lighten(@gray-light, 30%);
.drop-shadow(@x: 1px, @y: 0, @blur: 8px, @spread: 0, @alpha: 0.50);
z-index: @zindex;
transform: translateX(-100%);
transition: transform 0.3s ease, width 0.3s ease;
}

Style the Slideout Header

When our slideout drawer is open we’ll want the ability to close it. For that reason, we have a .slideout-headercontainer with a nested .slideout-close button. The .slideout-header container is given some padding to increase the visual space around the close button. We'll also be using flexbox to position the .slideout-close button. To do so, we'll first set the display property of the .slideout-header to flex, which defines the .slideout-header as a flex container. Next we'll set the justify-content property on .slideout-header to flex-end. This will position the nested .slideout-close button toward the far right-hand side of the .slideout-header container. For more on flexbox checkout Chris Coyier's Complete Guide to Flexbox.

.slideout-header {
padding: 1.2em 1.0em 1.2em 1.7em;
display: flex;
justify-content: flex-end;

.slideout-close {
display: inline-block;
.hover(@color: lighten(@gray-light, 10%), @hover-color: @black);
&:focus {
outline: auto 5px -webkit-focus-ring-color;
}
}
}

Style the Slideout Content

Along with a .slideout-header our slideout drawer has a .slideout-content container, which, as you may have already guessed, contains all of our slideout drawer's contents. Similar to the .slideout-header we'll add some padding to the .slideout-content container for visual space and add a border-top definition to the container to add some visual separation between the header and content area of our slideout drawer. Lastly, for a little added polish, we'll apply our .hover mixin to all anchor tags within the .slideout-content container. The .hover mixin adds a smooth color transition when hovering or focusing site links. In our case, the .hover mixin will fade from black to a dark red when hovered or focused.

.slideout-content {
border-top: 0.063em solid lighten(@gray-light, 10%);
padding: 3.750em;

a {
.hover(@hover-color: darken(@red, 10%));
text-transform: uppercase;
font-size: 0.875em;
}
}

Style the Slideout Sections

Our slideout drawer also contains a navigation menu for our site, which consists of a menu title and three links (e.g., home, about and contact). We want the .section-title to be a bit larger then our navigation links so we set the font-sizeto 1.125em or (18px) and we set the text-transformproperty to uppercase. We also wanted to have a little separation between the .section-title and our .section-list so, first, we reset the margin and paddingdefinitions and then we give the .section-title a heavy margin-bottom of 1.563em or (25px). Lastly, the .section-list and .section-list-item containers, which make up our actual menu, is mostly relying on Bootstrap 4's .list-unstyled class definition along with some minor tweaks to margin and padding definitions for its over all visual appearance.

.slideout-section {
.section-title {
font-size: 1.125em;
text-transform: uppercase;
margin: 0 0 1.563em 0;
padding: 0;
}

.section-list {
margin: 0;

.section-list-item {
padding: 0 0 0.625em 0;
}
}
}

Style the Slideout Mask

Whenever our slideout drawer is opened we want to cover or mask everything that lives underneath it. We’ll do this by setting the position, top, left, bottom and rightproperties for the .slideout-mask as shown below. To picture what we're doing think of the .slideout-maskcontainer as a rectangle. We are simply forcing each of the sides of our rectangle to stick to the four corners of your browser window.

.slideout-mask {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
}

While unlikely, let's also add overflow: hidden to our .slideout-mask, which will explicitly prevent any scrolling behavior.

.slideout-mask {
position: fixed;
overflow: hidden;
top: 0;
left: 0;
right: 0;
bottom: 0;
}

Now that our mask is covering our screen we want to give it a slightly transparent background color. We'll accomplish this by setting the background-color property using our @black variable and then we'll adjust the opacity of the background with our .opacity mixin that we defined earlier.

.slideout-mask {
position: fixed;
overflow: hidden;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: @black;
.opacity(@opacity: .5);
}

Next we'll add the z-index property to our .slideout-mask and we'll set it to have a value that is less then our .slideout container. If your remember from earlier on in the tutorial we set the z-index on our .slideout container to be a 1000 using the @zindex variable. Setting the z-indexproperty on the .slideout-mask to be less then a 1000 will explicitly position our .slideout-mask above the main page but not above our .slideout container, which houses our navigation menu.

.slideout-mask {
position: fixed;
overflow: hidden;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: @black;
.opacity(@opacity: .5);
z-index: (@zindex - 1);
}

When we open and close our slideout drawer we'd like our mask to fade in and fade out, respectively. To start we'll use our .opacity mixin and set it to 0 to make our .slideout-mask fully transparent. For this tutorial our .slideout-maskcontainer is always present, always lives on the main page and is stacked on top of our main page regardless of it's opacity. Without adding anything more to our .slideout-mask definition we've created an invisible sheet that is covering our main page, which prevents the user from interacting with any of the underlying links or buttons. To fix this issue, we need to also add the visibility property and initially set it to hidden. The visibility property, unlike the display property can be animated to show or hide an element without removing that element from the layout of our document. Lastly, we configure the transition property to animate any changes to opacity or visibility. For more information on what CSS properties can be animated checkout the MDN web docs on animatable css properties.

.slideout-mask {
position: fixed;
overflow: hidden;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: @black;
z-index: (@zindex - 1);
.opacity(@opacity: 0);
visibility: hidden;
transition: opacity 0.3s ease, visibility 0.3s ease;
}

Add a Style to Open the Slideout Drawer

The last piece of CSS that needs to be added is the .slideout-open definition. This class, using JavaScript, will be dynamically added and removed from our #siteWrappercontainer. If you remember from when we set up our markup, the #siteWrapper wraps our entire slideout drawer feature and represents the top-most level of our template. We'll append the .slideout-open class to the #siteWrappercontainer to override the opacity and visibility settings on our .slideout-mask as well as position our .slideoutdrawer into view with the transform property and since these properties also have a transition associated with them changes in their values will result in a smooth animation.

.slideout-open {
.slideout-mask {
.opacity(@opacity: 0.7);
visibility: visible;
}

.slideout {
transform: translateX(0);
}
}

Add Responsive Styling

To make our slideout drawer more responsive we’ll write a media query that will set the width of our .slideoutcontainer to 100vw any time our slideout drawer is open and the browser width is less then or equal to 768px. So, when our slideout drawer is open and the browser width is reduced our slideout drawer's width will grow until it reaches the full width of the available viewport.

@media (max-width : 768px) {
.slideout-open {
.slideout {
width: 100vw;
}
}
}

Now that our CSS is complete let’s bring it all together with some JavaScript.

The JavaScript

We want our slideout drawer to open when the user selects the hamburger menu in the navigation header as well as close with the slideout drawer’s close button. We’ll wire up this functionality with Native JavaScript. The below code is relatively lightweight and could easily be translated to work within your framework of choice (i.e., AngularJS, React, VueJS, etc.)

To get started we’ll first cache references to the #siteWrapper, #hamburgerMenuButton and the #slideoutCloseButton using the const declaration. We have chosen to use the const declaration instead of the letkeyword because we assume that the chosen variable identifiers won't or shouldn't change. For more information on the const declaration checkout the MDN documentation on using the const keyword.

// Cache references to DOM nodes.
const siteWrapper = document.querySelector('#siteWrapper');
const hamburgerMenuButton = document.querySelector('#hamburgerMenuButton');
const slideoutCloseButton = document.querySelector('#slideoutCloseButton');

We’ll then add the openSlideoutDrawer function which will open our slideout drawer by appending the .slideout-openclass name onto the #siteWrapper container.

// Cache references to DOM nodes.
const siteWrapper = document.querySelector('#siteWrapper');
const hamburgerMenuButton = document.querySelector('#hamburgerMenuButton');
const slideoutCloseButton = document.querySelector('#slideoutCloseButton');

function openSlideoutDrawer () {
siteWrapper.classList.add('slideout-open');
slideoutCloseButton.focus();
}

We’ll also need to add the closeSlideoutDrawer function which will close our slideout drawer by removing the .slideout-open class name from the #siteWrappercontainer.

// Cache references to DOM nodes.
const siteWrapper = document.querySelector('#siteWrapper');
const hamburgerMenuButton = document.querySelector('#hamburgerMenuButton');
const slideoutCloseButton = document.querySelector('#slideoutCloseButton');
function openSlideoutDrawer () {
siteWrapper.classList.add('slideout-open');
slideoutCloseButton.focus();
}
function closeSlideoutDrawer () {
siteWrapper.classList.remove('slideout-open');
hamburgerMenuButton.focus();
}

Finally, we’ll use the addEventListener method to call the openSlideoutDrawer function anytime the hamburgerMenuButton receives a click event. Using the same approach, we'll also wire up the slideoutCloseButton to trigger the closeSlideoutDrawer function.

// Cache references to DOM nodes.
const siteWrapper = document.querySelector('#siteWrapper');
const hamburgerMenuButton = document.querySelector('#hamburgerMenuButton');
const slideoutCloseButton = document.querySelector('#slideoutCloseButton');

function openSlideoutDrawer () {
siteWrapper.classList.add('slideout-open');
slideoutCloseButton.focus();
}

function closeSlideoutDrawer () {
siteWrapper.classList.remove('slideout-open');
hamburgerMenuButton.focus();
}

// Bind event listeners.
hamburgerMenuButton.addEventListener('click', openSlideoutDrawer, false);
slideoutCloseButton.addEventListener('click', closeSlideoutDrawer, false);

Conclusion

This tutorial covered one way of creating a slideout drawer with transitions. I’m positive there are other approaches and techniques but this should give a nice starting point for you to play around with different implementations.

I hope you enjoyed this tutorial and thanks for checking it out! Please leave me a comment with any feedback, improvements or suggestions.

--

--

Matthew Polizzotti

Husband, father, software architect, all round developer and lifetime learner. Currently works at Unicon. Loves Pearl Jam.