How to use defineComponent in Vue 3
defineComponent is a Vue 3 utility function that enables full TypeScript support by providing proper type inference for component options and the setup function.
As the creator of CoreUI with Vue development experience since 2014, I use defineComponent in every TypeScript Vue component because it makes IDEs correctly infer prop types, emitted events, and the return value of setup.
Without it, TypeScript treats the component object as a plain object and loses the Vue-specific type information.
For <script setup> components you don’t need it — it’s most valuable when writing components with explicit setup functions or Options API in TypeScript files.
Wrap your component definition with defineComponent to enable type inference.
// UserCard.ts
import { defineComponent, PropType, computed } from 'vue'
interface User {
id: number
name: string
role: 'admin' | 'user'
}
export default defineComponent({
name: 'UserCard',
props: {
user: {
type: Object as PropType<User>,
required: true
},
showRole: {
type: Boolean,
default: true
}
},
setup(props) {
const isAdmin = computed(() => props.user.role === 'admin')
return { isAdmin }
}
})
PropType<User> tells TypeScript the exact shape of the user prop. Inside setup, props.user is fully typed — accessing props.user.role gives autocomplete and type checking. Without defineComponent, props would be typed as Readonly<{[key: string]: any}>.
Using defineComponent with Composition API
Full setup function with typed emits.
import { defineComponent, PropType, ref, computed } from 'vue'
export default defineComponent({
name: 'Counter',
props: {
initialValue: {
type: Number,
default: 0
},
max: {
type: Number as PropType<number>,
default: Infinity
}
},
emits: {
change: (value: number) => typeof value === 'number',
reset: null
},
setup(props, { emit }) {
const count = ref(props.initialValue)
const canIncrement = computed(() => count.value < props.max)
function increment() {
if (canIncrement.value) {
count.value++
emit('change', count.value)
}
}
function reset() {
count.value = props.initialValue
emit('reset')
}
return { count, canIncrement, increment, reset }
}
})
Defining emits as an object with validator functions gives TypeScript knowledge of what each event emits. Calling emit('change', 'wrong-type') would cause a TypeScript error. The validator function also runs at runtime in development to warn about invalid emit calls.
defineComponent with Options API
Improve type inference in Options API components.
import { defineComponent } from 'vue'
export default defineComponent({
name: 'LoginForm',
data() {
return {
email: '',
password: '',
loading: false
}
},
computed: {
isValid(): boolean {
return this.email.includes('@') && this.password.length >= 8
}
},
methods: {
async submit(): Promise<void> {
if (!this.isValid) return
this.loading = true
// submit logic
this.loading = false
}
}
})
With defineComponent, this inside data, computed, and methods is correctly typed. this.email is inferred as string, this.loading as boolean, and this.isValid as boolean.
Best Practice Note
In CoreUI Vue components we use <script setup> for new components and defineComponent when writing components as separate .ts files or when the Options API is more readable for the use case. For <script setup>, TypeScript support is built in — defineProps, defineEmits, and defineExpose macros replace the need for defineComponent. See how to use setup() in Vue 3 for a comparison of both approaches.



