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
- Vue API Preference
- Script Section Formatting
- Template Section Formatting
- Naming Conventions
- Code Style Rules
- 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:
- Imports from 'vue' (always first)
- Other imports from node_modules (pinia, axios, etc.)
- Internal dependencies (in ASC alphabetical order):
@Composables@Helpers@Types
- UI components (in ASC alphabetical order):
@Atoms@Molecules@Organisms@Templates@Layouts
- Modules (in ASC alphabetical order)
- Internal dependencies from current domain (relative imports):
./styles./components./providers
Example: Import Ordering
👎🏼 BAD
<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
<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
<script setup lang="ts">
const someRef = ref();
const word = ref();
const other = ref();
</script>👍🏼 GOOD
<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
<script setup lang="ts">
const someRef = ref();
const word = ref();
const multipleLiner = ref({
something: 1
})
const other = ref();
</script>👍🏼 GOOD
<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:
- Directives
- Macros (in ASC order: defineEmits, defineProps, etc.)
- Stores
- Helpers
- Composables
- Reactivity (in this order: defineModel, ref, readonly, reactive, computed, injected)
- Provide
- Watchers
- Lifecycle
- 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
<script setup lang="ts">
defineProps<{
prop1: string;
prop2: number;
}>();
defineEmits<{
onSubmit: [input: string]
}>();
</script>👍🏼 GOOD
<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()refreadonlyreactivecomputedinjected
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
<script setup lang="ts">
watch(someRef, (newVal) => {
console.log('bad example', newVal);
doSomething(newVal);
});
watchEffect(() => {
console.log('bad example');
});
</script>👍🏼 GOOD
<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
<script setup lang="ts">
onMounted(() => {
console.log('bad example');
initializeComponent();
});
onUnmounted(() => {
cleanup();
});
</script>👍🏼 GOOD
<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
<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
<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
<template>
<section>
<h2>Title</h2>
<p>Some content here.</p>
<div>
<span>Item 1</span>
<span>Item 2</span>
</div>
</section>
</template>👍🏼 GOOD
<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
<template>
<ul>
<li>Item 1
<li>Item 2
</ul>
<img src="logo.png">
<input type="text" name="email">
</template>👍🏼 GOOD
<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):
- Directives:
v-if(always first)- All other
v-*directives in ASC alphabetical order
- Simple attributes (e.g.,
id,class,type,name) in ASC alphabetical order - Bound attributes (
:prop) in ASC alphabetical order - Events (
@event) in ASC alphabetical order
👎🏼 BAD
<template>
<MyComponent
submit
v-model="model"
:otherProp="prop"
@onSomeEvent="onSomeEvent"
v-if="!loading"
outlined
class="w-full"
@click="onClick"
:label="myCustomLabel"
/>
</template>👍🏼 GOOD
<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:
Aprefix for Atoms (e.g.,AButton,AText)Mprefix for Molecules (e.g.,MFormField,MCard)Oprefix for Organisms (e.g.,OHeader,OForm)- Templates use suffix
Template(e.g.,FormTemplate) - Layouts use suffix
LayoutwithTheprefix (e.g.,TheDefaultLayout,TheAuthLayout)
File Suffixes
Use suffixes to identify file scope:
example.constant.tsexample.helper.tsexample.plugin.tsexample.store.tsexample.type.tsexample.provider.tsexample.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:
<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
<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
<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
- Conventions - Detailed conventions documentation
- HTML Templates - Template-specific formatting rules
- Implementation - Architecture and folder structure
- Vue.js Style Guide - Official Vue.js style guide
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.