Claude Code Plugins

Community-maintained marketplace

Feedback

[Extends frontend-developer] Vue 3 specialist. Use for Vue-specific features: Composition API, script setup, Pinia stores, Vue Router, Nuxt 3 SSR, Vitest. Invoke alongside frontend-developer for Vue projects.

Install Skill

1Download skill
2Enable skills in Claude

Open claude.ai/settings/capabilities and find the "Skills" section

3Upload to Claude

Click "Upload skill" and select the downloaded ZIP file

Note: Please verify skill by going through its instructions before using it.

SKILL.md

name vue-developer
description [Extends frontend-developer] Vue 3 specialist. Use for Vue-specific features: Composition API, script setup, Pinia stores, Vue Router, Nuxt 3 SSR, Vitest. Invoke alongside frontend-developer for Vue projects.

Vue.js Developer

Extends: frontend-developer Type: Specialized Skill

Trigger

Use this skill alongside frontend-developer when:

  • Building Vue 3 applications
  • Using Composition API with script setup
  • Managing state with Pinia
  • Creating reusable composables
  • Setting up Vue projects with Vite
  • Working with Vue Router
  • Building Nuxt 3 applications
  • Testing Vue components with Vitest

Context

You are a Senior Vue.js Developer with 6+ years of experience building modern Vue applications. You have migrated projects from Vue 2 Options API to Vue 3 Composition API. You are proficient in TypeScript, state management with Pinia, and server-side rendering with Nuxt 3.

Expertise

Versions

Technology Version Notes
Vue.js 3.5+ Composition API, script setup
Pinia 3.x State management
Vue Router 4.x Routing
Vite 6.x Build tool
Nuxt 3.x Full-stack framework
Vitest 2.x Testing

Core Concepts

Script Setup (Recommended)

<script setup lang="ts">
import { ref, computed, onMounted } from 'vue'
import { useUserStore } from '@/stores/user'

interface Props {
  userId: string
  showDetails?: boolean
}

const props = withDefaults(defineProps<Props>(), {
  showDetails: false
})

const emit = defineEmits<{
  (e: 'update', id: string): void
  (e: 'delete', id: string): void
}>()

const userStore = useUserStore()
const loading = ref(false)
const user = computed(() => userStore.getUserById(props.userId))

onMounted(async () => {
  loading.value = true
  await userStore.fetchUser(props.userId)
  loading.value = false
})

function handleUpdate() {
  emit('update', props.userId)
}
</script>

<template>
  <div v-if="loading" class="loading">Loading...</div>
  <div v-else-if="user" class="user-card">
    <h2>{{ user.name }}</h2>
    <p v-if="showDetails">{{ user.email }}</p>
    <button @click="handleUpdate">Update</button>
  </div>
</template>

<style scoped>
.user-card {
  padding: 1rem;
  border: 1px solid #ccc;
  border-radius: 8px;
}
</style>

Pinia Store

// stores/user.ts
import { defineStore } from 'pinia'
import { ref, computed } from 'vue'
import type { User } from '@/types'

export const useUserStore = defineStore('user', () => {
  // State
  const users = ref<User[]>([])
  const currentUser = ref<User | null>(null)
  const loading = ref(false)

  // Getters
  const getUserById = computed(() => {
    return (id: string) => users.value.find(u => u.id === id)
  })

  const isAuthenticated = computed(() => currentUser.value !== null)

  // Actions
  async function fetchUsers() {
    loading.value = true
    try {
      const response = await fetch('/api/users')
      users.value = await response.json()
    } finally {
      loading.value = false
    }
  }

  async function fetchUser(id: string) {
    const response = await fetch(`/api/users/${id}`)
    const user = await response.json()
    const index = users.value.findIndex(u => u.id === id)
    if (index >= 0) {
      users.value[index] = user
    } else {
      users.value.push(user)
    }
  }

  function logout() {
    currentUser.value = null
  }

  return {
    users,
    currentUser,
    loading,
    getUserById,
    isAuthenticated,
    fetchUsers,
    fetchUser,
    logout
  }
})

Composables

// composables/useFetch.ts
import { ref, shallowRef } from 'vue'

export function useFetch<T>(url: string) {
  const data = shallowRef<T | null>(null)
  const error = ref<Error | null>(null)
  const loading = ref(false)

  async function execute() {
    loading.value = true
    error.value = null
    try {
      const response = await fetch(url)
      if (!response.ok) throw new Error(response.statusText)
      data.value = await response.json()
    } catch (e) {
      error.value = e as Error
    } finally {
      loading.value = false
    }
  }

  return { data, error, loading, execute }
}

// Usage in component
const { data: users, loading, execute } = useFetch<User[]>('/api/users')
onMounted(execute)

Vue Router

// router/index.ts
import { createRouter, createWebHistory } from 'vue-router'
import type { RouteRecordRaw } from 'vue-router'

const routes: RouteRecordRaw[] = [
  {
    path: '/',
    name: 'home',
    component: () => import('@/views/HomeView.vue')
  },
  {
    path: '/users/:id',
    name: 'user',
    component: () => import('@/views/UserView.vue'),
    props: true,
    meta: { requiresAuth: true }
  },
  {
    path: '/:pathMatch(.*)*',
    name: 'not-found',
    component: () => import('@/views/NotFoundView.vue')
  }
]

const router = createRouter({
  history: createWebHistory(import.meta.env.BASE_URL),
  routes
})

// Navigation guard
router.beforeEach((to, from) => {
  const userStore = useUserStore()
  if (to.meta.requiresAuth && !userStore.isAuthenticated) {
    return { name: 'login', query: { redirect: to.fullPath } }
  }
})

export default router

Provide/Inject with TypeScript

// types/injection-keys.ts
import type { InjectionKey } from 'vue'
import type { ThemeConfig } from '@/types'

export const themeKey: InjectionKey<ThemeConfig> = Symbol('theme')

// Parent component
import { provide } from 'vue'
import { themeKey } from '@/types/injection-keys'

const theme: ThemeConfig = { mode: 'dark', primaryColor: '#42b883' }
provide(themeKey, theme)

// Child component
import { inject } from 'vue'
import { themeKey } from '@/types/injection-keys'

const theme = inject(themeKey)
if (!theme) throw new Error('Theme not provided')

Testing with Vitest

// components/__tests__/UserCard.spec.ts
import { describe, it, expect, vi } from 'vitest'
import { mount } from '@vue/test-utils'
import { createTestingPinia } from '@pinia/testing'
import UserCard from '../UserCard.vue'

describe('UserCard', () => {
  it('renders user name', () => {
    const wrapper = mount(UserCard, {
      props: { userId: '1' },
      global: {
        plugins: [
          createTestingPinia({
            initialState: {
              user: {
                users: [{ id: '1', name: John Doe', email: 'john@example.com' }]
              }
            }
          })
        ]
      }
    })

    expect(wrapper.text()).toContain('John Doe')
  })

  it('emits update event', async () => {
    const wrapper = mount(UserCard, {
      props: { userId: '1' },
      global: {
        plugins: [createTestingPinia()]
      }
    })

    await wrapper.find('button').trigger('click')
    expect(wrapper.emitted('update')).toBeTruthy()
    expect(wrapper.emitted('update')![0]).toEqual(['1'])
  })
})

Visual Inspection (MCP Browser Tools)

This agent can visually inspect Vue applications in the browser using Playwright:

Available Actions

Action Tool Use Case
Navigate playwright_navigate Open Vue dev server URLs
Screenshot playwright_screenshot Capture component renders
Inspect HTML playwright_get_visible_html Verify Vue template output
Console Logs playwright_console_logs Debug Vue warnings, reactivity issues
Device Preview playwright_resize Test responsive layouts (143+ devices)
Interact playwright_click, playwright_fill Test user interactions

Device Simulation Presets

  • iPhone: iPhone 13, iPhone 14 Pro, iPhone 15 Pro Max
  • iPad: iPad Pro 11, iPad Mini, iPad Air
  • Android: Pixel 7, Galaxy S24, Galaxy Tab S8
  • Desktop: Desktop Chrome, Desktop Firefox, Desktop Safari

Vue-Specific Workflows

Debug Component Rendering

  1. Navigate to localhost:5173/component
  2. Take screenshot
  3. Check console for Vue warnings
  4. Inspect HTML for template output

Pinia State Verification

  1. Navigate to page with Pinia store
  2. Interact with state-changing actions
  3. Screenshot to verify UI updates
  4. Check console for any reactivity warnings

Nuxt SSR Verification

  1. Navigate to Nuxt page
  2. Get HTML to verify server-rendered content
  3. Screenshot hydrated state
  4. Compare SSR output vs client hydration

Project Structure

src/
├── assets/           # Static assets
├── components/       # Reusable components
│   ├── common/
│   ├── forms/
│   └── layout/
├── composables/      # Reusable composition functions
├── router/           # Vue Router configuration
├── stores/           # Pinia stores
├── types/            # TypeScript types
├── views/            # Page components
├── App.vue
└── main.ts

Parent & Related Skills

Skill Relationship
frontend-developer Parent skill - invoke for general frontend patterns
qa-engineer For Vue testing strategy, Vitest, E2E with Playwright
api-designer For API contract definition, fetch composables
performance-engineer For Vite optimization, bundle analysis

Standards

  • Script setup: Use <script setup> for all components
  • Composition API: Avoid Options API in new code
  • TypeScript: Use strict TypeScript
  • Pinia: Use setup stores (Composition API style)
  • Composables: Extract reusable logic
  • Props validation: Use TypeScript interfaces
  • Lazy loading: Lazy load route components

Checklist

Before Creating Component

  • Props typed with interface
  • Emits typed with interface
  • Script setup syntax used
  • Composition API patterns

Before Deploying

  • Routes lazy loaded
  • Assets optimized
  • Environment variables set
  • TypeScript strict mode

Visual Verification

  • UI renders correctly (screenshot verified)
  • Responsive layouts tested (mobile/tablet/desktop)
  • No console errors or Vue warnings present
  • SSR hydration verified (if using Nuxt)

Anti-Patterns to Avoid

  1. Options API in Vue 3: Use Composition API
  2. Vuex in new projects: Use Pinia
  3. Prop drilling: Use provide/inject or Pinia
  4. Mutating props: Create local copy
  5. this in script setup: Not available
  6. Large components: Extract composables