DEV Community

Cover image for Let’s code a dribble design with Vue.js & Tailwindcss (Working demo) — Part 1 of 2
Fayaz Ahmed
Fayaz Ahmed

Posted on • Originally published at Medium on

32 8

Let’s code a dribble design with Vue.js & Tailwindcss (Working demo) — Part 1 of 2

Let’s code a dribble design with Vue.js & Tailwindcss (Working demo) — Part 1 of 2

Article #5 of my “1 article a day till lockdown ends”

Update: here's part 2

Lets pick a small design from dribble and code it with Vue.js& tailwindcss, you will find out how Vue’s two way binding works and how tailwind lets you make beautiful apps while being completely flexible and without writing any css manually.

Let’s pick a design which is easy to implement so that it wont make the article too long and is also intuitive, fun and looks beautiful. I found this design which calculates BMI by letting you select your gender, height, weight & age.

Simple BMI calculator by Rubel Vaalt

I will make use of Nuxt.js which is a framework for Vue & tailwindcss.

Why Vue & Nuxt?

Vue.js is a progressive framework which lets you makes web apps fast and quickly, it has two way binding and makes use of single file components which will let you make custom html tags like or etc, it let’s you divide your html markup into smaller blocks and make your code cleaner, readable and more maintainable. Nuxt is a framework for a vue. Why do we need a framework for a framework, as Nuxt handles at lot of real world use cases for websites, out the box which are practical, where you will end up writing lesser boilerplate code if you had chosen vue. You can still choose vue if you want to, I just like Nuxt better because of its routing and folder structure.

Why Tailwindcss?

Tailwindcss is a CSS framework with only low level css classes, it provides all the feature of a full fledged CSS framework and yet still provide all the flexibility and freedom CSS provides you, it’s not opinionated and won’t force you to write CSS in a restricted way, like a traditional framework would (Bootstrap), in simpler terms there’s a class form almost every single css rule (which you use most of the time). Technically this would mean it’s a very huge framework, which it is, but purgeCss comes to the rescue, it will remove all excess classes and only keeps whats used in your HTML (Nuxt.js has purgeCSS built in, so you don’t have to do it manually)

Step 1: Let’s create a Nuxt project and select TailwindCSS as our UI Framework.

Creating a Nuxtjs project with Tailwindcss

Once done, open the project in your favourite code editor, I prefer VS Code. you can start the project in development mode by running the command npm run dev where you will be greeted with a template. Remove all the code from pages/index.vue and remove the css from layouts/default.vue. The layouts/default.vue will act as the entry point of your project, here all the route content will be shown dynamically & your routes will be defined in the pages folder, each .vue file inside the pages folder means a webpage, and the route will be the file name. You can find more about the nuxt routing here and a free udemy course entire dedicated to nuxt.js.

We will be having two page in the app, one is the calculator which will be shown as soon as the site is opened and one would be the result page, we already have a index.vue file for it in pages folder, add another called result.vue this will add a new route to our project like example.com/result.

That’s how easy it is to create routes in nuxt.js.The project structure below.

Basic Project structure for app

Start the project by running npm run dev and open localhost:3000 in your browser and see “The Home Page” text which is our index.vue rendered, try opening localhost:3000/result you will see “The Result Page”, the result.vue page rendered.

The default.vue will be rendering these routes inside inside component. If you want any component to be shown in both routes, you can just add them in default.vue it will be automatically shown, instead of repeating the code in both pages, it’s great for n_avbar_, navigation drawers, back top buttons etc.

Step 2: Let’s code the calculator/index/home page.

We will begin by dividing the code into different components

Components

So we have 4 different components here.The Navigation Bar which will be shown in boh the index page and the result page, so we can just include it once in layout/default.vue. The other three components will be shown in the index/calculator page. Here’s the folder structure I always follow to keep the code clean and readable.

Step 3: Code the components

  • The entire app has a dark them, so let’s apply the bg-gray-900 class to our top most div, which is is layouts/default.vue.
  • Make the Navigation bar. It has one div with a icon and some text in the center, with a thich box shadow, we will use css flex to align the content and the shadow classes of tailwindcss. The below HTML code generates the navbar for us
<template>
<div class="shadow-lg py-6 px-4 flex items-center">
<div>
<svg fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"
stroke-width="2"
viewBox="0 0 24 24"
class="w-8 h-8"
>
<path d="M4 6h16M4 12h16M4 18h7"></path>
</svg>
</div>
<div class="text-center flex-1">
<p class="font-bold">BMI CALCULATOR</p>
</div>
</div>
</template>
view raw navbar.vue hosted with ❤ by GitHub

Icon used from Heroicons

So the above code generate this navbar with zero custom css written. Basically we have a parent div with with two children, a div with a svg icon and a paragrarh tag with some text. I applied flex & items-center to it to divide them on the same line and vertically aligning them in center with items-center.

I will be adding the navbar component in our layouts/default.vue file so that its available in both routes.

Navbar code being imported in default.vue file

  • The Gender component has two divs, we will make use of css grid, it’s not necessary to make use of css grid, this can be easily achievable with other ways too, just wanted explain tailwindcss features for you.

Here’s how CSS Grid works in tailwindcss, more on it here.

<template>
<section class="grid grid-cols-2 gap-4">
<div class="rounded-md shadow-md bg-gray-800 h-48"></div>
<div class="rounded-md shadow-md bg-gray-800 h-48"></div>
</section>
</template>
view raw gender.vue hosted with ❤ by GitHub

Gnder card grid with tailwindcss

The above code generates this layout, its simple and is responsive with tailwinds responsive helpers, the grid-cols-2 is specifying that we our layout will have two columns and there’s a gap of 4 units between then, no more margin hacks with columns.

<template>
<section class="grid grid-cols-2 gap-2 mb-6">
<div class="rounded-md shadow-md bg-gray-800 p-4 w-full">
<svg
class="w-16 h-16 mx-auto"
fill="currentColor"
stroke="currentColor"
viewBox="0 0 384 384"
>
<path
d="M383.793 13.938c-.176-1.38-.48-2.708-.984-3.954-.016-.03-.016-.074-.024-.113 0-.008-.008-.016-.015-.023-.555-1.313-1.313-2.504-2.168-3.61-.211-.261-.418-.52-.641-.765-.914-1.032-1.906-1.985-3.059-2.762-.03-.024-.07-.031-.101-.055-1.114-.734-2.344-1.289-3.633-1.726-.32-.114-.633-.211-.961-.297C370.855.266 369.465 0 368 0H256c-8.832 0-16 7.168-16 16s7.168 16 16 16h73.367l-95.496 95.496C208.406 107.13 177.055 96 144 96 64.602 96 0 160.602 0 240s64.602 144 144 144 144-64.602 144-144c0-33.04-11.121-64.383-31.504-89.871L352 54.625V128c0 8.832 7.168 16 16 16s16-7.168 16-16V16c0-.336-.078-.656-.098-.984a16.243 16.243 0 00-.109-1.079zM144 352c-61.762 0-112-50.238-112-112s50.238-112 112-112c29.902 0 58.055 11.64 79.223 32.734C244.359 181.945 256 210.098 256 240c0 61.762-50.238 112-112 112zm0 0"
/>
</svg>
<p class="text-center mt-8 uppercase font-bold">male</p>
</div>
<div class="rounded-md bg-gray-800 p-4 w-full opacity-50">
<svg
class="w-16 h-16 mx-auto"
fill="currentColor"
stroke="currentColor"
viewBox="-56 0 384 384"
>
<path
d="M272 136C272 61.008 210.992 0 136 0S0 61.008 0 136c0 69.566 52.535 127.016 120 134.977V304H88c-8.832 0-16 7.168-16 16s7.168 16 16 16h32v32c0 8.832 7.168 16 16 16s16-7.168 16-16v-32h32c8.832 0 16-7.168 16-16s-7.168-16-16-16h-32v-33.023c67.465-7.961 120-65.41 120-134.977zm-240 0C32 78.656 78.656 32 136 32s104 46.656 104 104-46.656 104-104 104S32 193.344 32 136zm0 0"
/>
</svg>
<p class="text-center mt-8 uppercase font-bold">female</p>
</div>
</section>
</template>
<script>
export default {};
</script>
<style></style>
view raw gender.vue hosted with ❤ by GitHub

Gender component

Let’s pick the gender icons from flaticon and add them inside these cards. This is how it will end up looking. I have added a little opacity to the non selected card with class opacity-75 to hightlight the other one.

Looks good, now let’s design the Height component. I have used a html range slider with some custom css because tailwindcss does not allow customising at this level, I made a small image to act as the slider button, because it had a border radius with opacity which we cannot do with css, yet.

<template>
<section class="rounded-md shadow-md bg-gray-800 p-4 text-center">
<p>HEIGHT</p>
<p class="text-5xl font-bold">
{{ height }}<small class="text-sm">cm</small>
</p>
<input
type="range"
min="120"
max="215"
v-model="height"
class="slider w-full h-1 rounded-lg outline-none opacity-75 transition-all duration-300 hover:opacity-100"
/>
</section>
</template>
<script>
export default {
data() {
return {
height: 120
};
}
};
</script>
<style>
.slider {
-webkit-appearance: none;
}
.slider::-webkit-slider-thumb {
-webkit-appearance: none;
appearance: none;
width: 35px;
height: 35px;
border: 0;
background: url("/slider.png");
cursor: pointer;
}
.slider::-moz-range-thumb {
width: 35px;
height: 35px;
border: 0;
background: url("/slider.png");
cursor: pointer;
}
</style>
view raw height.vue hosted with ❤ by GitHub

We now have end up with this design in our code, matches the design, though not pixel to pixel, I am still happy with the result. I have added a v-model directive to capture the range slider value whenever a user slides it and used string interpolation to display the value on top of it {{height}}, that’s a good example of how two way binding works.

Similar to the gender component with two equal divs, lets make a component to get the user’s age and weight.

<template>
<section class="grid grid-cols-2 gap-2 mb-6 text-center">
<div class="rounded-md shadow-md bg-gray-800 p-4 w-full">
<p class="uppercase font-bold">Weight</p>
<p class="text-5xl font-bold">74</p>
<div class="flex items-center justify-around">
<button class="rounded-full bg-gray-900 h-12 w-12 text-2xl">-</button>
<button class="rounded-full bg-gray-900 h-12 w-12 text-2xl">+</button>
</div>
</div>
<div class="rounded-md shadow-md bg-gray-800 p-4 w-full">
<p class="uppercase font-bold">Age</p>
<p class="text-5xl font-bold">19</p>
<div class="flex items-center justify-around">
<button class="rounded-full bg-gray-900 h-12 w-12 text-2xl">-</button>
<button class="rounded-full bg-gray-900 h-12 w-12 text-2xl">+</button>
</div>
</div>
</section>
</template>
<script>
export default {};
</script>
<style></style>
view raw ageweight.vue hosted with ❤ by GitHub

So far we have achievedmakign most of the calculator part, all that’s left to add in the markup is a button at the bottom.

We will add a fixed button at the bottom which will calculate, and display the result in the second page, where we will make use of events emitted from our components and getting captured in the parent.

This is how the index.vue page will look at the end. I have also added a button at the bottom, which finishes coding the first page. It’s not pixel perfect but we have made good progress. Here is the code commited so far (I might update it soon once the project is completed) and alive demo .

This article has become long enough hence I will break it in two parts and publish the part 2 later today.

Hope you enjoyed. You can follow me on twitter to get updated on part 2 as I will keep posting about my series over there.

Let me know if you need any help or have suggestions on this.

PS, A very happy Ramadan to everyone. Peace!

Image of AssemblyAI

Automatic Speech Recognition with AssemblyAI

Experience near-human accuracy, low-latency performance, and advanced Speech AI capabilities with AssemblyAI's Speech-to-Text API. Sign up today and get $50 in API credit. No credit card required.

Try the API

Top comments (3)

Collapse
 
kokaneka profile image
kapeel kokane

Hey, Nice article.
Btw.. The code that you provided for Navbar, in the beginning, seems to be out of place. Please check.

Collapse
 
fayaz profile image
Fayaz Ahmed

Hey. What do you mean out of place? Can you share a screenshot?

Collapse
 
sergix profile image
Peyton McGinnis • Edited

My favorite frontend stack. 🙌

Vue, Tailwind, Nuxt.

A Workflow Copilot. Tailored to You.

Pieces.app image

Our desktop app, with its intelligent copilot, streamlines coding by generating snippets, extracting code from screenshots, and accelerating problem-solving.

Read the docs

👋 Kindness is contagious

Discover a treasure trove of wisdom within this insightful piece, highly respected in the nurturing DEV Community enviroment. Developers, whether novice or expert, are encouraged to participate and add to our shared knowledge basin.

A simple "thank you" can illuminate someone's day. Express your appreciation in the comments section!

On DEV, sharing ideas smoothens our journey and strengthens our community ties. Learn something useful? Offering a quick thanks to the author is deeply appreciated.

Okay