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 thev-if
to a container element (e.g.ul
orol
).
// 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>