Navigation drawer tutorial with Nuxt
Coming up with navigation that looks good and responsive on small devices could be challenging. This will get you started with responsive navigation without necessary bloating the project with a third-party plugin.
The responsiveness was achieved with CSS grid. If you are yet to dive into this powerful and yet elegant way of achieving responsiveness, check here.
This tutorial assumes you are already familiar with Nuxt (framework for writing a universal application in Vue), its folder structure and Vuex (state management library for Vue).
Requirements.
I will be using the available nuxt-create-app
tool to scaffold this app quickly. Feel free to create the Nuxt app from scratch.
So, using nuxt-create-app
, you need to have npx
installed on your device. npx is shipped with NPM
since 5.2.0 version.
However, you would need NPM and Node installed either to start the project from scratch or to use nuxt-create-app
tool.
Follow these simple steps to install Nuxt. We are going to keep the options very simple.
- Run
npx create-nuxt-app <project-name>
(in this case, navigation-nav is the name of our project. You can change it to whatever you like). - Select
none
for server-side frameworks, UI framework, testing framework. - Choose
spa
mode - Select none for
axios
andlinters
- Navigate to the project and run
npm install
- Finally npm run dev If everything goes well, you should see your application running on localhost:3000
Default layout
Components that are common to all pages will be placed here. This file will have the TheHeader
, TheSideNav
, TheFooter
, and the default nuxt
component.
<template>
<div class="app-container">
<TheHeader/>
<TheSideNav/>
<div class="app-content">
<nuxt />
</div>
<TheFooter/>
</div>
</template>
<script>
import TheHeader from '~/components/TheHeader'
import TheFooter from '~/components/TheFooter'
import TheSideNav from '~/components/TheSideNav'
export default {
components: {
TheHeader,
TheSideNav,
TheFooter
},
computed: {
isSidebar() {
return this.$store.getters['nav/toggleSidebar']
}
},
watch: {
'$route': function() {
if (process.client && this.isSidebar && window.innerWidth < 768) {
this.$store.dispatch('nav/toggleSidebar')
}
}
}
</script>
<style>
html, body{
margin: 0;
height: 100%;
width: 100%;
}
.app-container{
height: 100%;
position: relative;
display: grid;
grid-template: auto 1fr auto / 1fr;
}
.app-content{
min-height: 100vh;
padding: 24px;
display: grid;
align-items: center;
justify-items: center;
}
</style>
App Links
Since the links are going to be the same in TheHeader
and TheSideNav
components, I will create a file in the components directory and name it appLinks.vue
so it can be reused.
<template>
<ul class="nav-list">
<li class="nav-item"><nuxt-link to="/about">About</nuxt-link></li>
<li class="nav-item"><nuxt-link to="/services">Services</nuxt-link></li>
<li class="nav-item"><nuxt-link to="/contact">Contact</nuxt-link></li>
</ul>
</template>
<style scoped>
.nav-list {
list-style: none;
padding: 0;
margin: 0;
}
.nav-item {
margin: 0 10px;
}
.nav-item a {
text-decoration: none;
color: #fefefe;
}
.nav-item a:hover,
.nav-item a:active{
color: #b4b4b4;
}
@media (max-width: 767px) {
.nav-list {
display: block;
}
.nav-item{
margin-top: 16px;
margin-bottom: 16px;
}
}
@media (min-width: 768px) {
.nav-list {
display: flex;
}
}
</style>
The Header
This file will have the brand name, the toggle button that displays on a smaller device and of course the app links we initially created.
The essence of this tutorial lies in this component and particularly in the toggle button.
The toggle element has v-on:click
directive attached to it ( I prefer the short form, @click
). When this element is clicked, it dispatches the theToggleSidebar
action in the nav module. The toggleSibebar state determines if the sidebar would be displayed or not. This same code works hand in hand with the backdrop element inside theTheSideNav
component.
<template>
<header>
<div class="brand-name">
<nuxt-link to="/">HAPPY VUE YEAR</nuxt-link>
</div>
<div class="drawer-toggle" role="button" @click="$store.dispatch('nav/toggleSidebar')">
<div class="bar"></div>
<div class="bar"></div>
<div class="bar"></div>
</div>
<div class="app-links">
<app-links></app-links>
</div>
</header>
</template>
<script>
import AppLinks from '~/components/appLinks'
export default {
components: { AppLinks }
}
</script>
<style scoped>
header{
display: grid;
grid-template: 60px / auto 1fr;
align-items: center;
background-color: #333;
}
.app-links{
justify-self: end;
}
.brand-name {
margin: 0 10px;
font-size: 1.3rem;
}
.brand-name a {
text-decoration: none;
color: white;
}
.drawer-toggle .bar {
width: 90%;
height: 2px;
background-color: white;
}
.drawer-toggle {
display: flex;
justify-self: end;
flex-direction: column;
justify-content: space-around;
height: 50%;
width: 35px;
padding-right: 16px;
cursor: pointer;
}
@media (max-width: 767px) {
header{
padding: 0 16px;
}
header:nth-child{
justify-self: end !important;
}
.app-links {
display: none;
}
}
@media (min-width: 768px) {
header{
padding: 0 64px;
}
.app-links {
display: block;
}
.drawer-toggle {
display: none;
}
}
</style>
The Sidebar Navigation
This element has toggleSidebar
method in the computed
property of the TheSideNav
component. The value is boolean. This element also has a backdrop as earlier stated. When either the toggle button or the backdrop is clicked, the toggleSibebar
state in nav module(store) is mutated to either true
or false
. Hence, that change is received in the computed
property via toggleSidebar
method we created.
I added a transition effect to the sidebar in order have a nice experience on the toggling of the sidebar. This was done effortlessly because of the Vue transition wrapper component.
<template>
<div class="sidenav-container">
<div v-if="toggleSidebar" class="backdrop" @click="$store.dispatch('nav/toggleSidebar')"></div>
<transition name="slide-side">
<div v-if="toggleSidebar" class="sidenav">
<app-links></app-links>
</div>
</transition>
</div>
</template>
<script>
import AppLinks from '~/components/appLinks'
export default {
components: { AppLinks },
computed: {
toggleSidebar() {
return this.$store.getters['nav/toggleSidebar']
}
}
}
</script>
<style scoped>
.sidenav-container {
height: 100%;
width: 100%;
}
.sidenav {
height: 100%;
width: 300px;
background-color: #d6d6d6;
z-index: 10000;
position: fixed;
top: 0;
left: 0;
box-sizing: border-box;
padding: 30px;
}
.backdrop {
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.7);
z-index: 1000;
position: fixed;
top: 0;
left: 0;
}
.slide-side-enter-active,
.slide-side-leave-active {
transition: all 0.3s ease-out;
}
.slide-side-enter,
.slide-side-leave-to {
transform: translateX(-100%);
}
</style>
The store
I opted for the module method. The classic way of managing state will be deprecated in the next Nuxt release. Our nav
module is pretty basic. It has the normal handlers state
, mutations
, actions
, and getters
.
// States
export const state = () =>({
toggleSidebar: false
})
// mutations
export const mutations = {
TOGGLE_SIDEBAR(state) {
state.toggleSidebar = !state.toggleSidebar
}
}
// actions
export const actions = {
toggleSidebar({ commit }) {
commit('TOGGLE_SIDEBAR')
}
}
// Getters
export const getters = {
toggleSidebar: state => state.toggleSidebar,
}
The mobile look!.
Conclusion
We have suceeded in coming up with cool navigation drawer that works well on small devices by using simple CSS grid and Vuex in Nuxt app.
The full code can be found on Github amd the Demo is here
This post was first published here.
An amazing tutorial, it was pretty detailed.
Thank you Ebuka