Vue 3
Vue 3 patterns — Composition API + <script setup lang="ts">, Pinia, Pinia
Colada, file-based routing, and performance. Single entrypoint routing to focused references.
Use npx chub search vue for relevant documentation packages within the Context Hub.
When to Use
Auto-activate on: .vue files, Vue 3 Composition API, <script setup>, Pinia stores,
Pinia Colada queries/mutations, Vue Router file-based routes, composables.
Explicit: /mk:vue [concern]
Deep best practices: for a thorough best-practices review/recommendations pass or the
full ordered workflow — built-in components (Teleport/Suspense/KeepAlive/Transition),
animation techniques, optional features, and the performance pass — use mk:vue-best-practices.
This skill stays the everyday quick-reference.
Do NOT invoke for: deep best-practices review/workflow (use mk:vue-best-practices),
TypeScript fundamentals (use mk:typescript), React (use mk:react-patterns), Angular (use
mk:angular), visual design (use mk:frontend-design), testing (use mk:testing / mk:qa;
Vue test design/review → mk:vue-testing-best-practices).
Core Rules (always apply)
Follow the repository's established conventions first. When it has no convention, use
these defaults; project requirements may override them. Everything else is in the references.
<script setup lang="ts"> only — never Options API, never defineComponent() wrapper.
type over interface for defining object/prop shapes; keep types alongside the code.
- Arrow functions for methods and callbacks.
- Named exports over default exports — except composables, which use
export default.
- PascalCase component names, kebab-case file names (
UserProfile in
user-profile.vue); composables are camelCase with use prefix (useAuth).
- Name general → specific:
SearchButtonClear.vue, not ClearSearchButton.vue.
- TailwindCSS classes, not manual CSS; never hard-code colors — use the Tailwind color system.
- Comments explain why, not what.
ref() for primitives, reactive() only for complex objects; computed() for derived state.
- Never
v-html with user-provided content — XSS vector (see mk: security rules).
Data Layer (conditional default)
When the project depends on @pinia/colada, it is the preferred async/data-fetching layer
— see references/pinia-colada.md. Otherwise use plain Pinia stores + composables for state.
Pinia stores hold global UI/app state; data fetching belongs in Pinia Colada when present.
When to Read Each Reference
Read one level deep from this file. Read multiple when a task spans topics.
| Task involves |
Read |
Components, props/emits, slots, defineModel, naming, templates |
references/components.md |
Composables (use*), shared reactive logic |
references/composables.md |
ref/reactive/computed/watch, toRefs, performance |
references/reactivity-performance.md |
Global state with Pinia setup stores, storeToRefs |
references/state-pinia.md |
Pinia Colada core: keys, defineQueryOptions, useQuery, mutations |
references/pinia-colada.md |
| Optimistic updates, infinite/paginated queries, SSR, cancellation |
references/pinia-colada-advanced.md |
| Pinia Colada plugins (retry, delay, auto-refetch, persister, hooks) |
references/pinia-colada-plugins.md |
Direct cache access: getQueryData/setQueryData/invalidateQueries |
references/pinia-colada-cache.md |
File-based routing, route groups, params, definePage, typed router |
references/routing-pages.md |
| Project stack, structure, commands, conventions, docs research |
references/project-standards.md |
3.4/3.5 core APIs: useId, nextTick, flush timing, watch once/deep, onWatcherCleanup |
references/core-new-apis.md |
<script setup> compiler macros — defineOptions, withDefaults, generic components |
references/script-setup-macros.md |
Advanced reactivity: effectScope, customRef, triggerRef, markRaw, shallowReadonly |
references/advanced-patterns.md |
Gotchas
- Destructuring
reactive() loses reactivity — const { count } = reactive({ count: 0 })
makes count a plain number; keep the reactive object intact, or use toRefs(), or ref().
storeToRefs() required when destructuring Pinia state — const { user } = useAuthStore()
gives a non-reactive snapshot; use const { user } = storeToRefs(useAuthStore()). Methods
are destructured directly from the store (not through storeToRefs).
- Parent
ref access needs defineExpose() — childRef.value.method() returns undefined
unless the child <script setup> explicitly defineExpose({ method }).
- Use
:slotted() not :deep() for slotted content — slot content comes from the parent
scope, so :deep(.child-class) in a scoped style block does not match it; use :slotted(.child-class).
watchEffect cleanup race on fast re-renders — register cleanup to cancel async work:
watchEffect((onCleanup) => { onCleanup(() => controller.abort()) }), or a fast prop change
starts a second effect before the first resolves and writes stale state.
- Pinia store not hydrated in SSR — calling
useMyStore() outside a component setup context
(e.g. a top-level module) creates an instance disconnected from the SSR app; call stores inside
setup() or pass the pinia instance explicitly: useMyStore(pinia).
- Query keys must depend on ALL variables used in the query function — a key that omits a
variable serves stale cached data when that variable changes (Pinia Colada).
- Dynamic query keys need a getter, not a plain value — pass
key: () => [...] so the key
re-evaluates reactively; a plain array snapshot never updates.
- Prefer
refresh() over refetch() — refresh() reuses in-flight requests and respects
staleTime; refetch() always forces a new fetch.
- Avoid
index.vue route files — use a named group like pages/(home).vue for a meaningful
route name.
- Use explicit route param names —
userId not id, postSlug not slug, for type-safe,
self-documenting routes.
- Watching a destructured prop needs a getter —
const { count } = defineProps() stays reactive
in templates, but watch(count, ...) watches a snapshot; use watch(() => count, ...)
(references/core-new-apis.md).
onWatcherCleanup() must be called synchronously — invoking it after an await throws, since
the active-watcher context is gone; use the onCleanup callback param for post-await cancellation
(references/core-new-apis.md).
Anti-Patterns
| Don't |
Do Instead |
Options API (data(), methods:) |
Composition API <script setup> |
this.$store / Vuex |
Pinia with setup store syntax |
| Direct store state destructuring |
storeToRefs(useMyStore()) |
v-html with dynamic content |
v-text or sanitized rendering |
reactive() for primitives |
ref() for primitives |
| Watchers when computed works |
computed() for derived state |
| Global event bus |
provide/inject or Pinia |
defineComponent() wrapper |
<script setup> directly |
Workflow Integration
Auto-activates during Build when a Vue project is detected (.vue files, vue in
package.json). Loaded by the developer agent alongside mk:typescript.
1---2name: mk-vue3description: Use when writing, reviewing, or refactoring Vue 3 code — components, composables, reactivity, Pinia state, Pinia Colada data-fetching, file-based routing, and forms. Targets Composition API + <script setup> + TypeScript. Auto-activates on .vue files. For deep best-practices review/recommendations or the full ordered workflow (built-in components, animations, performance pass), use mk:vue-best-practices.4---56# Vue 378Vue 3 patterns — Composition API + `<script setup lang="ts">`, Pinia, Pinia9Colada, file-based routing, and performance. Single entrypoint routing to focused references.1011> Use `npx chub search vue` for relevant documentation packages within the Context Hub.1213## When to Use1415**Auto-activate on:** `.vue` files, Vue 3 Composition API, `<script setup>`, Pinia stores,16Pinia Colada queries/mutations, Vue Router file-based routes, composables.1718**Explicit:** `/mk:vue [concern]`1920**Deep best practices:** for a thorough best-practices **review/recommendations** pass or the21full ordered workflow — built-in components (Teleport/Suspense/KeepAlive/Transition),22animation techniques, optional features, and the performance pass — use `mk:vue-best-practices`.23This skill stays the everyday quick-reference.2425**Do NOT invoke for:** deep best-practices review/workflow (use `mk:vue-best-practices`),26TypeScript fundamentals (use `mk:typescript`), React (use `mk:react-patterns`), Angular (use27`mk:angular`), visual design (use `mk:frontend-design`), testing (use `mk:testing` / `mk:qa`;28Vue test design/review → `mk:vue-testing-best-practices`).2930## Core Rules (always apply)3132Follow the repository's established conventions first. When it has no convention, use33these defaults; project requirements may override them. Everything else is in the references.3435- **`<script setup lang="ts">` only** — never Options API, never `defineComponent()` wrapper.36- **`type` over `interface`** for defining object/prop shapes; keep types alongside the code.37- **Arrow functions for methods and callbacks.**38- **Named exports over default exports — except composables, which use `export default`.**39- **PascalCase component names, kebab-case file names** (`UserProfile` in40 `user-profile.vue`); composables are `camelCase` with `use` prefix (`useAuth`).41- **Name general → specific:** `SearchButtonClear.vue`, not `ClearSearchButton.vue`.42- **TailwindCSS classes, not manual CSS;** never hard-code colors — use the Tailwind color system.43- **Comments explain _why_, not _what_.**44- **`ref()` for primitives, `reactive()` only for complex objects;** `computed()` for derived state.45- **Never `v-html` with user-provided content** — XSS vector (see `mk:` security rules).4647## Data Layer (conditional default)4849When the project depends on `@pinia/colada`, it is the **preferred async/data-fetching layer**50— see `references/pinia-colada.md`. Otherwise use plain Pinia stores + composables for state.51Pinia stores hold global UI/app state; data fetching belongs in Pinia Colada when present.5253## When to Read Each Reference5455Read one level deep from this file. Read multiple when a task spans topics.5657| Task involves | Read |58| ---------------------------------------------------------------------- | -------------------------------------- |59| Components, props/emits, slots, `defineModel`, naming, templates | `references/components.md` |60| Composables (`use*`), shared reactive logic | `references/composables.md` |61| `ref`/`reactive`/`computed`/`watch`, `toRefs`, performance | `references/reactivity-performance.md` |62| Global state with Pinia setup stores, `storeToRefs` | `references/state-pinia.md` |63| Pinia Colada core: keys, `defineQueryOptions`, `useQuery`, mutations | `references/pinia-colada.md` |64| Optimistic updates, infinite/paginated queries, SSR, cancellation | `references/pinia-colada-advanced.md` |65| Pinia Colada plugins (retry, delay, auto-refetch, persister, hooks) | `references/pinia-colada-plugins.md` |66| Direct cache access: `getQueryData`/`setQueryData`/`invalidateQueries` | `references/pinia-colada-cache.md` |67| File-based routing, route groups, params, `definePage`, typed router | `references/routing-pages.md` |68| Project stack, structure, commands, conventions, docs research | `references/project-standards.md` |69| 3.4/3.5 core APIs: `useId`, `nextTick`, flush timing, `watch` once/deep, `onWatcherCleanup` | `references/core-new-apis.md` |70| `<script setup>` compiler macros — `defineOptions`, `withDefaults`, generic components | `references/script-setup-macros.md` |71| Advanced reactivity: `effectScope`, `customRef`, `triggerRef`, `markRaw`, `shallowReadonly` | `references/advanced-patterns.md` |7273## Gotchas7475- **Destructuring `reactive()` loses reactivity** — `const { count } = reactive({ count: 0 })`76 makes `count` a plain number; keep the reactive object intact, or use `toRefs()`, or `ref()`.77- **`storeToRefs()` required when destructuring Pinia state** — `const { user } = useAuthStore()`78 gives a non-reactive snapshot; use `const { user } = storeToRefs(useAuthStore())`. Methods79 are destructured directly from the store (not through `storeToRefs`).80- **Parent `ref` access needs `defineExpose()`** — `childRef.value.method()` returns `undefined`81 unless the child `<script setup>` explicitly `defineExpose({ method })`.82- **Use `:slotted()` not `:deep()` for slotted content** — slot content comes from the parent83 scope, so `:deep(.child-class)` in a scoped style block does not match it; use `:slotted(.child-class)`.84- **`watchEffect` cleanup race on fast re-renders** — register cleanup to cancel async work:85 `watchEffect((onCleanup) => { onCleanup(() => controller.abort()) })`, or a fast prop change86 starts a second effect before the first resolves and writes stale state.87- **Pinia store not hydrated in SSR** — calling `useMyStore()` outside a component setup context88 (e.g. a top-level module) creates an instance disconnected from the SSR app; call stores inside89 `setup()` or pass the `pinia` instance explicitly: `useMyStore(pinia)`.90- **Query keys must depend on ALL variables used in the query function** — a key that omits a91 variable serves stale cached data when that variable changes (Pinia Colada).92- **Dynamic query keys need a getter, not a plain value** — pass `key: () => [...]` so the key93 re-evaluates reactively; a plain array snapshot never updates.94- **Prefer `refresh()` over `refetch()`** — `refresh()` reuses in-flight requests and respects95 `staleTime`; `refetch()` always forces a new fetch.96- **Avoid `index.vue` route files** — use a named group like `pages/(home).vue` for a meaningful97 route name.98- **Use explicit route param names** — `userId` not `id`, `postSlug` not `slug`, for type-safe,99 self-documenting routes.100- **Watching a destructured prop needs a getter** — `const { count } = defineProps()` stays reactive101 in templates, but `watch(count, ...)` watches a snapshot; use `watch(() => count, ...)`102 (`references/core-new-apis.md`).103- **`onWatcherCleanup()` must be called synchronously** — invoking it after an `await` throws, since104 the active-watcher context is gone; use the `onCleanup` callback param for post-await cancellation105 (`references/core-new-apis.md`).106107## Anti-Patterns108109| Don't | Do Instead |110| ---------------------------------- | -------------------------------- |111| Options API (`data()`, `methods:`) | Composition API `<script setup>` |112| `this.$store` / Vuex | Pinia with setup store syntax |113| Direct store state destructuring | `storeToRefs(useMyStore())` |114| `v-html` with dynamic content | `v-text` or sanitized rendering |115| `reactive()` for primitives | `ref()` for primitives |116| Watchers when computed works | `computed()` for derived state |117| Global event bus | `provide/inject` or Pinia |118| `defineComponent()` wrapper | `<script setup>` directly |119120## Workflow Integration121122Auto-activates during Build when a Vue project is detected (`.vue` files, `vue` in123`package.json`). Loaded by the `developer` agent alongside `mk:typescript`.