Codementor Events

Navigation drawer tutorial with Nuxt

Published Feb 01, 2019Last updated Jun 05, 2020
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.

  1. 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).
  2. Select none for server-side frameworks, UI framework, testing framework.
  3. Choose spa mode
  4. Select none for axios and linters
  5. Navigate to the project and run npm install
  6. 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!.

The mobile view

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.

Discover and read more posts from Justin
get started
post comments2Replies
Ebuka Umeh
6 years ago

An amazing tutorial, it was pretty detailed.

Justin
6 years ago

Thank you Ebuka