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:
<script setup>
const props = defineProps(['foo'])
console.log(props.foo)
</script>
In non-<script setup>
components, props are declared using the props
option:
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:
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:
export default {
props: {
title: String,
likes: Number
}
}
// in <script setup>
defineProps({
title: String,
likes: Number
})
// 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:
<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:
defineProps({
greetingMessage: String
})
export default {
props: {
greetingMessage: String
}
}
<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:
<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 benefits for 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:
<BlogPost title="My journey with Vue" />
You've also seen props assigned dynamically with v-bind
or its :
shortcut, such as in:
<!-- 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
<!-- 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
<!-- 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
<!-- 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
<!-- 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:
export default {
data() {
return {
post: {
id: 1,
title: 'My Journey with Vue'
}
}
}
}
const post = {
id: 1,
title: 'My Journey with Vue'
}
The following template:
<BlogPost v-bind="post" />
Will be equivalent to:
<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:
const props = defineProps(['foo'])
// ❌ warning, props are readonly!
props.foo = 'bar'
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:
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:
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)
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 } } }
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:
const props = defineProps(['size']) // computed property that auto-updates when the prop changes const normalizedSize = computed(() => props.size.trim().toLowerCase())
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:
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.
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 haveundefined
value.The
Boolean
absent props will be cast tofalse
. You should set adefault
value for it in order to get desired behavior.If a
default
value is specified, it will be used if the resolved prop value isundefined
- this includes both when the prop is absent, or an explicitundefined
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:
class Person {
constructor(firstName, lastName) {
this.firstName = firstName
this.lastName = lastName
}
}
You could use it as a prop's type:
defineProps({
author: Person
})
export default {
props: {
author: Person
}
}
to validate that the value of the author
prop was created with new Person
.
Boolean Casting
Props with Boolean
type has special casting rules to mimic the behavior of native boolean attributes. Given a <MyComponent>
with the following declaration:
defineProps({
disabled: Boolean
})
export default {
props: {
disabled: Boolean
}
}
The component can be used like this:
<!-- equivalent of passing :disabled="true" -->
<MyComponent disabled />
<!-- equivalent of passing :disabled="false" -->
<MyComponent />
When a prop is declared to allow multiple types, e.g.
defineProps({
disabled: [Boolean, Number]
})
export default {
props: {
disabled: [Boolean, Number]
}
}
The casting rules for Boolean
will apply regardless of type appearance order.