Creating Navbar and Footer in NuxtJS with TailwindCSS
If you have been following my website series, till now we have:
- Selected the tech stack for our website
- Created starter code for our project
- Deployed the starter project to Kubernetes
In this article, let's create a Navbar and footer for our website. Unlike other components/parts of our website, both Navbar and footer persist on every page of our website.
Prerequisites for this guide
Make sure:-
- You have Tailwind installed - How to install?
- You have set up a starter Project. Visit this article to see how to set up one. You don't need to use the exact one. Just adapt the steps according to your stack.
Know a bit about TailwindCSS
Before we start using Tailwind for our styling needs, let's get to know how it is different from conventional CSS and why you should consider using it in your next project.
What is it?
Unlike the numerous UI frameworks such as Bootstrap, Vuetify, Materialize, etc., Tailwind doesn't directly provide pre-built components such as Navbar, Footer, Hero Sections, etc. What it provides is a huge assortment of classes, each applying single or, at times, a few CSS properties. It's you putting short forms of all CSS properties as classes in your HTML element. Tailwind has classes for nearly every property that you will need for styling. It also provides an easy and intuitive way to extend its base classes to add your own.
Setting up TailwindCSS for your project.
Since this is a post topic in itself, I won't go into much detail. But here is an overview of steps that you need to do:
- Add TailwindCSS to your packages.
- Configure your Nuxt(or any other framework) to use the Library by configuring the nuxt.config.js file.
- Setting up a tailwindcss.js file in your repository. This will store your Tailwind configurations.
- A tailwindcss.css file for adding your custom reusable "Tailwind mixins."
- Say goodbye?? to scratch CSS and use Tailwind classes!
My assessment
At first look, it might look a bit messy with the huge-a*s number of classes like in the below picture.
But as you start using it more, you'll realize that it's not intimidating as it looks. In fact! It's quite enjoyable. The classes are predefined, so it helps maintain consistency by limited choices. I have used both Plain CSS and SCSS. They tend to become quite ugly as your component size increases. Here you need not switch between the component and its style as the style is defined inline!!?
Let's begin!
The Navbar...
I am sure you know what's a Navbar! That ugly-looking strip at the top has links to visit various pages of the website.
Navbars are mainly top positioned(most common) like
Or they can be wherever the hell you want.?
Here is the code for the above fully responsive Navbar. This is only the template part. Read on to see the script part as well.
<template>
<nav id="navigation"
class="bg-white px-6 py-4 md:py-8 sticky top-0 z-40 font-body shadow-card hover:shadow-cardhover transition-all duration-500">
<!-- Container to give Navbar a fixed width -->
<div class="flex mx-auto items-center justify-between flex-wrap p-4 max-w-7xl">
<!-- Logo Icon -->
<NuxtLink to="/" class="flex items-center flex-no-shrink">
<BrandLogo class="h-9" alt="limosyn.com" title="Go Home" />
</NuxtLink>
<!-- Icons that show up on mobile or when window width is less -->
<span class="flex md:hidden items-center flex-row">
<a v-for="icon in socialItems" :key="item.name" target="_blank" :href="icon.link" :title="icon.name1" rel="noopener" >
<component :is="icon.icon" class="h-6 w-6 ml-4 text-gray-200 hover:text-white" />
</a>
</span>
<!-- Collapsable Menu Button - if one is active other is not. -->
<div class="block md:hidden">
<button type="button" class="inline-flex items-center justify-center border p-1 rounded text-gray-400 border-gray-400 hover:border-white focus:border-white hover:text-white focus:text-white focus:outline-none" aria-controls="mobile-menu" aria-expanded="false" @click="toggleVisibility">
<span class="sr-only">Open main menu</span>
<!-- Burger button svg-->
<svg v-if="!isVisible" class="block h-6 w-6" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor" aria-hidden="true">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16M4 18h16" />
</svg>
<!-- Cross button svg -->
<svg v-if="isVisible" class="block h-6 w-6" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor" aria-hidden="true">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
</svg>
</button>
</div>
<!-- Items of collapsible menu -->
<div :class="{ hidden: !isVisible }" class="w-full text-lg font-medium text-gray-200 hover:text-white flex-grow md:flex md:items-center md:w-auto md:ml-10">
<span v-for="menu in navItems" :key="menu.name" class="flex flex-row mt-6 md:mt-0 mr-6"> <NuxtLink :to="menu.link" class="flex items-center no-underline flex-row" @click.native="toggleVisibility" >
<component :is="menu.icon" class="w-5 h-5 mr-2 ml-1" /> \{\{ item.name }} </NuxtLink>
</span>
<!-- Icons that show up when window width > md(768px) -->
<span class="hidden md:flex flex-row mt-4 md:mt-0 ml-auto">
<a v-for="icon in socialItems" :key="icon.name" target="_blank" :href="icon.link" :title="item.name" rel="noopener" >
<component :is="icon.icon" class="h-6 w-6 ml-4" />
</a>
</span>
</div>
</div>
</div>
</template>
I know it looks like a mess! But believe me! Once you get used to it (which is not gonna be a long time), it won't be a problem. Plus, to hell with the constant scrolling up and down!?
socialItems
data object. This helps in easier addition of new fields.
Let us first add the toggling logic for the main menu.
<script>
import { ref, onMounted } from "@nuxtjs/composition-api";
export default {
components: {
BrandLogo: () => import("Images/logo-symbol.svg")
},
setup() {
const isVisible = ref(false);
const toggleVisibility = () => {
isVisible.value = !isVisible.value;
};
return {
isVisible,
navItems,
toggleVisibility,
socialItems
};
}
};
</script>
Great! Now let's add the data items to be populated in our HTML code. Here I am using reactive data items - socialItems and navItems.
const navItems = [
{
name: "Home",
link: "/",
icon: () => import("Images/svgs/home.svg")
},
{
name: "Shorts",
link: "/shorts",
icon: () => import("Images/svgs/rocket.svg"),
},
{
name: "About",
link: "/about",
icon: () => import("Images/svgs/about.svg")
}
];
const socialItems = [
{
name: "Twitter",
link: process.env.TwitterUrl,
icon: () => import("Images/svgs/twitter.svg")
},
{
name: "Github",
link: process.env.GitHubUrl,
icon: () => import("Images/svgs/github.svg")
},
{
name: "Linkedin",
link: process.env.LinkedInUrl,
icon: () => import("Images/svgs/linkedin.svg")
}
];
Constants such as Social Media Urls should be defined as environment variables to avoid redundancy and maintain consistency.
Here's what our script part of our Navbar looks like -
<script>
import { ref, onMounted } from "@nuxtjs/composition-api";
export default {
components: {
BrandLogo: () => import("Images/logo-symbol.svg")
},
setup() {
const isVisible = ref(false);
const toggleVisibility = () => {
isVisible.value = !isVisible.value;
};
const navItems = [
....]
const socialItems = [
....]
onMounted(() => // See the next section for this!)
return {
isVisible,
navItems,
toggleVisibility,
socialItems
};
}
};
</script>
Bonus - Scroll Dependent Navbar
Try scrolling on the website down! What do you observe?The Scroll bar hides...
Now scrolling up a bit! What do you see?The Scroll bar reappears!
It took me a bit of time to get this working. But it was worth it.
To add this behavior add this piece of code inside your setup() cycle hook.
onMounted(() => {
let prevScrollpos = window.pageYOffset;
window.onscroll = function() {
const currentScrollPos = window.pageYOffset;
if ((prevScrollpos > currentScrollPos) || (isVisible.value)) {
document.getElementById("navigation").style.top = "0";
} else {
document.getElementById("navigation").style.top = "-100px";
// isVisible.value = false;
}
prevScrollpos = currentScrollPos;
};
});
Now you have a Navbar for your website. You can edit the HTML, the way you like. Refer to TailwindCSS docs for class names. You can add more objects in socialItems and navItems data arrays. Do whatever you like, Jose!
The Footer...
Creating a Footer is a much more straightforward task provided you don't want to put too many shenanigans into it. My suggestion is to keep it as simple as possible. Avoid putting too many navigation items as people seldom use these links.
Here's the HTML code for this website's footer -
<template>
<footer id="Footer" class="w-full z-10 font-body bottom-0 bg-gray-700 text-gray-200 flex flex-col">
<div class="flex mx-auto items-center px-2 pt-3 pb-8 md:p-6 mb-8 flex-col md:flex-row max-w-7xl">
<div class="flex flex-row mx-2 md:mx-0">
<NuxtLink :prefetch="false" to="/" class="p-2 my-4 mx-0 flex justify-items-center items-center">
<div class="bg-gray-100 rounded-md p-2">
<BrandLogo class="h-12" />
</div>
</NuxtLink>
<div class="m-4 md:mr-36">
<Quotes class="w-4 h-4 text-gray-200 my-2" />
<p class="">Hope You learned something here. To stay updated follow me on other social media platforms</p>
<Quotes class="w-4 h-4 text-gray-200 my-2 transform rotate-180" />
</div>
</div>
<div class="flex flex-col">
<span class="flex flex-row m-4 mx-auto flex-wrap">
<a v-for="item in socialItems" :key="item.name" target="_blank" :href="item.link" :title="item.name" rel="noopener" >
<component :is="item.icon" class="h-6 w-6 mx-2 md:mx-4 text-gray-400 hover:text-white"/>
</a>
</span>
<div class="text-center text-gray-300">
Powered by
<a class="text-white hover:text-green-400" href="https://nuxtjs.org" rel="noopener" target="_blank"> NuxtJS </a>
,
<a class="text-white hover:text-red-500" href="https://ghost.org/" rel="noopener" target="_blank"> GHOST </a>
& some HARDWORK | Designed by
<NuxtLink class="text-white hover:text-yellow-400" to="/about/">Me</NuxtLink>
</div>
</div>
</div>
<div class="bg-gray-800 bottom-0 right-0 left-0">
<div class="flex mx-auto items-center justify-between flex-wrap p-4 md:px-6 max-w-7xl">
<span>
<NuxtLink :prefetch="false" to="/legal" class="text-gray-400 hover:text-white mr-2" >
Legal
</NuxtLink>
<NuxtLink :prefetch="false" to="/privacy" class="text-gray-400 hover:text-white ml-2" >
Privacy
</NuxtLink>
</span>
<a class="text-gray-100" href="/"> © limosyn.com </a>
<span class="flex items-center flex-no-shrink">
<GithubIcon class="h-6 w-6 text-gray-100 mr-2"/>
<span class="mr-1"> v{{$options.version}}</span>
</span>
</div>
</div>
</footer>
</template>
<script>
import BrandLogo from "Images/logo-symbol.svg";
import TwitterIcon from "Images/svgs/twitter.svg";
import GithubIcon from "Images/svgs/github.svg";
import LinkedInIcon from "Images/svgs/linkedin.svg";
import InstagramIcon from "Images/svgs/instagram.svg";
import Quotes from "Images/svgs/quotes.svg";
import { version } from "~/package.json";
export default {
name: "Footer",
version,
components: { BrandLogo, GithubIcon, Quotes },
setup() {
const socialItems = [
{ name: "Twitter", link: "https://twitter.com/ss18041998", icon: TwitterIcon },
{ name: "Github", link: "https://github.com/limosin", icon: GithubIcon },
{ name: "Linkedin", link: "https://www.linkedin.com/in/ss1804", icon: LinkedInIcon },
{ name: "Instagram", link: "https://www.instagram.com/limosin18/", icon: InstagramIcon }
];
return { socialItems };
}
};
</script>
You can pass socialItem objects similar to what we did in the case of Navbar data objects and have a message of your choice, etc. The sky is the limit to your creativity!
To Sum Up
I have tried to provide a brief guide to creating a Navbar and footer using TailwindCSS. Although I would love to, it's a bit impractical to write the code step by step for both Navbar and Footer in an article. Hence I have resorted to giving the complete code. In my opinion, your biggest learnings from this article should be -
- To introduce Tailwind for your HTML styling needs.
- How to create an Auto-collapsing navbar.
- How to provide data to your DOM other than hardcoding it.
See you in the next post!!