Creating a Responsive Navbar using Bulma and Vue.js
A simple guide on how to create a responsive navbar on a Vue.js project using Bulma CSS framework.
Published on May 21, 2020

First things first, I’ll be using Nuxt.js as my framework but you can follow along using anything else, modifications might be needed though.
Setup
Since we are going to use Bulma on our project let’s add it as a dependency, in my case I’ll be installing @nuxtjs/bulma
, if you are using plain Vue you can install the bulma
package instead.
yarn add @nuxtjs/bulma
Additional packages may be required depening on your project, such as node-sass
and sass-loader
if you are using sass.
Next, instead of registering the package into the modules section, we will be doing the exact opposite, if you aready have it there just comment it out. You don’t have to do this if you want to style everything within the single file components themselves, A.K.A. <style scoped>
tag.
// nuxt.config.js
export default {
modules: [
// '@nuxtjs/bulma' remove if using a separated file
]
}
Now, create a new folder to host our stylesheets, I will be using assets/stylesheets
.
In that folder, create a new file and add the following contents:
// assets/stylesheets/main.scss
// Bulma variables
@import "~bulma/sass/utilities/all";
// Bulma framework
@import "~bulma";
And last but not least, add the newly created file to the global css path:
// nuxt.config.js
export default {
css: ['~assets/stylesheets/main.scss']
}
Great, now that our CSS framework is imported and ready to be used we’re going to create the navbar component.
Creating The Navbar
Create a new component under the components
folder and name it whatever you want, I’ll call mine Navbar.vue
.
Based on Bulma’s documentation a simple navbar would look like the code below:
<!-- components/Navbar.vue -->
<template>
<header class="navbar is-fixed-top is-transparent is-spaced">
<div class="container">
<div class="navbar-brand">
<nuxt-link to="/" class="navbar-item">
<h1 class="is-size-4">Marlos Pomin</h1>
</nuxt-link>
<a
role="button"
class="navbar-burger"
aria-label="menu"
data-target="collapse"
>
<span aria-hidden="true" />
<span aria-hidden="true" />
<span aria-hidden="true" />
</a>
</div>
<div id="collapse" class="navbar-menu is-paddingless">
<nav class="navbar-end">
<nuxt-link to="/" class="navbar-item">Home</nuxt-link>
<nuxt-link to="/blog" class="navbar-item">Blog</nuxt-link>
</nav>
</div>
</div>
</header>
</template>
Here’s the demo so you can see the result:
The problem right now is that it does’t do anything, it’s responsive but not interactive in any way.
In the next step will be adding some functionality to it using a some Vue magic.
Interactivity
Create some control vars under the <script>
tag:
<!-- components/Navbar.vue -->
<script>
export default {
data() {
return {
isActive: false,
showNavbar: true
}
}
}
</script>
Now, add a click handler to toggle isActive
:
<!-- components/Navbar.vue -->
<template>
<a
:aria-expanded="isActive"
:class="{ 'is-active': isActive }"
@click="isActive = !isActive"
>
</template>
The final result will probably look like the code block below:
<template>
<header class="navbar is-fixed-top is-transparent is-spaced">
<div class="container">
<div class="navbar-brand is-family-secondary">
<nuxt-link to="/" class="navbar-item">
<h1 class="is-size-4">Marlos Pomin</h1>
</nuxt-link>
<!-- @click handler goes here, make sure to scroll a bit to see it. -->
<a
:aria-expanded="isActive"
:class="{ 'is-active': isActive }"
role="button"
class="navbar-burger"
aria-label="menu"
data-target="collapse"
@click="isActive = !isActive"
>
<span aria-hidden="true" />
<span aria-hidden="true" />
<span aria-hidden="true" />
</a>
</div>
<div
id="collapse"
:class="{ 'is-active': isActive }"
class="navbar-menu is-paddingless"
>
<nav class="navbar-end">
<nuxt-link to="/" class="navbar-item">Home</nuxt-link>
<nuxt-link to="/blog" class="navbar-item">Blog</nuxt-link>
</nav>
</div>
</div>
</header>
</template>
<script>
export default {
data() {
return {
// control variables
isActive: false,
showNavbar: true
}
}
}
</script>
And the demo would be:
Now clicking the menu works, neat right?! As a bonus I’ll show you how you can hide your navbar upon scroll and a way to close the menu if the user resizes the window.
Add the following snippet to your navbar style:
.navbar {
transition: transform 200ms ease-out;
&.is-transformed {
transform: translate3d(0, -100%, 0);
}
}
This will take care of the animation when the navbar is toggled by a scroll or resize event which is controlled by hideNav()
.
<template>
<header
:class="{ 'is-transformed': !showNavbar }"
class="navbar is-fixed-top is-transparent is-spaced"
>
<div class="container">
<div class="navbar-brand is-family-secondary">
<nuxt-link to="/" class="navbar-item">
<h1 class="is-size-4">Marlos Pomin</h1>
</nuxt-link>
<a
:aria-expanded="isActive"
:class="{ 'is-active': isActive }"
role="button"
class="navbar-burger"
aria-label="menu"
data-target="collapse"
@click="isActive = !isActive"
>
<span aria-hidden="true" />
<span aria-hidden="true" />
<span aria-hidden="true" />
</a>
</div>
<div
id="collapse"
:class="{ 'is-active': isActive }"
class="navbar-menu is-paddingless"
>
<nav class="navbar-end">
<nuxt-link to="/" class="navbar-item">Home</nuxt-link>
<nuxt-link to="/blog" class="navbar-item">Blog</nuxt-link>
</nav>
</div>
</div>
</header>
</template>
<script>
import throttle from 'lodash/throttle'
export default {
data() {
return {
isActive: false,
showNavbar: true,
lastScrollPosition: 0
}
},
mounted() {
this.$nextTick(() => {
window.addEventListener('resize', throttle(this.closeMenu, 500))
window.addEventListener('scroll', throttle(this.hideNav, 250))
})
},
beforeDestroy() {
window.removeEventListener('resize', this.closeMenu)
window.removeEventListener('scroll', this.hideNav)
},
methods: {
closeMenu() {
this.isActive = false
},
hideNav() {
const currentScrollPosition =
window.pageYOffset || document.documentElement.scrollTop
if (currentScrollPosition < 0) return
if (Math.abs(currentScrollPosition - this.lastScrollPosition) < 60) return
this.showNavbar = currentScrollPosition < this.lastScrollPosition
this.lastScrollPosition = currentScrollPosition
setTimeout(this.closeMenu, 250)
}
}
}
</script>
As for the result, you are looking at it! scroll up and down to see the effect.
Thanks for reading!