Vue 3 / Nuxt 3 Code Generation Rules
Loaded when: package.json contains vue or nuxt.
These rules are ADDITIVE to the universal rules in SKILL.md.
Project conventions override: If design-tokens.json contains a conventions section, those conventions take priority over the patterns in this file for stylistic decisions (export style, file naming, import ordering, barrel exports). This file remains authoritative for framework-specific syntax requirements (<script setup>, Composition API, <template>, class attribute, Vue directives).
0. NON-NEGOTIABLES
These Vue 3 / Nuxt 3 rules hold for every generated file. No exceptions.
- MUST use
<script setup lang="ts">+ Composition API. NEVER emit Options API for new code. NEVER mix<script setup>and plain<script>in the same SFC. - NEVER use
.valueinside<template>. Vue automatically unwraps refs in templates. Writing{{ count.value }}instead of{{ count }}is a bug. - MUST use
class, NEVERclassName. Vue uses the native HTMLclassattribute. - MUST use
defineModel()for v-model bindings in Vue 3.4+. NEVER hand-roll the prop + emit pair whendefineModel()is available. - NEVER use React hooks (
useEffect,useState). Vue has nouseEffect. Usewatch,watchEffect,ref,reactive, oronMounted.
1. KEY REMINDERS
- Always use
<script setup lang="ts">with Composition API. Never use Options API for new code. - Use
class(neverclassName). UsedefineModel()for v-model instead of manual prop+emit. - Never use
useEffect-- Vue has nouseEffect. Usewatch,watchEffect, oronMounted.
2. SYNTAX QUICK REFERENCE (framework-specific gotchas and newer APIs only)
- Reactive props destructure (3.5+):
const { title, disabled = false } = defineProps<Props>()-- reactivity preserved automatically, notoRefsneeded - defineModel (3.4+):
const modelValue = defineModel<string>()-- two-way binding macro, replaces manual prop + emit pattern for v-model - useTemplateRef (3.5+):
const el = useTemplateRef<HTMLInputElement>('input')-- preferred overref()for DOM refs - Typed emits:
defineEmits<{ select: [item: string] }>()
3. FILE STRUCTURE CONVENTIONS
Nuxt 3 (auto-imports enabled):
- Nuxt 4 moves app code into
app/directory (e.g.,app/pages/,app/components/). Check forapp/before generating paths. - If project has
app/directory with pages/components inside, useapp/prefix for all paths below. - Pages:
pages/index.vue,pages/dashboard.vue,pages/users/[id].vue - Components:
components/(auto-imported),components/ui/for primitives - Composables:
composables/(auto-imported, prefix files withuse) - Server API:
server/api/users.get.ts,server/api/users.post.ts - Layouts:
layouts/default.vue - Middleware:
middleware/auth.ts - Plugins:
plugins/ - Public assets:
public/
Standalone Vue 3 (Vite):
- Pages/views:
src/views/orsrc/pages/ - Components:
src/components/,src/components/ui/ - Composables:
src/composables/ - Router:
src/router/index.ts - Store:
src/stores/
4. DATA FETCHING PATTERNS
| Context | Pattern |
|---|---|
| Nuxt simple | const { data, pending, error } = useFetch('/api/users') |
| Nuxt complex | const { data } = useAsyncData('users', () => $fetch('/api/users', { query: { page } })) |
| Nuxt server route | server/api/users.get.ts exports defineEventHandler() |
| Vue Query | const { data } = useQuery({ queryKey: ['users'], queryFn }) |
| No library fallback | onMounted(async () => { data.value = await fetch(...) }) |
- Nuxt 4+:
useFetch/useAsyncDatareturnshallowRefdata by default. Adddeep: truefor deep reactivity. getCachedDataoption onuseAsyncDatacontrols SPA-navigation caching.
5. CLIENT / SERVER BOUNDARIES
- No
"use client"or"use server"directives. Never add them. - Vue/Nuxt components hydrate automatically after SSR.
- Client-only rendering:
<ClientOnly><MyWidget /></ClientOnly> - Server-only components: name file
MyComponent.server.vue(Nuxt 3) - Server-only logic: place in
server/directory (Nuxt) - Environment check:
import.meta.client/import.meta.server(Nuxt 3) - Never use
process.client/process.server— these are deprecated. Always useimport.meta.client/import.meta.server. - Vapor Mode (experimental): do NOT enable unless explicitly requested — it changes the rendering pipeline and is not yet stable.
6. STYLING SYNTAX
| Method | Syntax |
|---|---|
| Tailwind | class="flex gap-4" |
| Dynamic classes | :class="{ 'bg-blue-500': isActive, 'opacity-50': disabled }" |
| Array syntax | :class="['base', condition ? 'extra' : '']" |
| Scoped styles | <style scoped> (preferred) |
| CSS Modules | <style module> with :class="$style.name" |
7. FRAMEWORK-SPECIFIC LIBRARY CATEGORIES
- state_management:
pinia(recommended),vuex(legacy only) - data_fetching:
@tanstack/vue-query,ofetch,axios - forms:
vee-validate,formkit,@tanstack/vue-form - icons:
lucide-vue-next,@iconify/vue,unplugin-icons - animation:
@vueuse/motion,gsap,<Transition>(built-in) - component_library:
shadcn-vue,reka-ui (formerly radix-vue),@nuxt/ui,vuetify,primevue,naive-ui,element-plus,quasar - composables:
@vueuse/core(essential utility composables) - tables:
@tanstack/vue-table,ag-grid-vue3 - charts:
vue-chartjs,vue-echarts - toast:
vue-sonner,vue-toastification - dnd:
vuedraggable,@vueuse/integrations(useSortable)
Rule: check package.json before importing. If a library from this table is already installed, use it. Never add a second library for the same category.
Library Boundary Values (SVG Chart Libraries)
Some libraries accept color/style values as string props, not CSS classes or variables. SVG-based chart libraries (vue-chartjs, @nivo/core, d3) require hex/rgb strings because the SVG renderer does not resolve CSS custom properties at the attribute level.
Pattern: Resolve CSS variables at runtime with computed + onMounted.
<script setup lang="ts">
import { ref, onMounted } from 'vue'
function resolveTokenColor(tokenVar: string): string {
return getComputedStyle(document.documentElement)
.getPropertyValue(tokenVar)
.trim() || '#000000'
}
const chartColors = ref({ primary: '#000000' })
onMounted(() => {
chartColors.value = {
primary: resolveTokenColor('--colors-primary'),
}
})
</script>
When runtime resolution is not feasible, hardcoding is acceptable but MUST include a comment linking the value to its token:
<!-- Token: colors.primary (#2563EB) — hardcoded for SVG chart compatibility -->
<Bar :fill="'#2563EB'" />
These values are exempt from the Phase 5 hardcoded-values audit (bucket A) because the library API requires them.