Vue with TypeScript — Typing Components and Stores for Applications That Don't Break
> cd .. / HUB_EDITORIALE
Sviluppo di siti web

Vue with TypeScript — Typing Components and Stores for Applications That Don't Break

[2026-07-25] Author: Ing. Calogero Bono
> share
Zenithby Meteora Web The operating system for your business. Social, clients, bookings and invoices in one platform. Gyms, barbers, professionals. Discover Zenith Free demo · no card

Have you ever spent hours debugging because a Vue component received a prop with the wrong type? Or a Pinia store changed structure without warning and your app exploded in production? At Meteora Web, we've been building Vue applications for years, and we've seen the cost of missing types: lost hours, sneaky bugs, unhappy clients. TypeScript isn't a trend — it's a guarantee that your code behaves as expected. In this guide, we show you how to bring TypeScript into your Vue 3 components and Pinia stores, with ready-to-copy examples.

Why TypeScript with Vue saves time and money?

TypeScript adds a static type layer that catches errors before deployment. If a prop receives a number instead of a string, or a store returns an object with a missing property, TypeScript flags it during development, not in production. For a small or medium business, this means fewer bugs, less debugging time, and more confidence in releases. We apply it on every Vue project — from small internal tools to multi-client platforms. The return is immediate: code that is easier to maintain and evolve.

Common errors TypeScript prevents

  • Prop passed with wrong type (e.g., string instead of number) — TypeScript catches it instantly.
  • Unsafed emit: if a child component emits an event with a wrong payload, the parent receives it incorrectly. TypeScript types the emit.
  • Fragile store structure: changing a field in a Pinia store without updating components used to cause silent errors. With TypeScript the compiler yells.
  • Untyped refs and reactive: if you write const x = ref([]), TypeScript infers Ref<never[]> and restricts operations.

How to type Vue 3 component props with Composition API?

The Composition API allows writing logic in setup functions, but props must be declared and typed. There are two approaches: using an explicit interface or inline declaration. We prefer the first — more readable and reusable.

Sponsored Protocol

Interface approach

// components/UserCard.vue
<script setup lang="ts">
interface Props {
  name: string;
  age: number;
  isActive?: boolean; // optional
}

const props = defineProps<Props>();

// Now props.name is string, props.age is number, props.isActive is boolean | undefined
</script>

With defineProps<Props>(), TypeScript checks every usage. If elsewhere you pass name={42}, the compiler complains. Note: defineProps with generics works only with <script setup>. If using Options API, you must fall back to PropType.

Sponsored Protocol

Props with default values

interface Props {
  title: string;
  count?: number;
}

const props = withDefaults(defineProps<Props>(), {
  count: 0
});
// count is always number even if not passed

How to type emits with TypeScript?

Outgoing events from a component must also be typed to prevent the parent from receiving malformed data. Declare them with defineEmits.

// components/UserForm.vue
<script setup lang="ts">
interface Emits {
  (e: 'submit', data: { name: string; email: string }): void;
  (e: 'cancel'): void;
}

const emit = defineEmits<Emits>();

// Usage
emit('submit', { name: 'Mario', email: 'mario@example.com' }); // OK
emit('submit', { name: 123 }); // ERROR: name must be string
</script>

Alternative: with TypeScript 5.x you can use defineEmits<{ submit: [data: { name: string; email: string }]; cancel: [] }>(). Choose whichever you like.

How to type ref, reactive and computed?

We often see const items = ref([]) — TypeScript infers Ref<never[]>, and then items.value.push('test') fails because it expects never. Always declare the type explicitly.

Sponsored Protocol

Typed ref

import { ref } from 'vue';

const counter = ref<number>(0); // Ref<number>
const users = ref<User[]>([]); // User is an interface

interface User {
  id: number;
  name: string;
}

Typed reactive

reactive() infers type from the passed object. To explicitly type, use an interface.

interface FormState {
  email: string;
  password: string;
  remember: boolean;
}

const form = reactive<FormState>({
  email: '',
  password: '',
  remember: false
});

Typed computed

The type is inferred automatically from the return value. But if you want to force it (useful for generics), use the generic parameter.

const double = computed<number>(() => counter.value * 2);

How to type a Pinia store with TypeScript?

Pinia is natively TypeScript-friendly. The best approach is using the Setup Store (or Option Store) with explicit types. The Setup Store integrates perfectly with Composition API.

Typed Setup Store

// stores/cart.ts
import { defineStore } from 'pinia';
import { ref, computed } from 'vue';

interface CartItem {
  productId: number;
  name: string;
  price: number;
  quantity: number;
}

export const useCartStore = defineStore('cart', () => {
  const items = ref<CartItem[]>([]);

  const total = computed(() => {
    return items.value.reduce((sum, item) => sum + item.price * item.quantity, 0);
  });

  function addItem(item: CartItem) {
    const existing = items.value.find(i => i.productId === item.productId);
    if (existing) {
      existing.quantity += item.quantity;
    } else {
      items.value.push(item);
    }
  }

  return { items, total, addItem };
});

In a component:

Sponsored Protocol

const store = useCartStore();
store.addItem({ productId: 1, name: 'T-shirt', price: 25, quantity: 2 }); // OK
store.addItem({ productId: 'abc' }); // ERROR

Typing getters and actions with advanced generics

If you have getters returning types dependent on state, TypeScript infers them automatically. You can also export a type for the whole store.

export type CartStore = ReturnType<typeof useCartStore>;

Handy for passing the store as a prop or via inject.

Sponsored Protocol

What to do next: checklist for adding TypeScript to an existing Vue project

  1. Install TypeScript and Vue plugins: npm install -D typescript @vue/tsconfig and configure tsconfig.json as per official guide.
  2. Rename files: from .js to .ts and Vue components with lang="ts" on script.
  3. Type props first: start with the most used components. Use defineProps<Props>().
  4. Type Pinia stores: define interfaces for data and add types to ref and reactive.
  5. Enable strict mode: in tsconfig.json set "strict": true for maximum safety.
  6. Run vue-tsc --noEmit: to check for type errors before build.
  7. Integrate in CI: add a type-checking job to prevent untyped code from reaching production.

We, at Meteora Web, followed these steps on over a dozen projects — the result is more stable code and faster code reviews. TypeScript is not overhead; it's an investment that pays off at the first bug-free deploy.

For a deeper dive into the Vue 3 ecosystem with Composition API, check our pillar guide on Vue.js 3 and Composition API.

External resource: Official Vue TypeScript documentation.

> share
Ing. Calogero Bono

> AUTHOR_EXTRACTED

Ing. Calogero Bono

Ingegnere informatico, fondatore di Meteora Web e Zenith OS. System administrator e progettista di piattaforme, app e CMS proprietari, con esperienza in sviluppo full-stack, marketing digitale ed ecosistema Google.
[ Read Full Dossier ]

> METEORA_WEB // DIGITAL AGENCY

We build the digital presence your business deserves.

Websites, social media, online advertising, e-commerce and high-performance hosting, engineered with method by computer engineers in Sciacca, for all of Italy.

> MW_JOURNAL

> READ_ALL()