Vue.js Component Props

Props are the mechanism for passing data from a parent component down to a child component. They make components flexible and reusable by letting the parent control what the child displays or uses.

How Props Work

Think of a component as a coffee vending machine. The machine is the same every time (the component), but you press a button to choose what goes in (the prop). The machine produces different results depending on what you selected.

Diagram: Props Flow

Parent Component
┌────────────────────────────────────────┐
│  <user-card                            │
│    name="Elena"                        │
│    role="Designer"                     │
│    :score="92"                         │
│  />                                    │
└──────────────────┬─────────────────────┘
                   │  Props flow DOWN
                   ▼
Child Component (user-card)
┌────────────────────────────────────────┐
│  props: ["name", "role", "score"]      │
│                                        │
│  Receives:                             │
│    name  = "Elena"                     │
│    role  = "Designer"                  │
│    score = 92                          │
│                                        │
│  Renders its template using these      │
└────────────────────────────────────────┘

Defining Props — Array Syntax

The simplest way to declare props is with an array of prop names.

app.component("user-card", {
  props: ["name", "role", "score"],
  template: `
    <div>
      <h3>{{ name }}</h3>
      <p>Role: {{ role }}</p>
      <p>Score: {{ score }}</p>
    </div>
  `
});
<user-card name="Elena" role="Designer" :score="92"></user-card>

Notice the difference: name="Elena" passes a string literal. :score="92" uses v-bind (the colon shorthand) to pass a number. Without the colon, score="92" would pass the string "92" instead of the number 92.

Defining Props — Object Syntax with Validation

The object syntax lets you define the expected type, whether the prop is required, and a default value. Vue checks these rules in development mode and warns you if something is wrong.

app.component("product-badge", {
  props: {
    label: {
      type: String,
      required: true
    },
    count: {
      type: Number,
      default: 0
    },
    active: {
      type: Boolean,
      default: true
    }
  },
  template: `<span v-if="active">{{ label }}: {{ count }}</span>`
});

Supported Prop Types

┌─────────────┬──────────────────────────────────┐
│ Type        │ Example value                    │
├─────────────┼──────────────────────────────────┤
│ String      │ "Hello"                          │
│ Number      │ 42                               │
│ Boolean     │ true / false                     │
│ Array       │ ["a", "b", "c"]                  │
│ Object      │ { id: 1, name: "Alice" }         │
│ Function    │ () => doSomething()              │
│ Date        │ new Date()                       │
└─────────────┴──────────────────────────────────┘

Props with Default Values

When a prop has a default, the component works even if the parent does not pass that prop.

app.component("alert-banner", {
  props: {
    message: {
      type: String,
      required: true
    },
    type: {
      type: String,
      default: "info"      // used if parent doesn't pass "type"
    },
    dismissible: {
      type: Boolean,
      default: true
    }
  },
  template: `
    <div :class="'banner-' + type">
      {{ message }}
      <button v-if="dismissible">✕</button>
    </div>
  `
});
<!-- Only passes message; type and dismissible use defaults -->
<alert-banner message="Your file was saved."></alert-banner>

<!-- Overrides defaults -->
<alert-banner message="Error occurred." type="error" :dismissible="false">
</alert-banner>

Passing Dynamic Props from Parent Data

Instead of hardcoding values in the template, bind props to the parent's data. When the parent data changes, the child updates automatically.

<div id="app">
  <user-card
    :name="currentUser.name"
    :role="currentUser.role"
    :score="currentUser.score"
  ></user-card>
</div>

<script>
  const app = Vue.createApp({
    data() {
      return {
        currentUser: {
          name: "Marcus",
          role: "Engineer",
          score: 88
        }
      };
    }
  });

  app.component("user-card", {
    props: ["name", "role", "score"],
    template: `
      <div>
        <h3>{{ name }}</h3>
        <p>{{ role }} — Score: {{ score }}</p>
      </div>
    `
  });

  app.mount("#app");
</script>

Diagram: Dynamic Props Update Chain

Parent data: currentUser.name = "Marcus"
     │
     │  :name="currentUser.name"
     ▼
Child receives: name = "Marcus"
     │
     ▼
Child renders: <h3>Marcus</h3>

Parent updates: currentUser.name = "Lisa"
     │
     │  Vue re-passes the prop automatically
     ▼
Child receives: name = "Lisa"
     │
     ▼
Child re-renders: <h3>Lisa</h3>

Passing an Object as Props with v-bind

When a component has many props and the parent has a matching object, you can spread the object's properties as props using v-bind without an argument.

data() {
  return {
    product: {
      name: "Standing Desk",
      price: 399,
      inStock: true
    }
  };
}

<!-- Pass each property individually -->
<product-card :name="product.name" :price="product.price" :inStock="product.inStock">
</product-card>

<!-- Shorthand: spread the whole object -->
<product-card v-bind="product"></product-card>

<!-- Both produce the same result -->

Props Are Read-Only in the Child

A child component must never directly modify a prop it receives. Props are a one-way data channel — down from parent to child only. Modifying a prop directly causes a Vue warning and leads to unpredictable behavior.

Diagram: Why Props Should Not Be Modified

Parent owns the data: user.name = "Tom"
     │
     │ passes prop: name = "Tom"
     ▼
Child receives: name = "Tom"

If child modifies name directly:
  name = "Jerry"  ← Vue warns: "Avoid mutating a prop"
  
Why it's wrong:
  Parent still has user.name = "Tom"
  Child shows "Jerry"
  They are now out of sync — unpredictable behavior.

The correct approach:
  Child emits an event → Parent updates user.name → Parent passes new prop

The Correct Pattern — Local Copy

If you need to change a prop's value inside the child, copy it into a local data property first.

app.component("editable-name", {
  props: ["initialName"],
  data() {
    return {
      localName: this.initialName  // copy prop into local data
    };
  },
  template: `
    <div>
      <input v-model="localName">
      <p>Editing: {{ localName }}</p>
    </div>
  `
});

localName starts with the prop's value but is fully owned by the child. Changing it does not affect the parent.

Prop Validation with Custom Rules

For advanced validation, provide a validator function that returns true or false.

props: {
  status: {
    type: String,
    validator(value) {
      return ["active", "inactive", "pending"].includes(value);
    }
  },
  age: {
    type: Number,
    validator(value) {
      return value >= 0 && value <= 120;
    }
  }
}

Diagram: Validator in Action

<user-badge status="active"></user-badge>   ✓ Valid
<user-badge status="deleted"></user-badge>  ✗ Vue warns: invalid prop value

<age-field :age="25"></age-field>   ✓ Valid
<age-field :age="-5"></age-field>   ✗ Vue warns: invalid prop value

Summary

Props pass data from parent components down to child components. Define them as an array for quick setup or as an object to add type checking, required flags, defaults, and custom validators. Use v-bind (the colon shorthand) to pass numbers, booleans, arrays, and objects — without it, everything becomes a string. Never modify props inside the child; copy them to local data if you need a changeable version. Props are the foundation of component communication, and the validation system helps catch misuse early during development.

Leave a Comment

Your email address will not be published. Required fields are marked *