Next.js starter your AI actually understands. Ship internal tools in days not weeks. Pre-order $199 $499 → [Get it now]

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.


Speed up your responsive apps and websites with fully-featured, ready-to-use open-source admin panel templates—free to use and built for efficiency.


About the Author