Skip to content
← All questions
🌱 Beginner

How do you declare props with TypeScript in Vue 3?

🔷 TypeScript🧩 Components

In Vue 3 with <script setup>, you declare props using defineProps, a compiler macro that Vue processes at build time. You don't import it; it's available automatically inside <script setup>.

Basic declaration

Pass a TypeScript type or interface to defineProps as a generic parameter. Vue generates the runtime prop checks from your types:

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

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

<template>
  <h2>{{ props.title }}</h2>
  <span>{{ props.count }} items</span>
</template>
Open in Vue Playground

Optional props (marked with ?) are allowed to be undefined. Required props cause a runtime warning if the parent doesn't provide them.

Default values with withDefaults

Since you can't use the default option with the type-based syntax, Vue provides withDefaults:

vue
<script setup lang="ts">
interface Props {
  title: string
  count?: number
  tags?: string[]
}

const props = withDefaults(defineProps<Props>(), {
  count: 0,
  tags: () => ['vue']
})
// props.count is now `number` (not `number | undefined`)
// props.tags is now `string[]` (not `string[] | undefined`)
</script>
Open in Vue Playground

For arrays and objects, use a factory function (() => ['vue']), same reason as data() in the Options API: each component instance needs its own copy.

Reactive destructure (3.5+)

Since Vue 3.5, you can destructure props while keeping reactivity. Default values use standard JavaScript syntax:

vue
<script setup lang="ts">
const {
  title,
  count = 0,
  tags = ['vue']
} = defineProps<{
  title: string
  count?: number
  tags?: string[]
}>()
</script>

<template>
  <!-- `title` and `count` are reactive — no `.value` needed in the template -->
  <h2>{{ title }}</h2>
</template>
Open in Vue Playground

This replaces withDefaults for most cases. The destructured variables are reactive in the template and in watch/computed, but if you pass them to a plain function, they're just values at that point.

Complex types

Props can use unions, literal types, and imported interfaces:

vue
<script setup lang="ts">
import type { User } from '@/types'

interface Props {
  user: User
  status: 'active' | 'inactive' | 'pending'
  onUpdate?: (user: User) => void
}

const props = defineProps<Props>()
</script>
Open in Vue Playground

There are limitations on the types you can use. For example, you can't use conditional types or imported type aliases that resolve to complex generics in some cases.

Why not the runtime syntax?

Vue also supports a runtime object syntax (defineProps({ title: String })) that doesn't need TypeScript. The type-based syntax is better because:

  1. IDE support: types flow into the template, autocomplete works, refactoring catches all usages
  2. No duplication: your TypeScript types ARE the prop definitions, not a parallel declaration
  3. Stricter: union types, literal types, and complex shapes that the runtime syntax can't express

See also: What are the limitations of defineProps types? · How do you emit events with TypeScript? · What is the difference between props and state? · Why does Vue warn when you mutate props?

References

Released under the MIT License.