Stack conventions
Vue 3
Vue 3 applications with the Composition API and `<script setup>`, Pinia for state, tested with Vitest and Vue Test Utils.
Vue 3 applications with the Composition API and <script setup>, Pinia for state, tested with Vitest and Vue Test Utils.
Used for: vue.
Principles
- Write new components with the Composition API in
<script setup>(withlang="ts"when the repo uses TypeScript); keep Options API files consistent if the repo still uses them. - Props down, events up: never mutate a prop; emit an event or use
defineModelfor two-way binding. - Declare props and emits with types (
defineProps<...>(),defineEmits<...>()) and give optional props defaults. - Use
computedfor derived state and keepwatch/watchEffectfor side effects only. - Know reactivity's rules:
reffor primitives and replaceable values, do not destructure reactive objects withouttoRefs, and access.valuein script. - Extract reusable stateful logic into composables (
useXxx) that return refs and clean up after themselves. - Keep shared application state in Pinia stores; keep component-local state local.
- Give every
v-fora stable:keyfrom the data, and never combinev-ifwithv-foron the same element. - Build accessible templates: semantic elements, labelled controls, keyboard support.
Conventions
Accessibility
- Use native
button,a,label,navand heading hierarchy; avoid click handlers on non-interactive elements. - Bind every form control to a label (
<label for>or wrapping label); providealtfor images. - Manage focus for dialogs and route changes; follow WAI-ARIA Authoring Practices for custom widgets.
Concurrency
- Guard watchers that fetch against out-of-order responses using the
onCleanup/onWatcherCleanupcallback to cancel stale requests.
Dependencies
- Use the repo's UI library and design tokens; do not add a second component library.
- Use Pinia for new state management (Vuex is in maintenance).
- Commit the lockfile and keep
npm auditclean of high-severity advisories for production dependencies.
Documentation
- Typed props and emits document the component contract; add short comments for non-obvious slots and events.
- Document props and emitted events with types and short comments on shared components; keep stories current where the repo uses Storybook.
Errors
- Handle loading, empty and error states explicitly in async views; use
onErrorCapturedor an app-levelerrorHandlerfor reporting. - Show loading, empty and error states for every async view, and report unexpected errors from
app.config.errorHandleroronErrorCaptured.
Logging
- No
console.login committed components; route errors through the app's error reporting. - No
console.login committed components; send errors to the repo's monitoring client with the component and action as context, never tokens or personal data.
Naming
- Component names are multi-word PascalCase (
UserCard.vue), never single words that clash with HTML elements. - Base components share a prefix (
BaseButton,AppIcon); tightly coupled children use the parent name as prefix. - Props are camelCase in script and kebab-case in templates; events are camelCase in
defineEmitsand listened to as kebab-case.
Performance
- Prefer
computed(cached) over methods in templates for derived values. - Use
shallowRef/markRawfor large immutable data or third-party instances that should not be made deeply reactive. - Lazy-load routes with dynamic imports; use
v-showfor frequently toggled content andv-iffor rarely shown content.
Security
- Avoid
v-htmlwith untrusted content; sanitise when HTML rendering is unavoidable. - Never build URLs for
href/srcfrom user input without allowing only safe schemes. - Keep secrets out of the client bundle; only
VITE_variables are exposed by Vite and all of them are public. - Validate data from route params, query strings and APIs before use, and never compile templates from user input (template injection).
Structure
- Single-file components ordered
<script setup>,<template>,<style>(or the repo's established order). - Composables in
composables/, stores instores/, one store per domain concept. - Use scoped styles or CSS modules; component styles must not leak globally.
Testing
- Test components with Vitest + Vue Test Utils or Testing Library for Vue; query by role and accessible name.
- Use
createTestingPinia(or a fresh Pinia per test) for components that use stores. - Assert rendered output and emitted events, not internal reactive state.
- Unit-test composables and stores directly; integration-test components with their children, router and a mocked API (MSW) where the change spans them.
- Name spec files after the component (
InvoiceForm.spec.ts) and test titles after the behaviour the user sees. - Done means tested: new or changed behaviour ships with tests in the same change, and a fixed bug gets a regression test that fails without the fix.
Review checklist
- Does the component use
<script setup>with typeddefineProps/defineEmits(or match the repo's established style)? - Are props never mutated?
- Is derived state a
computedrather than a watcher writing to a ref? - Is reactivity preserved (no lost reactivity from destructuring)?
- Do all
v-forloops have stable keys and nov-ifon the same element? - Is shared state in Pinia and local state kept local?
- Do composables clean up listeners, timers and subscriptions?
- Is
v-htmlabsent or used only with sanitised content? - Are controls labelled and interactive elements native?
- Do tests query by role/label and assert emitted events?
Quality checks (run when your repository has the tool configured)
| Check | Kind | Tool | Blocks the pull request |
|---|---|---|---|
eslint |
lint | eslint | no |
vue-tsc |
typecheck | vue-tsc | no |
prettier |
format | prettier | no |
npm-audit |
security | npm audit | no |
gitleaks |
security | gitleaks | no |