Install
$ agentstack add skill-bobmatnyc-claude-mpm-skills-vue ✓ scanned · ✓ verified, works with Claude Code, Cursor, and more.
Security review
✓ PassedNo issues found. Passed automated security review. · v0.1.0 How review works →
- ✓ Prompt-injection patterns
- ✓ Secret / credential exfiltration
- ✓ Dangerous shell & filesystem operations
- ✓ Untrusted network calls
- ✓ Known-malicious package signatures
What it can access
- ● Network access Used
- ✓ Filesystem access No
- ✓ Shell / process execution No
- ● Environment & secrets Used
- ✓ Dynamic code execution No
From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.
Verified badge
Passed review? Show it. Paste this badge into your README, it links to the public security report.
Reliability & compatibility
Declared compatibility
Compatibility is declared by the source manifest. End-to-end runtime verification is coming, see below.
We're building live execution health for every listing: tool-call success rate, median latency, uptime, and last-checked timestamps, measured, not self-reported. It isn't live yet, so we don't show numbers we can't stand behind.
How agent discovery & health will work →About
Vue 3 - Progressive JavaScript Framework
Overview
Vue 3 is a progressive framework for building user interfaces with emphasis on approachability, performance, and flexibility. It features the Composition API for better logic reuse, a powerful reactivity system, and single-file components (.vue files).
Key Features:
- Composition API: setup() with ref, reactive, computed, watch
- Reactivity System: Fine-grained reactive data tracking
- Single-File Components: Template, script, style in one file
- Vue Router: Official routing for SPAs
- Pinia: Modern state management (Vuex successor)
- TypeScript: First-class TypeScript support
- Vite: Lightning-fast development with HMR
Installation:
# Create new Vue 3 project (recommended)
npm create vue@latest my-app
cd my-app
npm install
npm run dev
# Or with Vite template
npm create vite@latest my-app -- --template vue-ts
Composition API Fundamentals
setup() Function
// Modern syntax (recommended)
import { ref, computed, onMounted } from 'vue';
// Reactive state
const count = ref(0);
const message = ref('Hello Vue 3');
// Computed values
const doubled = computed(() => count.value * 2);
// Methods
function increment() {
count.value++;
}
// Lifecycle hooks
onMounted(() => {
console.log('Component mounted');
});
Count: {{ count }} (Doubled: {{ doubled }})
Increment
Reactive State with ref() and reactive()
import { ref, reactive } from 'vue';
// ref() - for primitives and objects (needs .value in script)
const count = ref(0);
const user = ref({ name: 'Alice', age: 30 });
console.log(count.value); // 0
console.log(user.value.name); // 'Alice'
// reactive() - for objects only (no .value needed)
const state = reactive({
todos: [] as Todo[],
filter: 'all',
error: null as string | null
});
console.log(state.todos); // []
state.todos.push({ id: 1, text: 'Learn Vue', done: false });
Count: {{ count }}
User: {{ user.name }}
Todos: {{ state.todos.length }}
Computed Properties
import { ref, computed } from 'vue';
const firstName = ref('John');
const lastName = ref('Doe');
// Read-only computed
const fullName = computed(() => `${firstName.value} ${lastName.value}`);
// Writable computed
const fullNameWritable = computed({
get() {
return `${firstName.value} ${lastName.value}`;
},
set(value: string) {
const parts = value.split(' ');
firstName.value = parts[0];
lastName.value = parts[1];
}
});
// Complex computations
interface Todo {
id: number;
text: string;
done: boolean;
}
const todos = ref([
{ id: 1, text: 'Learn Vue', done: true },
{ id: 2, text: 'Build app', done: false }
]);
const completedTodos = computed(() =>
todos.value.filter(t => t.done)
);
const activeTodos = computed(() =>
todos.value.filter(t => !t.done)
);
const progress = computed(() =>
todos.value.length > 0
? (completedTodos.value.length / todos.value.length) * 100
: 0
);
Full Name: {{ fullName }}
Progress: {{ progress.toFixed(1) }}%
Active: {{ activeTodos.length }} | Done: {{ completedTodos.length }}
Watchers and Side Effects
import { ref, watch, watchEffect } from 'vue';
const count = ref(0);
const user = ref({ name: 'Alice', age: 30 });
// watch() - explicit dependencies
watch(count, (newVal, oldVal) => {
console.log(`Count changed from ${oldVal} to ${newVal}`);
});
// Watch multiple sources
watch([count, user], ([newCount, newUser], [oldCount, oldUser]) => {
console.log('Count or user changed');
});
// Watch object property (needs getter)
watch(
() => user.value.name,
(newName, oldName) => {
console.log(`Name changed from ${oldName} to ${newName}`);
}
);
// Deep watch for nested objects
watch(
user,
(newUser) => {
console.log('User object changed deeply');
},
{ deep: true }
);
// watchEffect() - automatic dependency tracking
watchEffect(() => {
// Automatically watches count and user
console.log(`Count: ${count.value}, User: ${user.value.name}`);
});
// Cleanup function
watchEffect((onCleanup) => {
const timer = setTimeout(() => {
console.log('Delayed effect');
}, 1000);
onCleanup(() => {
clearTimeout(timer);
});
});
Component Props and Events
Defining Props (TypeScript)
// Type-safe props with defineProps
interface Props {
title: string;
count?: number;
tags?: string[];
user: {
name: string;
email: string;
};
disabled?: boolean;
}
// With defaults
const props = withDefaults(defineProps(), {
count: 0,
tags: () => [],
disabled: false
});
// Access props
console.log(props.title);
console.log(props.count);
{{ title }}
Count: {{ count }}
Tags: {{ tags.join(', ') }}
Emitting Events
// Define emitted events with types
const emit = defineEmits();
function handleClick() {
emit('update', 42);
}
function handleSubmit() {
emit('submit', { name: 'Alice', email: 'alice@example.com' });
}
Update
Submit
v-model for Two-Way Binding
// v-model creates 'modelValue' prop and 'update:modelValue' event
const props = defineProps();
const emit = defineEmits();
function handleInput(event: Event) {
const target = event.target as HTMLInputElement;
emit('update:modelValue', target.value);
}
import { ref } from 'vue';
import CustomInput from './CustomInput.vue';
const searchQuery = ref('');
Searching for: {{ searchQuery }}
Multiple v-model Bindings
defineProps();
const emit = defineEmits();
import { ref } from 'vue';
import UserForm from './UserForm.vue';
const first = ref('John');
const last = ref('Doe');
Full name: {{ first }} {{ last }}
Template Syntax
Directives
import { ref, reactive } from 'vue';
const message = ref('Hello Vue');
const isActive = ref(true);
const hasError = ref(false);
const items = ref(['Apple', 'Banana', 'Cherry']);
const user = ref({ name: 'Alice', email: 'alice@example.com' });
const formData = reactive({
username: '',
agree: false,
gender: 'male',
interests: [] as string[]
});
{{ message }}
Bold'">
Active
Error
Inactive
Visible when active
{{ index + 1 }}. {{ item }}
{{ key }}: {{ value }}
Toggle
Submit
Reading
Gaming
Coding
Event Modifiers
Submit
Click me
...
...
Click once
Lifecycle Hooks
import {
onBeforeMount,
onMounted,
onBeforeUpdate,
onUpdated,
onBeforeUnmount,
onUnmounted,
onErrorCaptured
} from 'vue';
// Before component is mounted
onBeforeMount(() => {
console.log('Component about to mount');
});
// After component is mounted (DOM is ready)
onMounted(() => {
console.log('Component mounted');
// Good place for API calls, DOM manipulation
fetchData();
});
// Before component updates due to reactive changes
onBeforeUpdate(() => {
console.log('Component about to update');
});
// After component updates
onUpdated(() => {
console.log('Component updated');
// Careful: can cause infinite loops if you update state here
});
// Before component unmounts
onBeforeUnmount(() => {
console.log('Component about to unmount');
// Clean up subscriptions, timers, etc.
});
// After component unmounts
onUnmounted(() => {
console.log('Component unmounted');
});
// Error handling
onErrorCaptured((err, instance, info) => {
console.error('Error captured:', err, info);
return false; // Prevent propagation
});
async function fetchData() {
const response = await fetch('/api/data');
const data = await response.json();
console.log(data);
}
Provide/Inject (Dependency Injection)
import { ref, provide } from 'vue';
import type { InjectionKey } from 'vue';
interface Theme {
primary: string;
secondary: string;
}
// Create typed injection key
export const ThemeKey: InjectionKey = Symbol('theme');
const theme = ref({
primary: '#007bff',
secondary: '#6c757d'
});
// Provide to all descendants
provide(ThemeKey, theme.value);
provide('userPermissions', ['read', 'write']);
import { inject } from 'vue';
import { ThemeKey } from './Parent.vue';
// Inject with type safety
const theme = inject(ThemeKey);
const permissions = inject('userPermissions', []);
// With default value
const config = inject('config', { debug: false });
Themed content
Vue Router Integration
Basic Setup
// router/index.ts
import { createRouter, createWebHistory } from 'vue-router';
import type { RouteRecordRaw } from 'vue-router';
import Home from '@/views/Home.vue';
import About from '@/views/About.vue';
const routes: RouteRecordRaw[] = [
{
path: '/',
name: 'Home',
component: Home
},
{
path: '/about',
name: 'About',
component: About
},
{
path: '/user/:id',
name: 'User',
component: () => import('@/views/User.vue'), // Lazy loading
props: true // Pass route params as props
},
{
path: '/dashboard',
name: 'Dashboard',
component: () => import('@/views/Dashboard.vue'),
meta: { requiresAuth: true }
},
{
path: '/:pathMatch(.*)*',
name: 'NotFound',
component: () => import('@/views/NotFound.vue')
}
];
const router = createRouter({
history: createWebHistory(import.meta.env.BASE_URL),
routes
});
export default router;
Navigation and Route Access
import { useRouter, useRoute } from 'vue-router';
import { computed } from 'vue';
const router = useRouter();
const route = useRoute();
// Access route params
const userId = computed(() => route.params.id);
const querySearch = computed(() => route.query.search);
// Programmatic navigation
function goToUser(id: number) {
router.push({ name: 'User', params: { id } });
}
function goToAbout() {
router.push('/about');
}
function goBack() {
router.back();
}
function replaceRoute() {
router.replace({ name: 'Home' }); // No history entry
}
Home
About
User 123
Dashboard
Go to User 456
Back
Current user ID: {{ userId }}
Search query: {{ querySearch }}
Navigation Guards
// router/index.ts
import { createRouter } from 'vue-router';
const router = createRouter({
// ... routes
});
// Global before guard
router.beforeEach((to, from, next) => {
const isAuthenticated = checkAuth();
if (to.meta.requiresAuth && !isAuthenticated) {
next({ name: 'Login', query: { redirect: to.fullPath } });
} else {
next();
}
});
// Global after hook
router.afterEach((to, from) => {
document.title = `${to.meta.title || 'App'} - My App`;
});
// Per-route guard
const routes = [
{
path: '/admin',
component: Admin,
beforeEnter: (to, from, next) => {
if (isAdmin()) {
next();
} else {
next('/unauthorized');
}
}
}
];
import { onBeforeRouteLeave, onBeforeRouteUpdate } from 'vue-router';
// Confirm before leaving
onBeforeRouteLeave((to, from) => {
if (hasUnsavedChanges.value) {
const answer = window.confirm('You have unsaved changes. Leave anyway?');
return answer;
}
});
// React to route changes (same component, different params)
onBeforeRouteUpdate((to, from) => {
console.log(`Route updated from ${from.params.id} to ${to.params.id}`);
fetchData(to.params.id);
});
Pinia State Management
Store Definition
// stores/counter.ts
import { defineStore } from 'pinia';
import { ref, computed } from 'vue';
// Composition API style (recommended)
export const useCounterStore = defineStore('counter', () => {
// State
const count = ref(0);
const name = ref('Counter Store');
// Getters (computed)
const doubleCount = computed(() => count.value * 2);
const isPositive = computed(() => count.value > 0);
// Actions
function increment() {
count.value++;
}
function decrement() {
count.value--;
}
async function fetchCount() {
const response = await fetch('/api/count');
const data = await response.json();
count.value = data.count;
}
return {
count,
name,
doubleCount,
isPositive,
increment,
decrement,
fetchCount
};
});
// Options API style (alternative)
export const useUserStore = defineStore('user', {
state: () => ({
user: null as User | null,
token: ''
}),
getters: {
isLoggedIn: (state) => state.user !== null,
fullName: (state) => state.user ? `${state.user.firstName} ${state.user.lastName}` : ''
},
actions: {
async login(email: string, password: string) {
const response = await fetch('/api/login', {
method: 'POST',
body: JSON.stringify({ email, password })
});
const data = await response.json();
this.user = data.user;
this.token = data.token;
},
logout() {
this.user = null;
this.token = '';
}
}
});
Using Stores in Components
import { useCounterStore } from '@/stores/counter';
import { useUserStore } from '@/stores/user';
import { storeToRefs } from 'pinia';
const counterStore = useCounterStore();
const userStore = useUserStore();
// Get reactive refs from store
const { count, doubleCount } = storeToRefs(counterStore);
const { user, isLoggedIn } = storeToRefs(userStore);
// Actions can be destructured directly (they're not reactive)
const { increment, decrement } = counterStore;
// Access state directly
console.log(counterStore.count);
// Modify state directly
counterStore.count++;
// Or use $patch for multiple changes
counterStore.$patch({
count: 10,
name: 'Updated Counter'
});
// Reset state
counterStore.$reset();
Count: {{ count }} (Double: {{ doubleCount }})
+
-
Welcome, {{ user?.firstName }}!
Logout
Store Composition (Accessing Other Stores)
// stores/cart.ts
import { defineStore } from 'pinia';
import { ref, computed } from 'vue';
import { useUserStore } from './user';
export const useCartStore = defineStore('cart', () => {
const items = ref([]);
const userStore = useUserStore();
const total = computed(() =>
items.value.reduce((sum, item) => sum + item.price * item.quantity, 0)
);
const canCheckout = computed(() =>
userStore.isLoggedIn && items.value.length > 0
);
async function checkout() {
if (!canCheckout.value) return;
await fetch('/api/checkout', {
method: 'POST',
headers: {
Authorization: `Bearer ${userStore.token}`
},
body: JSON.stringify({ items: items.value })
});
items.value = [];
}
return { items, total, canCheckout, checkout };
});
Composables (Reusable Logic)
Custom Composables
// composables/useFetch.ts
import { ref, type Ref } from 'vue';
interface UseFetchOptions {
immediate?: boolean;
}
export function useFetch(url: string, options: UseFetchOptions = {}) {
const data = ref(null) as Ref;
const error = ref(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;
}
}
if (options.immediate) {
execute();
}
return { data, error, loading, execute };
}
// composables/useLocalStorage.ts
import { ref, watch, type Ref } from 'vue';
export function useLocalStorage(key: string, defaultValue: T): Ref {
const storedValue = localStorage.getItem(key);
const data = ref(
storedValue ? JSON.parse(storedValue) : def
…
## Source & license
This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.
- **Author:** [bobmatnyc](https://github.com/bobmatnyc)
- **Source:** [bobmatnyc/claude-mpm-skills](https://github.com/bobmatnyc/claude-mpm-skills)
- **License:** MIT
Install and usage instructions live in the source repository linked above.
Reviews
No reviews yet, be the first.
Write a review
Versions
- v0.1.0 Imported from the upstream source.