Skills vue
install
source · Clone the upstream repo
git clone https://github.com/TerminalSkills/skills
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/TerminalSkills/skills "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/vue" ~/.claude/skills/terminalskills-skills-vue && rm -rf "$T"
manifest:
skills/vue/SKILL.mdsource content
Vue
Vue 3 uses the Composition API for reactive state management, single-file components (.vue), and a virtual DOM with compiler optimizations. It scales from progressive enhancement to full SPAs.
Installation
# Create Vue project with Vite npm create vue@latest my-app cd my-app npm install npm run dev
Project Structure
# Vue 3 project layout src/ ├── App.vue # Root component ├── main.ts # Entry point ├── router/index.ts # Vue Router config ├── stores/ # Pinia stores │ └── articles.ts ├── composables/ # Reusable logic │ └── useApi.ts ├── components/ # Shared components │ └── ArticleCard.vue ├── views/ # Page components │ ├── HomeView.vue │ └── ArticlesView.vue └── types/ # TypeScript types └── article.ts
Components (Composition API)
<!-- src/views/ArticlesView.vue — page component with script setup --> <script setup lang="ts"> import { ref, onMounted } from 'vue' import type { Article } from '@/types/article' import ArticleCard from '@/components/ArticleCard.vue' const articles = ref<Article[]>([]) const loading = ref(true) onMounted(async () => { const res = await fetch('/api/articles') articles.value = await res.json() loading.value = false }) </script> <template> <div> <h1>Articles</h1> <p v-if="loading">Loading...</p> <div v-else> <ArticleCard v-for="article in articles" :key="article.id" :article="article" /> </div> </div> </template>
<!-- src/components/ArticleCard.vue — reusable component with props --> <script setup lang="ts"> import type { Article } from '@/types/article' const props = defineProps<{ article: Article }>() const emit = defineEmits<{ (e: 'delete', id: number): void }>() </script> <template> <article class="card"> <RouterLink :to="`/articles/${props.article.slug}`"> <h2>{{ article.title }}</h2> </RouterLink> <p>{{ article.excerpt }}</p> <button @click="emit('delete', article.id)">Delete</button> </article> </template>
Reactivity
<!-- src/components/Counter.vue — reactive state demo --> <script setup lang="ts"> import { ref, computed, watch } from 'vue' const count = ref(0) const doubled = computed(() => count.value * 2) watch(count, (newVal) => { console.log(`Count changed to ${newVal}`) }) function increment() { count.value++ } </script> <template> <div> <p>Count: {{ count }} (doubled: {{ doubled }})</p> <button @click="increment">+1</button> </div> </template>
Pinia Store
// src/stores/articles.ts — Pinia state management import { defineStore } from 'pinia' import { ref, computed } from 'vue' import type { Article } from '@/types/article' export const useArticlesStore = defineStore('articles', () => { const articles = ref<Article[]>([]) const loading = ref(false) const published = computed(() => articles.value.filter((a) => a.published)) async function fetchAll() { loading.value = true const res = await fetch('/api/articles') articles.value = await res.json() loading.value = false } async function create(data: Partial<Article>) { const res = await fetch('/api/articles', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(data), }) const article = await res.json() articles.value.unshift(article) } return { articles, loading, published, fetchAll, create } })
Composables
// src/composables/useApi.ts — reusable fetch composable import { ref, type Ref } from 'vue' export function useApi<T>(url: string) { const data: Ref<T | null> = ref(null) const error = ref<string | null>(null) const loading = ref(false) async function execute() { loading.value = true error.value = null try { const res = await fetch(url) if (!res.ok) throw new Error(`HTTP ${res.status}`) data.value = await res.json() } catch (e: any) { error.value = e.message } finally { loading.value = false } } return { data, error, loading, execute } }
Router
// src/router/index.ts — Vue Router configuration import { createRouter, createWebHistory } from 'vue-router' const router = createRouter({ history: createWebHistory(), routes: [ { path: '/', component: () => import('@/views/HomeView.vue') }, { path: '/articles', component: () => import('@/views/ArticlesView.vue') }, { path: '/articles/:slug', component: () => import('@/views/ArticleView.vue'), props: true }, { path: '/admin', component: () => import('@/views/AdminView.vue'), meta: { requiresAuth: true }, }, ], }) router.beforeEach((to) => { if (to.meta.requiresAuth && !isAuthenticated()) { return '/login' } }) export default router
Provide/Inject
// src/main.ts — provide global dependencies import { createApp } from 'vue' import { createPinia } from 'pinia' import App from './App.vue' import router from './router' const app = createApp(App) app.use(createPinia()) app.use(router) app.provide('apiBase', '/api') app.mount('#app')
Key Patterns
- Use
for concise component definitions — it's the recommended style<script setup> - Use
for primitives,ref()
for objects;reactive()
is generally preferredref - Use Pinia stores for shared state across components
- Extract reusable logic into composables (
functions)use* - Use
anddefineProps<T>()
for type-safe component interfacesdefineEmits<T>() - Lazy-load route components with dynamic
for code splittingimport()