Introduction

Vue validates prop types at runtime and warns when the received type does not match the declared type. A common source of this error is confusing static attributes with dynamic bindings in templates:

bash
[Vue warn]: Invalid prop: type check failed for prop "count". Expected String
with value "42", got Number with value 42.

This happens because unbound HTML attributes are always strings, while v-bind (or :) passes the actual JavaScript value.

Symptoms

  • Console warning: "Invalid prop: type check failed"
  • Prop validation fails but the component still renders with the value
  • TypeScript shows type errors that do not match runtime behavior
  • Numeric props received as strings or vice versa
  • Boolean props not working as expected (e.g., disabled="false" is truthy)

Common Causes

  • Static attribute passes a string: count="42" instead of :count="42"
  • Template interpolation used where v-bind is needed: count="{{ value }}"
  • HTML attributes always pass strings, even for numbers and booleans
  • Prop defined with wrong type in the component definition
  • Third-party component expects a different type than documented

Step-by-Step Fix

  1. 1.Use v-bind for dynamic values (numbers, booleans, objects, arrays):
  2. 2.```html
  3. 3.<!-- BAD: passes string "42" -->
  4. 4.<Counter count="42" />

<!-- GOOD: passes number 42 --> <Counter :count="42" />

<!-- GOOD: passes a variable --> <Counter :count="userCount" /> ```

  1. 1.Define props with multiple acceptable types:
  2. 2.```javascript
  3. 3.export default {
  4. 4.props: {
  5. 5.count: {
  6. 6.type: [String, Number],
  7. 7.default: 0,
  8. 8.validator(value) {
  9. 9.return value >= 0;
  10. 10.},
  11. 11.},
  12. 12.},
  13. 13.};
  14. 14.`
  15. 15.Coerce prop values internally when you cannot control the parent:
  16. 16.```javascript
  17. 17.export default {
  18. 18.props: {
  19. 19.count: {
  20. 20.type: [String, Number],
  21. 21.default: 0,
  22. 22.},
  23. 23.},
  24. 24.computed: {
  25. 25.normalizedCount() {
  26. 26.return Number(this.count);
  27. 27.},
  28. 28.},
  29. 29.};
  30. 30.`
  31. 31.Handle boolean props correctly:
  32. 32.```html
  33. 33.<!-- BAD: "false" is a truthy string -->
  34. 34.<Button disabled="false" />

<!-- GOOD: binds the actual boolean --> <Button :disabled="false" /> <Button :disabled="isDisabled" />

<!-- Shorthand for boolean true --> <Button disabled /> ```

  1. 1.Add a custom prop validator for stricter type checking:
  2. 2.```javascript
  3. 3.export default {
  4. 4.props: {
  5. 5.status: {
  6. 6.type: String,
  7. 7.required: true,
  8. 8.validator(value) {
  9. 9.return ['pending', 'active', 'completed', 'cancelled'].includes(value);
  10. 10.},
  11. 11.},
  12. 12.},
  13. 13.};
  14. 14.`

Prevention

  • Always use v-bind (:) for non-string prop values
  • Define props with explicit types and default values
  • Use TypeScript with vue-tsc to catch type mismatches at compile time
  • Enable eslint-plugin-vue with the valid-define-props rule
  • Test component prop validation in unit tests with incorrect types
  • Document expected prop types in JSDoc comments for team members
  • Use prop validators for enum-like string props to catch typos early
  • Run vue-cli-service lint in CI to catch prop type issues before deployment