Skip to content

Vue.js conventions

These conventions are based on the official Vue.js style guide. Where a choice is given between various options, this document will explicitly state which one to pick within the IxDF codebases.

Use multi-word component names

User component names should always be multi-word, except for root App components. This prevents conflicts with existing and future HTML elements, since all HTML elements are a single word.

Bad

html
<item />

Good

html
<todo-item />

Always add detailed prop definitions

Prop definitions should always be as detailed as possible.

Bad

js
const props = defineProps(['status']);

Good

js
// With TypeScript (recommended)
const props = defineProps<{
    status: string
}>()

// Without TypeScript
const props = defineProps({
    status: {
        type: String,
        required: true,
    }
})

Add a key when using v-for

A key with v-for is always required on components, in order to maintain internal component state down the subtree.

Bad

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

Good

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

Don't use v-if with v-for

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

Bad

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

Good

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

Component files should be named with PascalCase

Bad

components/
|- mycomponent.vue
|- myComponent.vue

Good

|- MyComponent.vue

Append "Base" prefix to any foundational components

Foundational components, such as buttons, tables or icons that get used within more specific components should all begin with the 'Base' prefix. This ensures they don't clash with HTML element names and also ensures that they are neatly organized together in the codebase.

Bad

components/
|- Button.vue
|- Table.vue
|- Icon.vue

Good

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

Tightly coupled component names

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. Since editors typically organize files alphabetically, this also keeps these related files next to each other.

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. This helps make searching for specific components easier and keeps the codebase neatly organised.

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

Component Name Casing

Components should always be PascalCase in both .vue and .js files. This has a few advantages over kebab-case:

  • Editors can autocomplete component names in templates, because PascalCase is also used in JavaScript.
  • <MyComponent> is more visually distinct from a single-word HTML element than <my-component>, because there are two character differences (the two capitals), rather than just one (a hyphen).
  • If you use any non-Vue custom elements in your templates, such as a web component, PascalCase ensures that your Vue components remain distinctly visible.

Bad

html
<my-component />

Good

html
<MyComponent />

Full-word component names

Don't use abbreviations in component names.

Bad

components/
|- SdSettings.vue
|- UProfOpts.vue

Good

components/
|- StudentDashboardSettings.vue
|- UserProfileOptions.vue

Prop name casing

Always use camelCase for prop declarations and within templates

js
const props = defineProps({
    greetingText: String,
});
html
<WelcomeMessage greetingText="hi" />

Multi-attribute elements

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

Bad

html
<img src="https://vuejs.org/images/logo.png" alt="Vue Logo" /> <MyComponent foo="a" bar="b" baz="c" />

Good

html
<img src="https://vuejs.org/images/logo.png" alt="Vue Logo" />

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

Simple expressions in templates

Component templates should only include simple expressions, with more complex expressions refactored into computed properties or methods.

Bad

js
{{
    fullName.split(' ').map((word) => {
    return word[0].toUpperCase() + word.slice(1)
    }).join```(' ')
}}

Good

js
<!-- In a template -->
{{ normalizedFullName }}

// The complex expression has been moved to a computed property
const normalizedFullName = computed(() =>
fullName.value
    .split(' ')
    .map((word) => word[0].toUpperCase() + word.slice(1))
    .join(' ')
)

Simple computed properties

Complex computed properties should be split into as many simpler properties as possible.

Bad

js
const price = computed(() => {
    const basePrice = manufactureCost.value / (1 - profitMargin.value);
    return basePrice - basePrice * (discountPercent.value || 0);
});

Good

js
const basePrice = computed(() => manufactureCost.value / (1 - profitMargin.value));

const discount = computed(() => basePrice.value * (discountPercent.value || 0));

const finalPrice = computed(() => basePrice.value - discount.value);

Quoted attribute values

Non-empty HTML attribute values should always be inside quotes.

Bad

html
<input type="text" /> <AppSidebar :style={width:sidebarWidth+'px'}>

Good

html
<input type="text" /> <AppSidebar :style="{ width: sidebarWidth + 'px' }"></AppSidebar>

Directive shorthands

Within the IxDF codebases, directive shorthands (: for v-bind:, @ for v-on: and # for v-slot) should always be used. The rule is to either always use directive shorthands, or never - don't mix and match. We've chosen to use them.

Bad

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

<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>

Good

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

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

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

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

Single-file component top-level element order

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

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

Good

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