Skip to content

Props

This page assumes you've already read the Components Basics. Read that first if you are new to components.

Props Declaration

Vue components require explicit props declaration so that Vue knows what external props passed to the component should be treated as fallthrough attributes (which will be discussed in its dedicated section).

In SFCs using <script setup>, props can be declared using the defineProps() macro:

vue
<script setup>
const props = defineProps(['foo'])

console.log(props.foo)
</script>

In non-<script setup> components, props are declared using the props option:

js
export default {
  props: ['foo'],
  setup(props) {
    // setup() receives props as the first argument.
    console.log(props.foo)
  }
}

Notice the argument passed to defineProps() is the same as the value provided to the props options: the same props options API is shared between the two declaration styles.

Props are declared using the props option:

js
export default {
  props: ['foo'],
  created() {
    // props are exposed on `this`
    console.log(this.foo)
  }
}

In addition to declaring props using an array of strings, we can also use the object syntax:

js
export default {
  props: {
    title: String,
    likes: Number
  }
}
js
// in <script setup>
defineProps({
  title: String,
  likes: Number
})
js
// in non-<script setup>
export default {
  props: {
    title: String,
    likes: Number
  }
}

For each property in the object declaration syntax, the key is the name of the prop, while the value should be the constructor function of the expected type.

This not only documents your component, but will also warn other developers using your component in the browser console if they pass the wrong type. We will discuss more details about prop validation further down this page.

If you are using TypeScript with <script setup>, it's also possible to declare props using pure type annotations:

vue
<script setup lang="ts">
defineProps<{
  title?: string
  likes?: number
}>()
</script>

More details: Typing Component Props

Prop Passing Details

Prop Name Casing

We declare long prop names using camelCase because this avoids having to use quotes when using them as property keys, and allows us to reference them directly in template expressions because they are valid JavaScript identifiers:

js
defineProps({
  greetingMessage: String
})
js
export default {
  props: {
    greetingMessage: String
  }
}
template
<span>{{ greetingMessage }}</span>

Technically, you can also use camelCase when passing props to a child component (except in DOM templates). However, the convention is using kebab-case in all cases to align with HTML attributes:

template
<MyComponent greeting-message="hello" />

We use PascalCase for component tags when possible because it improves template readability by differentiating Vue components from native elements. However, there isn't as much practical benefit in using camelCase when passing props, so we choose to follow each language's conventions.

Static vs. Dynamic Props

So far, you've seen props passed as static values, like in:

template
<BlogPost title="My journey with Vue" />

You've also seen props assigned dynamically with v-bind or its : shortcut, such as in:

template
<!-- Dynamically assign the value of a variable -->
<BlogPost :title="post.title" />

<!-- Dynamically assign the value of a complex expression -->
<BlogPost :title="post.title + ' by ' + post.author.name" />

Passing Different Value Types

In the two examples above, we happen to pass string values, but any type of value can be passed to a prop.

Number

template
<!-- Even though `42` is static, we need v-bind to tell Vue that -->
<!-- this is a JavaScript expression rather than a string.       -->
<BlogPost :likes="42" />

<!-- Dynamically assign to the value of a variable. -->
<BlogPost :likes="post.likes" />

Boolean

template
<!-- Including the prop with no value will imply `true`. -->
<BlogPost is-published />

<!-- Even though `false` is static, we need v-bind to tell Vue that -->
<!-- this is a JavaScript expression rather than a string.          -->
<BlogPost :is-published="false" />

<!-- Dynamically assign to the value of a variable. -->
<BlogPost :is-published="post.isPublished" />

Array

template
<!-- Even though the array is static, we need v-bind to tell Vue that -->
<!-- this is a JavaScript expression rather than a string.            -->
<BlogPost :comment-ids="[234, 266, 273]" />

<!-- Dynamically assign to the value of a variable. -->
<BlogPost :comment-ids="post.commentIds" />

Object

template
<!-- Even though the object is static, we need v-bind to tell Vue that -->
<!-- this is a JavaScript expression rather than a string.             -->
<BlogPost
  :author="{
    name: 'Veronica',
    company: 'Veridian Dynamics'
  }"
 />

<!-- Dynamically assign to the value of a variable. -->
<BlogPost :author="post.author" />

Binding Multiple Properties Using an Object

If you want to pass all the properties of an object as props, you can use v-bind without an argument (v-bind instead of :prop-name). For example, given a post object:

js
export default {
  data() {
    return {
      post: {
        id: 1,
        title: 'My Journey with Vue'
      }
    }
  }
}
js
const post = {
  id: 1,
  title: 'My Journey with Vue'
}

The following template:

template
<BlogPost v-bind="post" />

Will be equivalent to:

template
<BlogPost :id="post.id" :title="post.title" />

One-Way Data Flow

All props form a one-way-down binding between the child property and the parent one: when the parent property updates, it will flow down to the child, but not the other way around. This prevents child components from accidentally mutating the parent's state, which can make your app's data flow harder to understand.

In addition, every time the parent component is updated, all props in the child component will be refreshed with the latest value. This means you should not attempt to mutate a prop inside a child component. If you do, Vue will warn you in the console:

js
const props = defineProps(['foo'])

// ❌ warning, props are readonly!
props.foo = 'bar'
js
export default {
  props: ['foo'],
  created() {
    // ❌ warning, props are readonly!
    this.foo = 'bar'
  }
}

There are usually two cases where it's tempting to mutate a prop:

  1. The prop is used to pass in an initial value; the child component wants to use it as a local data property afterwards. In this case, it's best to define a local data property that uses the prop as its initial value:

    js
    const props = defineProps(['initialCounter'])
    
    // counter only uses props.initialCounter as the initial value;
    // it is disconnected from future prop updates.
    const counter = ref(props.initialCounter)
    js
    export default {
      props: ['initialCounter'],
      data() {
        return {
          // counter only uses this.initialCounter as the initial value;
          // it is disconnected from future prop updates.
          counter: this.initialCounter
        }
      }
    }
  2. The prop is passed in as a raw value that needs to be transformed. In this case, it's best to define a computed property using the prop's value:

    js
    const props = defineProps(['size'])
    
    // computed property that auto-updates when the prop changes
    const normalizedSize = computed(() => props.size.trim().toLowerCase())
    js
    export default {
      props: ['size'],
      computed: {
        // computed property that auto-updates when the prop changes
        normalizedSize() {
          return this.size.trim().toLowerCase()
        }
      }
    }

Mutating Object / Array Props

When objects and arrays are passed as props, while the child component cannot mutate the prop binding, it will be able to mutate the object or array's nested properties. This is because in JavaScript objects and arrays are passed by reference, and it is unreasonably expensive for Vue to prevent such mutations.

The main drawback of such mutations is that it allows the child component to affect parent state in a way that isn't obvious to the parent component, potentially making it more difficult to reason about the data flow in the future. As a best practice, you should avoid such mutations unless the parent and child are tightly coupled by design. In most cases, the child should emit an event to let the parent perform the mutation.

Prop Validation

Components can specify requirements for their props, such as the types you've already seen. If a requirement is not met, Vue will warn you in the browser's JavaScript console. This is especially useful when developing a component that is intended to be used by others.

To specify prop validations, you can provide an object with validation requirements to the defineProps() macroprops option, instead of an array of strings. For example:

js
defineProps({
  // Basic type check
  //  (`null` and `undefined` values will allow any type)
  propA: Number,
  // Multiple possible types
  propB: [String, Number],
  // Required string
  propC: {
    type: String,
    required: true
  },
  // Number with a default value
  propD: {
    type: Number,
    default: 100
  },
  // Object with a default value
  propE: {
    type: Object,
    // Object or array defaults must be returned from
    // a factory function. The function receives the raw
    // props received by the component as the argument.
    default(rawProps) {
      return { message: 'hello' }
    }
  },
  // Custom validator function
  propF: {
    validator(value) {
      // The value must match one of these strings
      return ['success', 'warning', 'danger'].includes(value)
    }
  },
  // Function with a default value
  propG: {
    type: Function,
    // Unlike object or array default, this is not a factory 
    // function - this is a function to serve as a default value
    default() {
      return 'Default function'
    }
  }
})

TIP

Code inside the defineProps() argument cannot access other variables declared in <script setup>, because the entire expression is moved to an outer function scope when compiled.

js
export default {
  props: {
    // Basic type check
    //  (`null` and `undefined` values will allow any type)
    propA: Number,
    // Multiple possible types
    propB: [String, Number],
    // Required string
    propC: {
      type: String,
      required: true
    },
    // Number with a default value
    propD: {
      type: Number,
      default: 100
    },
    // Object with a default value
    propE: {
      type: Object,
      // Object or array defaults must be returned from
      // a factory function. The function receives the raw
      // props received by the component as the argument.
      default(rawProps) {
        return { message: 'hello' }
      }
    },
    // Custom validator function
    propF: {
      validator(value) {
        // The value must match one of these strings
        return ['success', 'warning', 'danger'].includes(value)
      }
    },
    // Function with a default value
    propG: {
      type: Function,
      // Unlike object or array default, this is not a factory 
      // function - this is a function to serve as a default value
      default() {
        return 'Default function'
      }
    }
  }
}

Additional details:

  • All props are optional by default, unless required: true is specified.

  • An absent optional prop other than Boolean will have undefined value.

  • The Boolean absent props will be cast to false. You can change this by setting a default for it — i.e.: default: undefined to behave as a non-Boolean prop.

  • If a default value is specified, it will be used if the resolved prop value is undefined - this includes both when the prop is absent, or an explicit undefined value is passed.

When prop validation fails, Vue will produce a console warning (if using the development build).

If using Type-based props declarations , Vue will try its best to compile the type annotations into equivalent runtime prop declarations. For example, defineProps<{ msg: string }> will be compiled into { msg: { type: String, required: true }}.

Note

Note that props are validated before a component instance is created, so instance properties (e.g. data, computed, etc.) will not be available inside default or validator functions.

Runtime Type Checks

The type can be one of the following native constructors:

  • String
  • Number
  • Boolean
  • Array
  • Object
  • Date
  • Function
  • Symbol

In addition, type can also be a custom class or constructor function and the assertion will be made with an instanceof check. For example, given the following class:

js
class Person {
  constructor(firstName, lastName) {
    this.firstName = firstName
    this.lastName = lastName
  }
}

You could use it as a prop's type:

js
defineProps({
  author: Person
})
js
export default {
  props: {
    author: Person
  }
}

Vue will use instanceof Person to validate whether the value of the author prop is indeed an instance of the Person class.

Boolean Casting

Props with Boolean type have special casting rules to mimic the behavior of native boolean attributes. Given a <MyComponent> with the following declaration:

js
defineProps({
  disabled: Boolean
})
js
export default {
  props: {
    disabled: Boolean
  }
}

The component can be used like this:

template
<!-- equivalent of passing :disabled="true" -->
<MyComponent disabled />

<!-- equivalent of passing :disabled="false" -->
<MyComponent />

When a prop is declared to allow multiple types, the casting rules for Boolean will also be applied. However, there is an edge when both String and Boolean are allowed - the Boolean casting rule only applies if Boolean appears before String:

js
// disabled will be casted to true
defineProps({
  disabled: [Boolean, Number]
})
  
// disabled will be casted to true
defineProps({
  disabled: [Boolean, String]
})
  
// disabled will be casted to true
defineProps({
  disabled: [Number, Boolean]
})
  
// disabled will be parsed as an empty string (disabled="")
defineProps({
  disabled: [String, Boolean]
})
js
// disabled will be casted to true
export default {
  props: {
    disabled: [Boolean, Number]
  }
}
  
// disabled will be casted to true
export default {
  props: {
    disabled: [Boolean, String]
  }
}
  
// disabled will be casted to true
export default {
  props: {
    disabled: [Number, Boolean]
  }
}
  
// disabled will be parsed as an empty string (disabled="")
export default {
  props: {
    disabled: [String, Boolean]
  }
}
Props has loaded