All skills
jeffallan avatar

/vue-expert

@efebc44
by jeffallanjeffallan/claude-skills12k stars
1,124

Builds Vue 3 components with Composition API patterns, configures Nuxt 3 SSR/SSG projects, sets up Pinia stores, scaffolds Quasar/Capacitor mobile apps, implements PWA features, and optimises Vite builds. Use when creating Vue 3 applications with Composition API, writing reusable composables, managing state with Pinia, building hybrid mobile apps with Quasar or Capacitor, configuring service workers, or tuning Vite configuration and TypeScript integration.

Use this Skill: https://skilld.dev/gh/jeffallan/claude-skills/vue-expert

This session only. Nothing lands on disk.

referencescomponents.md

≈2.3k tokens on demand. Your agent reads this file only when SKILL.md points to it.

Components

Props with TypeScript

<script setup lang="ts">
// Simple props
interface Props {
  title: string
  count?: number
  items: string[]
}

const props = defineProps<Props>()

// Props with defaults
const propsWithDefaults = withDefaults(defineProps<Props>(), {
  count: 0,
  items: () => []
})

// Runtime props (without TypeScript)
const runtimeProps = defineProps({
  title: {
    type: String,
    required: true
  },
  count: {
    type: Number,
    default: 0,
    validator: (value: number) => value >= 0
  },
  items: {
    type: Array as PropType<string[]>,
    default: () => []
  }
})

// Access props
console.log(props.title)
console.log(props.count)
</script>

<template>
  <div>
    <h1>{{ title }}</h1>
    <p>Count: {{ count }}</p>
  </div>
</template>

Emits (Events)

<script setup lang="ts">
// TypeScript emits
interface Emits {
  (e: 'update', value: string): void
  (e: 'delete', id: number): void
  (e: 'submit', payload: { name: string; email: string }): void
}

const emit = defineEmits<Emits>()

// Emit events
function handleUpdate() {
  emit('update', 'new value')
}

function handleDelete(id: number) {
  emit('delete', id)
}

function handleSubmit() {
  emit('submit', { name: 'John', email: 'john@example.com' })
}

// Runtime emits with validation
const runtimeEmit = defineEmits({
  update: (value: string) => {
    return value.length > 0
  },
  delete: (id: number) => {
    return id > 0
  }
})
</script>

<template>
  <button @click="handleUpdate">Update</button>
  <button @click="handleDelete(123)">Delete</button>
</template>

v-model (Two-way Binding)

<!-- Parent Component -->
<script setup lang="ts">
import { ref } from 'vue'
import CustomInput from './CustomInput.vue'

const searchQuery = ref('')
const filters = ref({ category: '', price: 0 })
</script>

<template>
  <!-- Single v-model -->
  <CustomInput v-model="searchQuery" />

  <!-- Multiple v-models -->
  <FilterPanel
    v-model:category="filters.category"
    v-model:price="filters.price"
  />
</template>

<!-- CustomInput.vue -->
<script setup lang="ts">
interface Props {
  modelValue: string
}

interface Emits {
  (e: 'update:modelValue', value: string): void
}

const props = defineProps<Props>()
const emit = defineEmits<Emits>()

function handleInput(event: Event) {
  const target = event.target as HTMLInputElement
  emit('update:modelValue', target.value)
}
</script>

<template>
  <input :value="modelValue" @input="handleInput" />
</template>

<!-- FilterPanel.vue with multiple v-models -->
<script setup lang="ts">
interface Props {
  category: string
  price: number
}

interface Emits {
  (e: 'update:category', value: string): void
  (e: 'update:price', value: number): void
}

const props = defineProps<Props>()
const emit = defineEmits<Emits>()
</script>

<template>
  <select
    :value="category"
    @change="emit('update:category', ($event.target as HTMLSelectElement).value)"
  >
    <option value="books">Books</option>
    <option value="electronics">Electronics</option>
  </select>
  <input
    type="number"
    :value="price"
    @input="emit('update:price', Number(($event.target as HTMLInputElement).value))"
  />
</template>

Slots

<!-- Parent Component -->
<template>
  <Card>
    <template #header>
      <h2>Card Title</h2>
    </template>

    <template #default>
      <p>Main content goes here</p>
    </template>

    <template #footer="{ close }">
      <button @click="close">Close</button>
    </template>
  </Card>
</template>

<!-- Card.vue -->
<script setup lang="ts">
import { useSlots } from 'vue'

const slots = useSlots()

// Check if slot exists
const hasHeader = !!slots.header
const hasFooter = !!slots.footer

function close() {
  console.log('Closing card')
}
</script>

<template>
  <div class="card">
    <div v-if="hasHeader" class="card-header">
      <slot name="header"></slot>
    </div>

    <div class="card-body">
      <slot></slot> <!-- Default slot -->
    </div>

    <div v-if="hasFooter" class="card-footer">
      <slot name="footer" :close="close"></slot> <!-- Scoped slot -->
    </div>
  </div>
</template>

Scoped Slots (Advanced)

<!-- List Component with Scoped Slot -->
<script setup lang="ts" generic="T">
interface Props {
  items: T[]
}

const props = defineProps<Props>()
</script>

<template>
  <div class="list">
    <div v-for="(item, index) in items" :key="index">
      <slot :item="item" :index="index"></slot>
    </div>
  </div>
</template>

<!-- Usage -->
<template>
  <List :items="users">
    <template #default="{ item, index }">
      <div>{{ index }}: {{ item.name }}</div>
    </template>
  </List>
</template>

Provide/Inject

<!-- Parent Component (Provider) -->
<script setup lang="ts">
import { provide, ref, readonly, InjectionKey } from 'vue'

// Type-safe injection key
interface UserData {
  name: string
  email: string
}

export const userKey = Symbol() as InjectionKey<UserData>

const user = ref<UserData>({
  name: 'John Doe',
  email: 'john@example.com'
})

function updateUser(newUser: UserData) {
  user.value = newUser
}

// Provide data
provide(userKey, readonly(user.value))
provide('updateUser', updateUser)
</script>

<!-- Child Component (Injector) -->
<script setup lang="ts">
import { inject } from 'vue'
import { userKey } from './Parent.vue'

// Inject with type safety
const user = inject(userKey)
const updateUser = inject<(user: UserData) => void>('updateUser')

// Inject with default value
const theme = inject('theme', 'light')

function handleUpdate() {
  if (updateUser) {
    updateUser({ name: 'Jane', email: 'jane@example.com' })
  }
}
</script>

<template>
  <div>
    <p>User: {{ user?.name }}</p>
    <p>Theme: {{ theme }}</p>
    <button @click="handleUpdate">Update User</button>
  </div>
</template>

Teleport

<script setup lang="ts">
import { ref } from 'vue'

const showModal = ref(false)
</script>

<template>
  <button @click="showModal = true">Show Modal</button>

  <!-- Teleport to body -->
  <Teleport to="body">
    <div v-if="showModal" class="modal">
      <div class="modal-content">
        <h2>Modal Title</h2>
        <p>Modal content</p>
        <button @click="showModal = false">Close</button>
      </div>
    </div>
  </Teleport>

  <!-- Teleport to specific element -->
  <Teleport to="#modal-container">
    <div class="notification">Notification message</div>
  </Teleport>

  <!-- Conditional teleport -->
  <Teleport to="body" :disabled="!isMobile">
    <div>Only teleported on mobile</div>
  </Teleport>
</template>

<style scoped>
.modal {
  position: fixed;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  background: rgba(0, 0, 0, 0.5);
  display: flex;
  align-items: center;
  justify-content: center;
}

.modal-content {
  background: white;
  padding: 2rem;
  border-radius: 8px;
}
</style>

Dynamic Components

<script setup lang="ts">
import { ref, shallowRef, Component } from 'vue'
import HomeView from './HomeView.vue'
import AboutView from './AboutView.vue'
import ContactView from './ContactView.vue'

// Use shallowRef for component references (performance)
const currentView = shallowRef<Component>(HomeView)

const components = {
  home: HomeView,
  about: AboutView,
  contact: ContactView
}

function switchView(view: keyof typeof components) {
  currentView.value = components[view]
}
</script>

<template>
  <button @click="switchView('home')">Home</button>
  <button @click="switchView('about')">About</button>
  <button @click="switchView('contact')">Contact</button>

  <!-- Dynamic component with KeepAlive -->
  <KeepAlive>
    <component :is="currentView" />
  </KeepAlive>
</template>

Async Components

<script setup lang="ts">
import { defineAsyncComponent } from 'vue'

// Lazy load component
const HeavyComponent = defineAsyncComponent(() =>
  import('./HeavyComponent.vue')
)

// With loading and error states
const AdminPanel = defineAsyncComponent({
  loader: () => import('./AdminPanel.vue'),
  loadingComponent: () => import('./LoadingSpinner.vue'),
  errorComponent: () => import('./ErrorDisplay.vue'),
  delay: 200, // Delay before showing loading component
  timeout: 3000 // Timeout before showing error
})
</script>

<template>
  <Suspense>
    <template #default>
      <HeavyComponent />
    </template>
    <template #fallback>
      <div>Loading...</div>
    </template>
  </Suspense>
</template>

Quick Reference

Pattern Use Case
defineProps<T>() Type-safe props with TypeScript
withDefaults() Props with default values
defineEmits<T>() Type-safe event emitters
v-model Two-way data binding
<slot> Content distribution
Scoped slots Pass data from child to parent
provide/inject Dependency injection (avoid prop drilling)
<Teleport> Render DOM outside component hierarchy
<component :is> Dynamic component switching
defineAsyncComponent() Lazy load components

Source: SKILL.md on GitHub

1 alert16d5 checks · Risk CRITICAL
  • Gen Agent Trust Hub16d

    The skill provides comprehensive documentation and boilerplate for Vue 3, Nuxt 3, and hybrid mobile development using modern best practices. It guides the agent through standard development workflows including type checking, unit testing, and build optimization using community-standard tools. All external references target the author's own documentation hosted on GitHub Pages, and all package dependencies are well-known industry libraries.

  • Socket16d

    No alerts

  • Snyk16d

    Risk: LOW · No issues

  • Runlayer6mo

    1/8 files flagged

  • ZeroLeaks5mo

    Score: 93/100 · 2 sections analyzed

Signed by skilld at efebc44. This ties the file your Agent reads to that commit on GitHub. It does not review the instructions.

Last checked against GitHub 2 months ago.

Steadyupdated 5 months ago
Other metadata
metadata
{
  "author": "https://github.com/Jeffallan",
  "version": "1.1.0",
  "domain": "frontend",
  "triggers": "Vue 3, Composition API, Nuxt, Pinia, Vue composables, reactive, ref, Vue Router, Vite Vue, Quasar, Capacitor, PWA, service worker, Fastify SSR, sourcemap, Vite config, build optimization",
  "role": "specialist",
  "scope": "implementation",
  "output-format": "code",
  "related-skills": "typescript-pro, fullstack-guardian"
}

README badge

README badge for jeffallan/claude-skills/vue-expert