eslint-vue-plugin

We default to using eslint-vue-plugin, along with plugin:vue/recommended and @vue/prettier. Please check the default rules for more in-depth documentation.

Naming

Extensions: Use .vue extension for Vie components. Do not use .js

Component Referencing: Use PascalCase when referencing Components.

// Bad

import cardBoard from 'cardBoard.vue'

components: {
  cardBoard
}

// Good

import CardBoard from 'cardBoard.vue'

components: {
  CardBoard
}

Multi-word Component Names: Component names should always be multi-word, except for root App components and built-in components provided by Vue, such as <transition> or <component>. This prevents conflicts with exisiting and future HTML elements, since all HTML elements are a single word.

// Bad
Vue.component('todo', {
  // ...
})

export default {
  name: 'Todo',
  // ...
}

// Good
Vue.component('todo-item', {
  // ...
})

export default {
  name: 'TodoItem',
  // ...
}

Single File Component Casing: Filenames of Single File Components should either be always PascalCase or kebab-case.

// Bad

components/
|- mycomponent.vue

components/
|- myComponent.vue


// Good

components/
|- MyComponent.vue

components/
|- my-component.vue

Component Data

Component Data: A Components data must be returned as a function. When data is returned as an object this exposes it to all instances of that Component.

// Bad

data: {
  listTitle: 'This is bad...',
  todos: []
}

// Good

data() {
  return {
    listTitle: 'This is much better!',
    todos: []
  }
}

Component Props

Prop definitions: Props should be as detailed as possible. At the bare minimum a Prop type should be defined.

// Bad

props: ['status']

// Good

props: {
  status: String
}

// Even Better!

props: {
  status: {
    type: String,
    required: true,
    validator: function (value) {
      return [
        'syncing',
        'synced',
        'version-conflict',
        'error'
      ].indexOf(value) !== -1
    }
  }
}

Event Names

Unlike components and props, event names don’t provide any automatic case transformation. Instead, the name of an emitted event must exactly match the name used to listen to that event.

Unlike components and props, event names will never be used as variable or property names in JavaScript, so there’s no reason to use camelCase or PascalCase. Additionally, v-on event listeners inside DOM templates will be automatically transformed to lowercase (due to HTML’s case-insensitivity), so v-on:myEvent would become v-on:myevent – making myEvent impossible to listen to.

For these reasons, always use kebab-case for event names.

// Bad
this.$emit('myEvent')

// Good
this.$emit('my-event')
this.$emit('my-event', someData)

:key and v-for

Keyed v-for: When using v-for you must always use :key on the iterated Component.

// Bad

<ul>
  <li v-for="todo in todos">
    {{ todo.text }}
  </li>
</ul>

// Good

<ul>
  <li
    v-for="todo in todos"
    :key="todo.id"
  >
    {{ todo.text }}
  </li>
</ul>

Avoid v-if with v-for

Never use v-if on the same element as a v-for

It may be tempting to use this in the following scenarios:

  • To filter items in a list (i.e. v-for="user in users" v-if="user.isActive"). In this case a better approach would be to replace users with a computed property that returns a filtered list (e.g. activeUsers).
  • To avoid rendering a list if it should be hidden (e.g. v-for="user in users" v-if="shouldShowUsers"). In this case a better approach would be to move the v-if to a container element (e.g. ul or ol).
// Bad

<ul>
  <li
    v-for="user in users"
    v-if="user.isActive"
    :key="user.id"
  >
    {{ user.name }}
  </li>
</ul>

<ul>
  <li
    v-for="user in users"
    v-if="shouldShowUsers"
    :key="user.id"
  >
    {{ user.name }}
  </li>
</ul>

// Good

<ul>
  <li
    v-for="user in activeUsers"
    :key="user.id"
  >
    {{ user.name }}
  </li>
</ul>

<ul v-if="shouldShowUsers">
  <li
    v-for="user in users"
    :key="user.id"
  >
    {{ user.name }}
  </li>
</ul>

Base Components

Base components (a.k.a presentational, dumb, or pure components) that apply app-specific styling and conventions should all begin with a specific prefix, such as Base, App, or V.

// Bad

components/
|- MyButton.vue
|- VueTable.vue
|- Icon.vue

// Good

components/
|- BaseButton.vue
|- BaseTable.vue
|- BaseIcon.vue

components/
|- AppButton.vue
|- AppTable.vue
|- AppIcon.vue

components/
|- VButton.vue
|- VTable.vue
|- VIcon.vue

Single Instance Components

Components that should only ever have a single active instance should begin with The prefix.

This does not mean the component is only used in a single page, but it will only be used once per page. These components never accept any props, since they are specific to your app, not their context within your app. If you find the need to add props, it’s a good indication that this is actually a reusable component that is only used once per page for now.

// Bad

components/
|- Heading.vue
|- MySidebar.vue

// Good

components/
|- TheHeading.vue
|- TheSidebar.vue

Tightly Couple Components

Child Components that are tightly coupled with their parent should include the parent component name as a prefix.

If a component only makes sense in the context of a single parent component, that relationship should be evident in its name.

// Bad

components/
|- TodoList.vue
|- TodoItem.vue
|- TodoButton.vue

components/
|- SearchSidebar.vue
|- NavigationForSearchSidebar.vue

// Good

components/
|- TodoList.vue
|- TodoListItem.vue
|- TodoListItemButton.vue

components/
|- SearchSidebar.vue
|- SearchSidebarNavigation.vue

Order of Words in Component names

Component names should start with the highest-level (often most general) words and end with descriptive modifying words.

// Bad

components/
|- ClearSearchButton.vue
|- ExcludeFromSearchInput.vue
|- LaunchOnStartupCheckbox.vue
|- RunSearchButton.vue
|- SearchInput.vue
|- TermsCheckbox.vue


// Good

components/
|- SearchButtonClear.vue
|- SearchButtonRun.vue
|- SearchInputQuery.vue
|- SearchInputExcludeGlob.vue
|- SettingsCheckboxTerms.vue
|- SettingsCheckboxLaunchOnStartup.vue

Self-closing Components

Components with no conten should be self-closing in SFC, string templates, and JSX - but never in DOM templates.

// Bad

<!-- In single-file components, string templates, and JSX -->
<MyComponent></MyComponent>

<!-- In DOM templates -->
<my-component/>

// Good

<!-- In single-file components, string templates, and JSX -->
<MyComponent/>

<!-- In DOM templates -->
<my-component></my-component>

Prop name casing

Prop names should always use camelCase during declaration, but kebab-case in template and JSX.

We're following the conventions of each language. Withing Javascript, camelCase is more natural. Withing HTML, kebab-case is more natural.

// Bad

props: {
	'greeting-text': String
}
<WelcomeMessage greetingText='h1' />
// Good

props: {
	greetingText: String
}
<WelcomeMessage greeting-text='h1' />

Multi-attribute elements

Elements with multiple attributes should span multiple lines, with one attribute per line.

// Bad

<img src="logo.png" alt="Vue Logo">

<MyComponent foo="a" bar="b" baz="c" />

// Good

<img
  src="logo.png"
  alt="Vue Logo"
>

<MyComponent
  foo="a"
  bar="b"
  baz="c"
/>

Directive Shorthands

Directive shorthands (: for v-bind:, @ for v-on: and # for v-slot) should be used always or never.

// Bad

<input
  v-bind:value="newTodoText"
  :placeholder="newTodoInstructions"
>

<input
  v-on:input="onInput"
  @focus="onFocus"
>

<template v-slot:header>
  <h1>Here might be a page title</h1> 
</template>

<template #footer>
  <p>Here's some contact info</p>
</template>

// Good

<input
  :value="newTodoText"
  :placeholder="newTodoInstructions"
>

<input
  v-bind:value="newTodoText"
  v-bind:placeholder="newTodoInstructions"
>

<input
  @input="onInput"
  @focus="onFocus"
>

<input
  v-on:input="onInput"
  v-on:focus="onFocus"
>

<template v-slot:header>
  <h1>Here might be a page title</h1> 
</template>

<template v-slot:footer>
  <p>Here's some contact info</p>
</template>

<template #header>
  <h1>Here might be a page title</h1> 
</template>

<template #footer>
  <p>Here's some contact info</p>
</template>

SFC Element Ordering

Single-file components should always order <script>, <template>, and <style> tags consistently, with <style> last, because at least one of the other two is always necessary.

// Bad

<style>/* ... */</style>
<script>/* ... */</script>
<template>...</template>

<!-- ComponentA.vue -->
<script>/* ... */</script>
<template>...</template>
<style>/* ... */</style>

<!-- ComponentB.vue -->
<template>...</template>
<script>/* ... */</script>
<style>/* ... */</style>

// Good

<!-- ComponentA.vue -->
<script>/* ... */</script>
<template>...</template>
<style>/* ... */</style>

<!-- ComponentB.vue -->
<script>/* ... */</script>
<template>...</template>
<style>/* ... */</style>