Warning: this article was published some time ago, and might no longer be up to date. Take it with a grain of salt.
I remember when I picked up CakePHP back in the days, I loved how easy it was to get started with. Not only were the docs well-structured and exhaustive, but they were also user-friendly. Years later, this is exactly what I found with Vue.js. Yet there’s one thing the Vue docs are still short of compared to Cake: a real-life project tutorial.
No matter how well-documented a framework is, this isn’t enough for everyone. Reading about concepts doesn’t always help seeing the bigger picture or understand how you can use them to actually make something. If you’re like me, you learn better by doing and you refer to the docs while you code, as you need them.
In this tutorial, we’ll build a star rating system component. We’ll visit several Vue.js concepts when we need them and we’ll cover why we’re using them.
TL;DR: this post goes in-depth in the how and why. It’s designed to help you grasp some core concepts of Vue.js and teach you how to make design decisions for your future projects. If you want to understand the whole thought process, read on. Otherwise you can look at the final code on CodeSandbox.
Vue.js (rightfully) prides itself on being runnable as a simple script include, but things are a bit different when you want to use single-file components. Now you don’t have to build components this way. You can perfectly get away with defining a global component with
Vue.component. Problem is, this comes with tradeoffs like having to use string templates, no scoped CSS support, and no build step (so, no preprocessors). Yet, we want to go deeper and learn how to build an actual component that you could use in a real project. For those reasons, we’ll go with an actual real-life setup, powered by Webpack.
First, you need to install vue-cli globally. Fire up your terminal and type the following:
You can now generate ready-to-use Vue.js boilerplates in a few keystrokes. Go ahead and type:
You’ll be asked a few questions. Choose defaults for all except “Use sass” to which you should answer yes (
y). Then, vue-cli will initialize the project and create the
package.json file. When it’s done you can navigate to the project’s directory, install dependencies and run the project:
That’s it! Webpack will start serving your project on port
8080 (if available) and fire it in your browser. If all went well, you should see a welcome page like this one.
Are we there yet?
Your first component
SFCs end with a
.vue extension and have the following structure:
Let’s go and make our first component: create a
Rating.vue file in
/src/components, and copy/paste the code snippet above. Then, open
/src/main.js and adapt the existing code:
Finally, add a little bit of HTML to your
Now look at the page in your browser, and you should see the list! Vue.js attached your
<Rating> component to the
#app element in
index.html. If you inspect the HTML, you should see no sign of the
#app element: Vue.js replaced it with the component.
Sidenote: have you noticed you didn’t even need to reload the page? That’s because Webpack’s vue-loader comes with a hot reload feature. Contrary to live reloading or browser syncing, hot reloading doesn’t refresh the page every time you change a file. Instead, it watches for component changes and only refreshes them, keeping state untouched.
Now we’ve spent some time setting things up, but it’s time we actually write meaningful code.
Then edit your component like the following:
Alright alright, let’s slow down for a moment and explain all that 😅
Vue.js uses native ES6 modules to handle dependencies and export components. The first two lines in the
<script> block import icons individually, so you don’t end up with icons you don’t need in your final bundle. The third one imports the
Icon component from
vue-awesome so you can use it in yours.
Icon is a Vue.js SFC, like the one we’re building. If you open the file you’ll see it has the exact same structure as ours.
export default block exports an object literal as our component’s view model. We registered the
Icon component in the
components property so we can use it locally in ours.
Finally, we used the
Icon in our HTML
<template> and passed it a
name property to define what icon we want. Components can be used as custom HTML tags by converting them to kebab-case (eg.:
<my-component>). We don’t need to nest anything inside of the component, so we used a self-closing tag.
Sidenote: have you noticed we added a wrapping
<div> around the HTML? That’s because we also added a counter in a
<span> at root level, and component templates in Vue.js only accept one root element. If you don’t respect that, you’ll get a compilation error.
If you’ve worked with CSS for some time, you know one of the main challenges is having to deal with its global nature. Nesting has long been considered the solution to this problem. Now we know it can quickly lead to specificity issues, making styles hard to override, impossible to reuse, and a nightmare to scale.
Methodologies like BEM were invented to circumvent this issue and keep low specificity, by namespacing classes. For a while, it’s been the ideal way to write clean and scalable CSS. Then, frameworks and libraries like Vue.js or React came along and brought scoped styling to the table.
React has styled components, Vue.js has component-scoped CSS. It lets you write component-specific CSS without having to come up with tricks to keep it contained. You write regular CSS with “normal” class names, and Vue.js handles scoping by assigning data-attributes to HTML elements and appending it to the compiled styles.
Let’s add some simple classes on the component:
And style it:
See that scoped attribute up there? That’s what tells Vue.js to scope the styles, so they won’t leak anywhere else. If you copy/paste the HTML code right in the
index.html, you’ll notice your styles won’t apply: that’s because they’re scoped to the component! 🎉
What about preprocessors?
Vue.js makes it a breeze to switch from plain CSS to your favorite preprocessor. All you need is the right Webpack loader and a simple attribute on the
<style> block. We said “yes” to “Use sass” when generating the project, so vue-cli already installed and configured sass-loader for us. Now, all we need to do is add
lang="scss" to the opening
We can now use Sass to write component-level styles, import partials like variables, color definitions or mixins, etc. If you prefer the indented syntax (or “sass” notation), simply switch
sass in the
Now that our component looks good, it’s time to make it work. Currently, we have a hardcoded template. Let’s set up some initial mock state and adjust the template so it reflects it:
What we did here is use Vue’s
data to setup component state. Every property you define in
data becomes reactive: if it changes, it will be reflected in the view.
We’re making a reusable component, so
data needs to be a factory function instead of an object literal. This way we’re getting a fresh object instead of a reference to an existing one that would be shared across several components.
data factory returns two properties:
stars, the current number of “active” stars, and
v-for directive loops over any iterable object (arrays, objects literals, maps, etc.). It also can take a number as a range to be repeated x number of times. That’s what we did with
v-for="star in maxStars", so we have an
<li> for each star in the component.
You may have noticed some properties are prefixed with a colon: this is a shorthand for the
v-bind directive, which dynamically binds attributes to an expression. We could have written it in its long form,
We need to append the
active class on
<li> elements when the star is active. In our case, this means every
<li> which index is less than
stars should have the
active class. We used an expression in the
:class directive to only append
active when the current
star is less than
stars. We used the same condition, this time with a ternary operator, to define what icon to use with the
What about the counter?
Now that our star list is bound to actual data, it’s time we do the same for the counter. The simplest way to do this is to use text interpolation with the mustache syntax:
Here this is overkill. We can get away with in-template expressions and still keep things readable. Yet, keep computed properties in mind for when you have to deal with more convoluted logic.
Another thing we need to do is provide a way to hide the counter if we don’t want it. The simplest way to do this is to use the
v-if directive with a boolean.
We’re almost done but we still have to implement the most interesting part of our component: reactivity. We’re going to use
v-on, the Vue.js directive that handles events, and
methods, a Vue.js property on which you can attach all your methods.
We added a
@click attribute on the
<li>, which is a shorthand for
v-on:click. This directive contains a call to the
rate method which we defined in the
methods property of the component.
“Wait a minute… this looks awfully familiar with HTML’s
It is indeed, but even though the syntax looks a lot like
onclick attributes. Vue.js compiled your component and created proper bindings. This is also why you have access to the context of the component right from your template: because directives are bound to the view model. Contrary to a traditional project with separate HTML, the template is an integral part of the component.
Back to our
rate method. We need to mutate
stars to the index of the clicked element, so we pass the index from the
@click directive, and we can do the following:
Go check the page in your browser and try clicking on stars: it works!
If you open the Vue panel in your browser devtools and select the
<Rating> component, you’ll see the data change as you click on stars. This shows you that your
stars property is reactive: as you mutate it, it dispatches its changes to the view. That concept is called data-binding, which you should be familiar with if you ever used frameworks like Backbone.js or Knockout. The difference is that Vue.js, like React, does it in one direction only: this is called one-way data-binding. But that topic deserves an article of its own 😊
At this point, we could call it done but there’s a bit more work we could do to improve user experience.
Right now, we can’t actually give a grade of zero, because clicking on a star sets the rate to its index. What would be better is to re-click on the same star and have it toggle its current state instead of staying active.
Now if the clicked star’s index is equal to the current value of
stars, we decrement its value. Otherwise, we assign it the value of
If we want to be thorough, we should also add a layer of controls to make sure
stars is never assigned a value that doesn’t make sense. We need to make sure
stars is never less than
0, never greater than
maxStars, and that it’s a proper number.
Right now, the component’s data is hardcoded in the
data property. If we want our component to actually be usable, we need to be able to pass custom data to it from its instances. In Vue.js, we do that with props.
There are three things to observe here:
First, we used the
v-bind shorthand to pass props from the component instance: this is what Vue.js calls the dynamic syntax. You don’t need it when you want to pass a string value, for which the literal syntax (normal attribute without
v-bind) will work. But in our case, since we’re passing numbers and booleans, it’s important we do.
data properties are merged at compile time, so we don’t need to change the way we call properties either in the view model or in the template. But for that same reason, we can’t use the same names for
Finally, we defined a
grade prop and passed it as a value to
stars in the
data property. The reason why we did that instead of using the
grade prop directly is that the value will be mutated when we change the grade. In Vue.js, props are passed from parents to children, not the other way around, so you don’t accidentally mutate the parent’s state. This would go against the one-way data flow principle and make things harder to debug. This is why you should not try to mutate a prop inside of a child component. Instead, define a local
data property that uses the prop’s initial value as its own.
Before we call it a day, there’s one last piece of Vue.js goodness we should visit: prop validation.
Vue.js lets you control props before they’re passed to the component. You can perform four major things: check type, require a prop to be defined, setup default values, and perform custom validation.
We used type checking to make sure the right type of data is passed to the component. This will be especially useful if we forget to use the dynamic syntax to pass non-string values. We also made sure the
grade prop was passed by requiring it. For other props, we defined default values so the component works even if no custom data is passed.
Now we can instantiate the component simply by doing the following:
And that’s it! You just created your first Vue.js component, and explored many concepts including generating a boilerplate projet with vue-cli, single-file components, importing components in components, scoped styling, directives, event handlers, computed properties, custom methods, one-way data flow, props and prop validation. And that’s only scratching the surface of what Vue.js has to offer!
This is a pretty dense tutorial, so don’t worry if you didn’t understand everything. Read it again, pause between sections, explore and fiddle with the code on CodeSandbox. And if you have any question or remark about the tutorial, don’t hesitate to hit me up on Twitter!