--- paths: - "**/*.vue" - "**/components/**/*.ts" - "**/composables/**/*.ts" - "**/stores/**/*.ts" - "**/pages/**/*.vue" --- # Vue Patterns > This file extends [typescript/patterns.md](../typescript/patterns.md) and [common/patterns.md](../common/patterns.md) with Vue-specific architecture patterns. For composable rules see [hooks.md](./hooks.md). ## Component Design Principles ### Presentational vs Container Split large views into container (data-fetching, state, orchestration) and presentational (props-in, events-out) components. ```vue ``` ### Provide / Inject Use for dependency injection (not state management). Ideal for: theme, locale, configuration, plugin API surfaces. ```ts // Provider — in a parent or plugin const theme = ref("light"); provide("theme", readonly(theme)); // Consumer — in any descendant const theme = inject>("theme"); ``` - Always use `readonly()` when providing to prevent child mutations. - Use `Symbol` keys for injection to avoid name collisions. - Document the injection key type with a shared constant. ### Scoped Slots Use scoped slots when a child component owns data but the parent controls rendering. ```vue ``` ## State Management ### Decision Tree 1. **Component-local**: `ref()` / `reactive()` inside the component 2. **Shared between parent + few children**: Lift to parent, pass via props + emits 3. **Shared across distant branches, infrequent updates**: `provide` / `inject` 4. **Global, shared, complex**: Pinia store 5. **Server-derived data**: Composables wrapping `fetch` / `useFetch` (Nuxt) / TanStack Query (Vue Query) ### Pinia Patterns ```ts // stores/useUserStore.ts import { defineStore } from "pinia"; import { ref, computed } from "vue"; import { getUser, updateUser } from "@/api/user"; export const useUserStore = defineStore("user", () => { // State const currentUser = ref(null); const isLoading = ref(false); const error = ref(null); // Getters (computed) const isLoggedIn = computed(() => currentUser.value !== null); const displayName = computed(() => currentUser.value ? currentUser.value.name : "Guest" ); // Actions async function fetchUser(id: string) { isLoading.value = true; error.value = null; try { currentUser.value = await getUser(id); } catch (e) { error.value = e as Error; } finally { isLoading.value = false; } } return { currentUser, isLoading, error, isLoggedIn, displayName, fetchUser }; }); ``` - Prefer **Setup Store** syntax (Composition API) over Options Store. - Store actions are the ONLY place to mutate state — no direct `store.$patch` in components for complex logic. - Every async action must handle loading, success, and error states. - Keep stores focused on one domain — split auth, user, cart, etc. into separate stores. ## Vue Router Patterns ### Navigation Guards ```ts // Global guard router.beforeEach((to, from) => { const store = useUserStore(); if (to.meta.requiresAuth && !store.isLoggedIn) { return { name: "login", query: { redirect: to.fullPath } }; } }); ``` - Always provide a redirect path so the user returns to their intended destination after login. - Route guards should not have side effects beyond navigation decisions. - Use `beforeEnter` on routes for route-specific checks; `beforeEach` for global ones. ### Lazy Loading ```ts const routes = [ { path: "/dashboard", component: () => import("@/pages/Dashboard.vue"), // lazy }, { path: "/settings", component: () => import("@/pages/Settings.vue"), // Provide loading/error components meta: { __loadingComponent: LoadingSpinner, __errorComponent: ErrorView, }, }, ]; ``` ### Route Params inside Same Component ```vue ``` ## List Rendering ### `v-for` with Stable Keys ```vue
{{ item.name }}
{{ item.name }}
``` ## Forms ### v-model Patterns ```vue ``` ### Form Validation For non-trivial forms, use a vetted library: - **VeeValidate** — declarative validation rules, form-level context. - **FormKit** — schema-based forms with built-in validation. - **Custom with composable** — for simple cases only. ```ts // Anti-pattern: manual validation in component const errors = ref([]); function submit() { errors.value = []; if (!email.value.includes("@")) errors.value.push("Invalid email"); // ... fragile, not reusable, no i18n } ``` ### Event Handling ```vue
Link ``` ## Scoped CSS ```vue ``` - Always use ` ``` ## Teleport Use `` for modals, tooltips, notifications — content that must escape parent overflow/z-index constraints. ```vue ``` **Vue 3.5+**: `` supports `defer` prop for deferred mounting. This allows teleporting to a target element that is rendered later in the same render cycle: ```vue

Teleported content

``` ## KeepAlive Cache component state when toggling between views. Always set `:max` to control memory. ```vue ``` ## `useId()` (Vue 3.5+) Generate unique, SSR-stable IDs for form elements and accessibility attributes: ```vue ``` - IDs are unique per application instance and stable across server/client rendering. - Prefer `useId()` over manual ID generation to avoid SSR hydration mismatches. ## `data-allow-mismatch` (Vue 3.5+) Suppress unavoidable server/client value mismatch warnings: ```vue {{ date.toLocaleString() }} {{ clientOnlyValue }} ``` Allowed types: `text`, `children`, `class`, `style`, `attribute`. ## Suspense (Experimental / Vue 3.3+) ```vue ``` ## Dynamic Components ```vue ``` ## Out of Scope (Pointer Sections) ### Nuxt-specific Patterns Nuxt auto-imports, server routes, Nitro, modules, and build configuration are treated as a separate framework concern. When adding deep Nuxt-specific patterns, see `skills/nuxt4-patterns/` if present, or propose a dedicated `rules/nuxt/` track. ### Vue 2 / Migration Options API, `Vue.extend`, `Vue.directive`, filters, and event bus patterns belong to migration documentation. New code should target Vue 3 Composition API. ## Skill Reference For Vue deep dives see `skills/vue-patterns/SKILL.md`. For cross-framework frontend concerns see `skills/frontend-patterns/SKILL.md`. For accessibility see `skills/accessibility/SKILL.md`.