Skip to main content
vue-typescript Provides Vue 3 + TypeScript patterns โ Composition API, script setup, Pinia, Vue Router, composables. Use when building Vue apps or wiring reactive state.
Jump to install Skills Marketplace Discover and explore AI skills built by the community.
Install with Codex or Claude Copy this prompt, paste it into Codex, Claude, or another assistant, and let it review the skill page and install it for you.
Copy promptShow prompt details A direct command skips the review prompt. Inspect the source before running it.
npx skills add https://github.com/MadAppGang/magus --skill vue-typescriptThe command stays on one line. Scroll horizontally to inspect it before copying.
Prefer a local copy? Download the files currently available to SkillsMP.
Download Zip Downloading... More from this repository
SOC
Based on SOC occupation classification
name vue-typescript description Provides Vue 3 + TypeScript patterns โ Composition API, script setup, Pinia, Vue Router, composables. Use when building Vue apps or wiring reactive state. disable-model-invocation true
Vue 3 + TypeScript Patterns
Overview
Modern Vue 3 patterns with TypeScript and Composition API for building robust applications.
Component Patterns
Script Setup with TypeScript
<script setup lang="ts">
import { ref, computed } from 'vue';
interface Props {
title: string;
count?: number;
}
const props = withDefaults(defineProps<Props>(), {
count: 0,
});
const emit = defineEmits<{
(e: 'update', value: number): void;
(e: 'close'): void;
}>();
const localCount = ref(props.count);
const doubled = computed(() => localCount.value * 2);
function increment() {
localCount.value++;
emit('update', localCount.value);
}
</script>
<template>
<div>
<h2>{{ title }}</h2>
<p>Count: {{ localCount }} (doubled: {{ doubled }})</p>
<button @click="increment">Increment</button>
</div>
</template>
Generic Components
<script setup lang="ts" generic="T">
interface Props {
items: T[];
selected?: T;
}
const props = defineProps<Props>();
const emit = defineEmits<{
(e: 'select', item: T): void;
}>();
</script>
<template>
<ul>
<li
v-for="(item, index) in items"
:key="index"
:class="{ selected: item === selected }"
@click="emit('select', item)"
>
<slot :item="item" />
</li>
</ul>
</template>
Composables
Basic Composable
import { ref, computed } ;
{
?: ;
?: ;
?: ;
}
( ) {
{ initial = , min, max } = options;
count = (initial);
= ( ) => {
(max === || count. < max) {
count. ++;
}
};
= ( ) => {
(min === || count. > min) {
count. --;
}
};
= ( ) => {
count. = initial;
};
isAtMin = ( min !== && count. <= min);
isAtMax = ( max !== && count. >= max);
{
count,
increment,
decrement,
reset,
isAtMin,
isAtMax,
};
}
from
'vue'
interface
UseCounterOptions
initial
number
min
number
max
number
export
function
useCounter
options : UseCounterOptions = {}
const
0
const
ref
const
increment
if
undefined
value
value
const
decrement
if
undefined
value
value
const
reset
value
const
computed
() =>
undefined
value
const
computed
() =>
undefined
value
return
Data Fetching Composable
import { ref, watchEffect, type Ref } from 'vue' ;
interface UseFetchReturn <T> {
data : Ref <T | null >;
error : Ref <Error | null >;
loading : Ref <boolean >;
refetch : () => Promise <void >;
}
export function useFetch<T>(url : string | Ref <string >): UseFetchReturn <T> {
const data = ref<T | null >(null ) as Ref <T | null >;
const error = ref<Error | null >(null );
const loading = ref (false );
async function fetchData ( ) {
const urlValue = typeof url === 'string' ? url : url.value ;
loading.value = true ;
error.value = null ;
try {
const response = await fetch (urlValue);
if (!response.ok ) throw new Error (`HTTP ${response.status} ` );
data.value = await response.json ();
} catch (e) {
error.value = e instanceof Error ? e : new Error ('Unknown error' );
} finally {
loading.value = false ;
}
}
watchEffect (() => {
fetchData ();
});
return { data, error, loading, refetch : fetchData };
}
State Management (Pinia)
Store Definition
import { defineStore } from 'pinia' ;
import { ref, computed } from 'vue' ;
import type { User } from '@/types' ;
export const useUserStore = defineStore ('user' , () => {
const users = ref<User []>([]);
const currentUserId = ref<string | null >(null );
const loading = ref (false );
const currentUser = computed (() =>
users.value .find (u => u.id === currentUserId.value )
);
const userCount = computed (() => users.value .length );
async function fetchUsers ( ) {
loading.value = true ;
try {
const response = await api.getUsers ();
users.value = response.data ;
} finally {
loading.value = false ;
}
}
function setCurrentUser (userId : string ) {
currentUserId.value = userId;
}
return {
users,
currentUserId,
loading,
currentUser,
userCount,
fetchUsers,
setCurrentUser,
};
});
Using Store in Components <script setup lang="ts">
import { storeToRefs } from 'pinia';
import { useUserStore } from '@/stores/userStore';
const store = useUserStore();
// Destructure reactive state
const { users, loading, currentUser } = storeToRefs(store);
// Actions don't need storeToRefs
const { fetchUsers, setCurrentUser } = store;
onMounted(() => {
fetchUsers();
});
</script>
Router with TypeScript
Route Definitions
import { createRouter, createWebHistory, 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 ,
},
{
path : '/admin' ,
name : 'admin' ,
component : () => import ('@/views/AdminView.vue' ),
meta : { requiresAuth : true },
},
];
export const router = createRouter ({
history : createWebHistory (),
routes,
});
Typed Route Params <script setup lang="ts">
import { useRoute, useRouter } from 'vue-router';
const route = useRoute();
const router = useRouter();
// Typed param access
const userId = computed(() => route.params.id as string);
function goToUser(id: string) {
router.push({ name: 'user', params: { id } });
}
</script>
Form Handling
VeeValidate with Zod <script setup lang="ts">
import { useForm } from 'vee-validate';
import { toTypedSchema } from '@vee-validate/zod';
import { z } from 'zod';
const schema = toTypedSchema(
z.object({
email: z.string().email('Invalid email'),
password: z.string().min(8, 'Password must be at least 8 characters'),
})
);
const { handleSubmit, errors, defineField } = useForm({
validationSchema: schema,
});
const [email, emailAttrs] = defineField('email');
const [password, passwordAttrs] = defineField('password');
const onSubmit = handleSubmit((values) => {
console.log('Form submitted:', values);
});
</script>
<template>
<form @submit="onSubmit">
<input v-model="email" v-bind="emailAttrs" type="email" />
<span v-if="errors.email">{{ errors.email }}</span>
<input v-model="password" v-bind="passwordAttrs" type="password" />
<span v-if="errors.password">{{ errors.password }}</span>
<button type="submit">Submit</button>
</form>
</template>
Provide/Inject with TypeScript
import type { InjectionKey } from 'vue' ;
interface ThemeContext {
theme : Ref <'light' | 'dark' >;
toggleTheme : () => void ;
}
export const themeKey : InjectionKey <ThemeContext > = Symbol ('theme' );
const theme = ref<'light' | 'dark' >('light' );
const toggleTheme = ( ) => {
theme.value = theme.value === 'light' ? 'dark' : 'light' ;
};
provide (themeKey, { theme, toggleTheme });
const themeContext = inject (themeKey);
if (!themeContext) throw new Error ('Theme context not provided' );
Performance
Lazy Loading Components import { defineAsyncComponent } from 'vue' ;
const AsyncModal = defineAsyncComponent (() =>
import ('@/components/Modal.vue' )
);
const AsyncModalWithOptions = defineAsyncComponent ({
loader : () => import ('@/components/Modal.vue' ),
loadingComponent : LoadingSpinner ,
errorComponent : ErrorDisplay ,
delay : 200 ,
timeout : 3000 ,
});
File Structure src/
โโโ components/
โ โโโ common/
โ โ โโโ BaseButton.vue
โ โ โโโ BaseInput.vue
โ โโโ layout/
โ โ โโโ AppHeader.vue
โ โ โโโ AppSidebar.vue
โ โโโ features/
โ โโโ users/
โโโ composables/
โ โโโ useAuth.ts
โ โโโ useFetch.ts
โโโ stores/
โ โโโ userStore.ts
โ โโโ appStore.ts
โโโ views/
โ โโโ HomeView.vue
โ โโโ UserView.vue
โโโ router/
โ โโโ index.ts
โโโ types/
โ โโโ index.ts
โโโ App.vue
Vue 3 + TypeScript patterns for modern frontend development