Skip to content

Formatting Reference

This document serves as the definitive source for all formatting rules and conventions when working on Vue.js projects. It consolidates all formatting standards into a single, comprehensive reference that can be used by developers, AI assistants, and tooling.

Last Updated: January 2025
Scope: Vue.js 3.5+ with Composition API, TypeScript


Table of Contents

  1. Vue API Preference
  2. Script Section Formatting
  3. Template Section Formatting
  4. Naming Conventions
  5. Code Style Rules
  6. Complete Examples

Vue API Preference

Always use the Composition API with <script setup> syntax.

We use Vue 3.5+ with the Composition API. The Options API should not be used for new code.


Script Section Formatting

Import Ordering

Imports must follow this exact sequence with clear separation between groups:

  1. Imports from 'vue' (always first)
  2. Other imports from node_modules (pinia, axios, etc.)
  3. Internal dependencies (in ASC alphabetical order):
    • @Composables
    • @Helpers
    • @Types
  4. UI components (in ASC alphabetical order):
    • @Atoms
    • @Molecules
    • @Organisms
    • @Templates
    • @Layouts
  5. Modules (in ASC alphabetical order)
  6. Internal dependencies from current domain (relative imports):
    • ./styles
    • ./components
    • ./providers

Example: Import Ordering

👎🏼 BAD

vue
<script setup lang="ts">
import { AuthForm } from '@Auth';
import { defineStore } from 'pinia';
import { hidePrompt, showPrompt } from '@Helpers';
import InternalComponent from '../../components';
import { computed, useAttrs } from 'vue';
import { AButton } from '@Atoms';
</script>

👍🏼 GOOD

vue
<script setup lang="ts">
import { computed, useAttrs } from 'vue';
import { defineStore } from 'pinia';
import { hidePrompt, showPrompt } from '@Helpers';
import { AButton } from '@Atoms';
import { AuthForm } from '@Auth';
import InternalComponent from '../../components';
</script>

Sorting Rules

Everything must be sorted in ASC (ascending/alphabetical) order.

Use your IDE's sort shortcut: F9 (don't sort manually)

One-Liners

👎🏼 BAD

vue
<script setup lang="ts">
const someRef = ref();
const word = ref();
const other = ref();
</script>

👍🏼 GOOD

vue
<script setup lang="ts">
const other = ref();
const someRef = ref();
const word = ref();
</script>

Mixed Content

When mixing one-liners and multi-liners, sort one-liners first, then multi-liners:

👎🏼 BAD

vue
<script setup lang="ts">
const someRef = ref();
const word = ref();
const multipleLiner = ref({
  something: 1
})
const other = ref();
</script>

👍🏼 GOOD

vue
<script setup lang="ts">
const other = ref();
const someRef = ref();
const word = ref();
const multipleLiner = ref({
  something: 1
})
</script>

Code Structure Order

After dependency imports, code must be organized in this exact order:

  1. Directives
  2. Macros (in ASC order: defineEmits, defineProps, etc.)
  3. Stores
  4. Helpers
  5. Composables
  6. Reactivity (in this order: defineModel, ref, readonly, reactive, computed, injected)
  7. Provide
  8. Watchers
  9. Lifecycle
  10. Methods

1. Directives

  • Must be named: vMyDirectiveName
  • Only use directives for low-level DOM access on plain elements
  • Not for stateful logic (use composables instead)

2. Macros

Macros must be in ASC alphabetical order:

👎🏼 BAD

vue
<script setup lang="ts">
defineProps<{
  prop1: string;
  prop2: number;
}>();

defineEmits<{
  onSubmit: [input: string]
}>();
</script>

👍🏼 GOOD

vue
<script setup lang="ts">
defineEmits<{
  onSubmit: [input: string]
}>();

defineProps<{
  prop1: string;
  prop2: number;
}>();
</script>

3. Stores

Store usage (e.g., const userStore = useUserStore())

4. Helpers

Helper function calls

5. Composables

Composable function calls

6. Reactivity

In this specific order:

  • defineModel()
  • ref
  • readonly
  • reactive
  • computed
  • injected

7. Provide

provide() calls

8. Watchers

Watchers must be one-liners that call event functions, not inline arrow functions.

The event function should be declared in the Methods section (section 10).

👎🏼 BAD

vue
<script setup lang="ts">
watch(someRef, (newVal) => {
  console.log('bad example', newVal);
  doSomething(newVal);
});

watchEffect(() => {
  console.log('bad example');
});
</script>

👍🏼 GOOD

vue
<script setup lang="ts">
watch(someRef, onRefUpdate);

watchEffect(onRefUpdate);

// Event function declared in Methods section
function onRefUpdate() {
  console.log('good example');
  doSomething(someRef.value);
}
</script>

9. Lifecycle

Lifecycle hooks must be one-liners that call event functions, not inline arrow functions.

The event function should be declared in the Methods section (section 10).

👎🏼 BAD

vue
<script setup lang="ts">
onMounted(() => {
  console.log('bad example');
  initializeComponent();
});

onUnmounted(() => {
  cleanup();
});
</script>

👍🏼 GOOD

vue
<script setup lang="ts">
onMounted(onComponentMount);

onUnmounted(onComponentUnmount);

// Event functions declared in Methods section
function onComponentMount() {
  console.log('good example');
  initializeComponent();
}

function onComponentUnmount() {
  cleanup();
}
</script>

10. Methods

Always use function declarations, NOT arrow functions.

This improves code legibility by creating a clear visual distinction between functions and other variables (ref, reactive, props, emits, etc.).

Important: Event functions used by watchers and lifecycle hooks should also be declared here as function declarations.

👎🏼 BAD

vue
<script setup lang="ts">
// using ES6+ arrow functions
const emits = defineEmits([]);
const props = defineProps({});

const myRef = ref(1);
const myComputedProperty = () => myRef.value * -1;

const myMethod = (param) => {
  console.log(param);
};

// No syntax difference from emits, props,
// myRef, myComputedProperty to myMethod :/
</script>

👍🏼 GOOD

vue
<script setup lang="ts">
const emits = defineEmits([]);
const props = defineProps({});

const myRef = ref(1);
const myComputedProperty = () => myRef.value * -1;

function myMethod(param) {
  console.log(param);
}

// Clear visual distinction! Functions stand out from variables.
</script>

Note: Computed properties can use arrow functions, but regular methods must use function declarations.


Template Section Formatting

Spacing Between Elements

Always insert a blank line between sibling elements when a parent has two or more direct children.

This applies recursively to all nested levels.

👎🏼 BAD

vue
<template>
  <section>
    <h2>Title</h2>
    <p>Some content here.</p>
    <div>
      <span>Item 1</span>
      <span>Item 2</span>
    </div>
  </section>
</template>

👍🏼 GOOD

vue
<template>
  <section>
    <h2>Title</h2>

    <p>Some content here.</p>

    <div>
      <span>Item 1</span>

      <span>Item 2</span>
    </div>
  </section>
</template>

Self-Closing Tags

  • Always use explicit closing tags, even for optional ones (<li>, <p>)
  • Self-closing elements (<img>, <br>, <hr>, <input>) must end with />

👎🏼 BAD

vue
<template>
  <ul>
    <li>Item 1
    <li>Item 2
  </ul>
  <img src="logo.png">
  <input type="text" name="email">
</template>

👍🏼 GOOD

vue
<template>
  <ul>
    <li>Item 1</li>

    <li>Item 2</li>
  </ul>

  <img src="logo.png" alt="" />

  <input type="text" name="email" />
</template>

Attribute Order

Attributes must follow this exact order (each group in ASC alphabetical order):

  1. Directives:
    • v-if (always first)
    • All other v-* directives in ASC alphabetical order
  2. Simple attributes (e.g., id, class, type, name) in ASC alphabetical order
  3. Bound attributes (:prop) in ASC alphabetical order
  4. Events (@event) in ASC alphabetical order

👎🏼 BAD

vue
<template>
  <MyComponent
    submit
    v-model="model"
    :otherProp="prop"
    @onSomeEvent="onSomeEvent"
    v-if="!loading"
    outlined
    class="w-full"
    @click="onClick"
    :label="myCustomLabel"
  />
</template>

👍🏼 GOOD

vue
<template>
  <MyComponent
    v-if="!loading"
    v-model="model"
    submit
    outlined
    class="w-full"
    :label="myCustomLabel"
    :otherProp="prop"
    @click="onClick"
    @onSomeEvent="onSomeEvent"
  />
</template>

Naming Conventions

Component Prefixes

Components follow Atomic Design methodology with prefixes:

  • A prefix for Atoms (e.g., AButton, AText)
  • M prefix for Molecules (e.g., MFormField, MCard)
  • O prefix for Organisms (e.g., OHeader, OForm)
  • Templates use suffix Template (e.g., FormTemplate)
  • Layouts use suffix Layout with The prefix (e.g., TheDefaultLayout, TheAuthLayout)

File Suffixes

Use suffixes to identify file scope:

  • example.constant.ts
  • example.helper.ts
  • example.plugin.ts
  • example.store.ts
  • example.type.ts
  • example.provider.ts
  • example.service.ts

Directive Naming

Custom directives must be named: vMyDirectiveName

Example: vFocus, vClickOutside, vTooltip


Code Style Rules

Function Declarations

Always use function declarations for methods, not arrow functions.

This creates clear visual distinction between functions and variables.

Reference: Even Evan You (Vue creator) uses function declarations in Vue core, and it's suggested in the official Vue docs.

Computed Properties

Computed properties can use arrow functions:

vue
<script setup lang="ts">
const count = ref(0);

const doubled = computed(() => count.value * 2);
</script>

Complete Examples

Complete Component Example

Here's a complete Vue component example following all formatting rules:

👍🏼 GOOD - Complete Example

vue
<script setup lang="ts">
// 1. Imports from 'vue'
import { computed, onMounted, ref, watch } from 'vue';

// 2. Imports from node_modules
import { defineStore } from 'pinia';

// 3. Internal dependencies (ASC order)
import { formatDate } from '@Helpers';
import { useAuth } from '@Composables';
import type { User } from '@Types';

// 4. UI components (ASC order)
import { AButton } from '@Atoms';
import { MCard } from '@Molecules';
import { OHeader } from '@Organisms';

// 5. Modules (ASC order)
import { AuthForm } from '@AuthModule';

// 6. Local imports
import './styles.css';
import LocalComponent from './components/LocalComponent.vue';

// 7. Macros (ASC order)
defineEmits<{
  submit: [data: User]
}>();

defineProps<{
  title: string;
  userId: number;
}>();

// 8. Stores
const userStore = useUserStore();

// 9. Helpers
const formattedTitle = formatTitle(props.title);

// 10. Composables
const { isAuthenticated } = useAuth();

// 11. Reactivity (in order: defineModel, ref, readonly, reactive, computed, injected)
const model = defineModel<string>();
const count = ref(0);
const readonlyData = readonly(someData);
const state = reactive({ name: '', email: '' });
const doubled = computed(() => count.value * 2);
const injectedValue = inject('key');

// 12. Provide
provide('theme', 'dark');

// 13. Watchers
watch(count, onCountUpdate);

// 14. Lifecycle
onMounted(onComponentMount);

// 15. Methods (function declarations, not arrow functions)
function handleSubmit() {
  emits('submit', userData.value);
}

function handleClick() {
  count.value++;
}

function onCountUpdate(newVal: number) {
  console.log(newVal);
}

function onComponentMount() {
  console.log('Mounted');
}
</script>

<template>
  <section>
    <OHeader
      v-if="isAuthenticated"
      :title="formattedTitle"
      class="header"
      @close="handleClose"
    />

    <MCard>
      <AButton
        v-if="count > 0"
        class="btn-primary"
        :disabled="loading"
        @click="handleClick"
      >
        Click me
      </AButton>

      <AuthForm
        v-model="model"
        :userId="userId"
        @submit="handleSubmit"
      />
    </MCard>
  </section>
</template>

Import Grouping Example

👍🏼 GOOD - Clear Import Groups

vue
<script setup lang="ts">
// Vue imports
import { computed, ref } from 'vue';

// Node modules
import { defineStore } from 'pinia';
import axios from 'axios';

// Internal dependencies
import { formatCurrency } from '@Helpers';
import { useUser } from '@Composables';
import type { Product } from '@Types';

// UI components
import { AButton } from '@Atoms';
import { MCard } from '@Molecules';

// Modules
import { ProductService } from '@ProductModule';

// Local imports
import './ProductCard.css';
</script>

Quick Reference Checklist

When formatting a Vue component, ensure:

  • [ ] Imports are in correct order (vue → node_modules → internal → UI → modules → local)
  • [ ] All imports are sorted alphabetically (ASC)
  • [ ] Script code follows structure order (Directives → Macros → Stores → Helpers → Composables → Reactivity → Provide → Watchers → Lifecycle → Methods)
  • [ ] Macros are in ASC order (defineEmits before defineProps)
  • [ ] Reactivity follows order (defineModel → ref → readonly → reactive → computed → injected)
  • [ ] Watchers are one-liners calling event functions (not inline arrow functions)
  • [ ] Lifecycle hooks are one-liners calling event functions (not inline arrow functions)
  • [ ] Methods use function declarations (not arrow functions)
  • [ ] Event functions for watchers/lifecycle are declared in Methods section
  • [ ] Template has blank lines between sibling elements
  • [ ] All tags are explicitly closed (including <li>, <p>)
  • [ ] Self-closing tags end with />
  • [ ] Attributes are in correct order (v-if → other v-* → simple → :bound → @events)
  • [ ] All attributes within each group are alphabetically sorted
  • [ ] Component names use correct prefixes (A, M, O) or suffixes (Template, Layout)

Additional Resources

Machine-Optimized Version

For tooling, ESLint plugins, IDE extensions, and programmatic access, a machine-optimized JSON version is available:

  • formatting-reference.json - Structured JSON format with all formatting rules

This JSON file can be used by:

  • ESLint plugins for import ordering and code structure validation
  • IDE extensions for auto-formatting
  • AI assistants for rule parsing
  • Custom tooling and scripts

Note: This reference consolidates rules from conventions.md, html-templates.md, and implementation.md. For detailed explanations and context, refer to those documents.