How to define props in Vue 3 with defineProps
defineProps is the <script setup> compiler macro for declaring component props in Vue 3, replacing the props option from the Options API with a more concise and TypeScript-friendly syntax.
As the creator of CoreUI with Vue development experience since 2014, I use defineProps in every <script setup> component because it gives both runtime validation and full TypeScript type inference without any extra configuration.
Choosing between the runtime syntax and the TypeScript generic syntax depends on whether you need complex default values — the TypeScript syntax offers better type safety but requires withDefaults for defaults.
Both approaches are idiomatic and supported — choose the one that fits your project’s TypeScript configuration.
Declare props with runtime validation syntax.
<script setup>
import { computed } from 'vue'
const props = defineProps({
title: {
type: String,
required: true
},
count: {
type: Number,
default: 0
},
variant: {
type: String,
default: 'primary',
validator: (val) => ['primary', 'secondary', 'danger'].includes(val)
},
items: {
type: Array,
default: () => []
}
})
const label = computed(() => `${props.title} (${props.count})`)
</script>
<template>
<div :class="`card card--${variant}`">
<h3>{{ label }}</h3>
<ul>
<li v-for="item in items" :key="item.id">{{ item.name }}</li>
</ul>
</div>
</template>
validator is a function that returns true for valid values. Vue logs a warning in development when an invalid value is passed. Note that Array and Object defaults must be factory functions (() => []) to avoid sharing the same instance across components.
TypeScript Generic Syntax
Use TypeScript interfaces for full type safety.
<script setup lang="ts">
import { computed } from 'vue'
interface User {
id: number
name: string
email: string
}
interface Props {
user: User
isSelected?: boolean
size?: 'sm' | 'md' | 'lg'
}
const props = withDefaults(defineProps<Props>(), {
isSelected: false,
size: 'md'
})
const displayName = computed(() => props.user.name.split(' ')[0])
</script>
defineProps<Props>() infers types from the TypeScript interface. withDefaults adds default values — required when using the TypeScript syntax because TypeScript interfaces cannot define runtime defaults directly. Props without defaults are required.
Accessing Props in Templates
Props are available directly in the template without props. prefix.
<template>
<!-- Access props directly in template -->
<div>
<p>{{ title }}</p>
<p>{{ count }}</p>
<!-- But in script, use props.title -->
</div>
</template>
<script setup>
const props = defineProps({ title: String, count: Number })
// In script: always use props.title, props.count
console.log(props.title)
</script>
Vue automatically unwraps defineProps return values in the template. In <script setup>, always access props through the returned object (props.title) to maintain reactivity.
Best Practice Note
In CoreUI Vue components we use the TypeScript generic syntax (defineProps<Props>()) for all typed components because it provides better IDE autocompletion, stricter type checking, and eliminates the need to type both the validation object and the TypeScript interface separately. See how to define emits in Vue 3 with defineEmits for the companion emit declaration pattern.



