Agent Skill
2/7/2026vue-coding-standards
This skill should be used when the user asks to "write Vue 3 code", "create a Vue component", "implement Composition API", "set up Pinia store", "configure Vue Router", "review Vue code", or "follow Vue best practices". Provides Vue 3 coding standards for the oakiv-website project including TypeScript patterns, component structure, composables, Pinia stores, and routing.
G
generative
0GitHub Stars
2Views
npx skills add Generative-Bricks/oakiv-website
SKILL.md
| Name | vue-coding-standards |
| Description | This skill should be used when the user asks to "write Vue 3 code", "create a Vue component", "implement Composition API", "set up Pinia store", "configure Vue Router", "review Vue code", or "follow Vue best practices". Provides Vue 3 coding standards for the oakiv-website project including TypeScript patterns, component structure, composables, Pinia stores, and routing. |
name: vue-coding-standards description: | This skill should be used when the user asks to "write Vue 3 code", "create a Vue component", "implement Composition API", "set up Pinia store", "configure Vue Router", "review Vue code", or "follow Vue best practices". Provides Vue 3 coding standards for the oakiv-website project including TypeScript patterns, component structure, composables, Pinia stores, and routing. version: 1.0.0
Vue Coding Standards & Best Practices
Vue 3 coding standards for the oakiv-website project. Based on official Vue style guide and current best practices (Vue 3.5+, Pinia, Vue Router 4).
Code Quality Principles
- Readability First - Clear names, self-documenting code, consistent formatting
- KISS - Simplest solution that works, no over-engineering
- DRY - Extract common logic into composables, create reusable components
- YAGNI - Don't build features before needed
Vue 3 Component Standards
Use Composition API with <script setup>
<script setup lang="ts">
import { ref, computed, onMounted } from 'vue'
interface Props {
title: string
count?: number
}
const props = withDefaults(defineProps<Props>(), {
count: 0
})
const emit = defineEmits<{
update: [value: number]
close: []
}>()
const localCount = ref(props.count)
const doubleCount = computed(() => localCount.value * 2)
function increment() {
localCount.value++
emit('update', localCount.value)
}
</script>
<template>
<div class="counter">
<h2>{{ title }}</h2>
<button @click="increment">Count: {{ localCount }}</button>
</div>
</template>
Component Naming
| Type | Pattern | Example |
|---|---|---|
| Base components | Base prefix | BaseButton.vue, BaseInput.vue |
| Layout | Layout prefix | LayoutHeader.vue, LayoutSidebar.vue |
| Feature | Feature prefix | UserProfile.vue, UserSettings.vue |
| Related | Shared prefix | SearchInput.vue, SearchResults.vue |
Rules:
- PascalCase for files:
UserProfile.vue - Multi-word names required (avoid
Button.vue) - Related components share prefix
Props & Emits
// Props - always typed with defaults
interface Props {
userId: string
isActive?: boolean
items?: string[]
}
const props = withDefaults(defineProps<Props>(), {
isActive: false,
items: () => []
})
// Emits - always typed
const emit = defineEmits<{
'update:modelValue': [value: string]
submit: [data: FormData]
}>()
Prop Casing: camelCase in script, both camelCase/kebab-case work in templates. Pick one, be consistent.
defineModel (Vue 3.4+)
// Simplifies v-model - no manual emit needed
const modelValue = defineModel<string>()
const count = defineModel<number>('count', { default: 0 })
useTemplateRef (Vue 3.5+)
import { useTemplateRef, onMounted } from 'vue'
const inputEl = useTemplateRef<HTMLInputElement>('input')
onMounted(() => inputEl.value?.focus())
// Template: <input ref="input" />
Pinia State Management
Use Setup Syntax (recommended over Options):
import { ref, computed } from 'vue'
import { defineStore } from 'pinia'
export const useUserStore = defineStore('user', () => {
// State
const user = ref<User | null>(null)
const isLoggedIn = ref(false)
// Getters
const fullName = computed(() =>
user.value ? `${user.value.firstName} ${user.value.lastName}` : 'Guest'
)
// Actions
async function login(email: string, password: string) {
const response = await fetch('/api/auth/login', {
method: 'POST',
body: JSON.stringify({ email, password })
})
user.value = (await response.json()).user
isLoggedIn.value = true
}
return { user, isLoggedIn, fullName, login }
})
Usage in components:
import { storeToRefs } from 'pinia'
const userStore = useUserStore()
// Use storeToRefs for reactive destructuring
const { user, fullName } = storeToRefs(userStore)
// Actions can be destructured directly
const { login, logout } = userStore
Vue Router
Lazy Loading Routes
const Home = () => import('@/views/Home.vue')
const UserProfile = () => import('@/views/UserProfile.vue')
const router = createRouter({
history: createWebHistory(),
routes: [
{ path: '/', name: 'home', component: Home },
{ path: '/user/:id', component: UserProfile, props: true, meta: { requiresAuth: true } }
]
})
Navigation Guards
router.beforeEach((to, from) => {
const userStore = useUserStore()
if (to.meta.requiresAuth && !userStore.isLoggedIn) {
return { path: '/login', query: { redirect: to.fullPath } }
}
return true
})
In-Component Guards
import { onBeforeRouteLeave, onBeforeRouteUpdate } from 'vue-router'
onBeforeRouteLeave(() => {
if (hasUnsavedChanges.value) {
return window.confirm('Discard unsaved changes?')
}
})
Template Best Practices
Conditional Rendering
<div v-if="loading">Loading...</div>
<div v-else-if="error">{{ error }}</div>
<div v-else-if="data"><UserCard :user="data" /></div>
<div v-else>No data</div>
List Rendering
<!-- Always use unique key -->
<li v-for="item in items" :key="item.id">{{ item.name }}</li>
<!-- Never use index as key -->
<li v-for="(item, index) in items" :key="index"><!-- BAD --></li>
Performance
| Pattern | Use Case |
|---|---|
computed | Derived state (cached) |
v-once | Static content, render once |
v-memo | Skip re-render when deps unchanged |
defineAsyncComponent | Lazy load heavy components |
shallowRef | When deep reactivity not needed |
// Lazy load heavy components
const HeavyChart = defineAsyncComponent(() =>
import('@/components/HeavyChart.vue')
)
File Organization
src/
├── components/
│ ├── Base/ # BaseButton, BaseInput
│ ├── Layout/ # LayoutHeader, LayoutFooter
│ └── [Feature]/ # Feature-specific
├── composables/ # useAuth, useDebounce
├── stores/ # Pinia stores
├── router/ # index.ts, guards.ts
├── types/ # TypeScript interfaces
├── views/ # Page components
└── utils/ # Helper functions
Project-Specific Patterns (oakiv-website)
Tailwind CSS 4
- Use Tailwind utility classes exclusively (no inline styles)
- Brand colors:
oak-green-primary,oak-gold,oak-cream - Use
@applysparingly in<style scoped>
Amplify Integration
- Types in
src/types/index.tsmirror Amplify Data schema - Stores handle mock data until backend deployed
- Chat uses Bedrock Lambda via Function URL
Form Handling
- Form state managed in parent view components with reactive refs
- Use native HTML form elements with v-model (no custom form components yet)
- Validation handled in submit handlers
Additional Resources
Reference Files
For detailed patterns and examples, consult:
references/composables.md- Composable patterns (useDebounce, useAsync, useUser)references/pinia-patterns.md- Advanced Pinia store patterns and compositionreferences/vue-router-patterns.md- Route organization and guard patternsreferences/testing.md- Vue Test Utils and component testing
Quick Reference
| Task | Pattern |
|---|---|
| Create component | <script setup lang="ts"> + typed props/emits |
| Two-way binding | defineModel<T>() (Vue 3.4+) |
| Template ref | useTemplateRef<T>('name') (Vue 3.5+) |
| State management | Pinia setup store syntax |
| Route lazy load | () => import('@/views/X.vue') |
| Derived state | Always use computed() |
Skills Info
Original Name:vue-coding-standardsAuthor:generative
Download