Skip to content

vue-perfectionist/component-prop-types ​

Enforce configured prop value types at Vue component usage sites.

Usage ​

Configure the Vue parser and list the component contracts to enforce. No components are checked by default.

js
import { defineConfig } from 'eslint/config'
import vuePerfectionist from 'eslint-plugin-vue-perfectionist'
import vueParser from 'vue-eslint-parser'

export default defineConfig([
  {
    files: ['**/*.vue'],
    languageOptions: { parser: vueParser },
    plugins: { 'vue-perfectionist': vuePerfectionist },
    rules: {
      'vue-perfectionist/component-prop-types': [
        'error',
        {
          targets: [{ components: ['AppStepper'], props: { count: 'number' } }],
          unknownValues: 'ignore',
        },
      ],
    },
  },
])

For TypeScript SFCs, also configure @typescript-eslint/parser as languageOptions.parserOptions.parser, as shown in the guide.

📖 Rule Details ​

These examples configure count: 'number' on AppStepper. Static attribute values are strings; a numeric prop requires an expression producing a number.

vue
<template>
  <AppStepper :count="20" />
  <AppStepper :count="-42" />
  <AppStepper :count="condition ? 1 : 2" />
</template>
correct
vue
<template>
  <AppStepper count=
"20"
/>
<AppStepper count=
"20"
/>
<AppStepper :count="
null
" />
<AppStepper :count="
condition ? 1 : '2'
" />
</template>
incorrect

Missing props are not type errors. Use require-component-props to require presence. Unknown expressions are ignored by default, which means no type conclusion was reached, not that the value passed validation.

Effective values and binding order ​

The rule follows Vue's compiler and runtime prop merging. Within one group of explicit attributes and named bindings, the first occurrence of a static raw key wins. Inside an object literal, the last key wins. Object v-bind, object v-on, and dynamic event names split explicit binding groups; later groups overwrite earlier values. Computed binding arguments remain ordered writes, even when the argument is a constant string.

vue
<template>
  <AppStepper
    count="wrong"
    v-bind="{ count: 1 }"
  />
  <AppStepper
    v-bind="extra"
    :count="1"
  />
  <AppStepper v-bind="{ ...extra, count: 1 }" />
  <AppStepper
    :count="1"
    count="wrong"
  />
</template>
correct
vue
<template>
  <AppStepper
    v-bind="{ count: 1 }"
    count=
"wrong"
/> <AppStepper v-bind="{ ...extra, count:
'wrong'
}" />
<AppStepper count=
"wrong"
:count="1" /> </template>
incorrect

In :count="1" v-bind="extra" and { count: 1, ...extra }, the key remains present but its final value is unknown. Mixed raw spellings such as pageSize and page-size also make the effective value unknown: Vue's raw-key merge and later prop normalization cannot be replaced by simply merging normalized names.

🔧 Options ​

Defaults:

json
{
  "targets": [],
  "unknownValues": "ignore"
}

targets ​

Type: { components: string[], props: Record<string, Constraint> }[]. Default: [].

Each target requires a nonempty list of unique component names and a nonempty prop map. An empty targets array disables checks. There are no built-in targets. Each constraint accepts a single type, a nonempty union of unique types, or a descriptor:

json
{
  "targets": [
    {
      "components": ["AppSelect"],
      "props": {
        "label": "string",
        "modelValue": ["string", "number", "null"],
        "options": "array",
        "filter": "function",
        "disabled": {
          "type": "boolean",
          "booleanCasting": true
        }
      }
    }
  ]
}
TypeMeaning
stringString values
numberNumbers, without integer, range, or finiteness restrictions
booleanBoolean values
arrayArrays, without checking element types
objectObjects excluding arrays and null, without checking fields
functionFunctions, without checking signatures
bigintBigInt values
symbolSymbol values; normal symbol-producing expressions are unknown in this version
nullExplicit null
undefinedExplicit undefined or void expressions

Union order has no meaning. null and undefined must be explicitly allowed. Matching targets merge distinct prop constraints. Equivalent constraints are accepted; conflicting types or booleanCasting values for a normalized component/prop pair throw a configuration error, including conflicts within one prop map.

booleanCasting ​

Descriptor field: boolean. Default: false.

Set this only when the actual component has Vue Boolean casting enabled for the prop, including empty-string casting. It models converting '' or the hyphenated prop name to true. It applies to known bound strings and object binding values as well as static attributes.

With disabled: { type: 'boolean', booleanCasting: true } on AppButton:

vue
<template>
  <AppButton disabled />
  <AppButton disabled="" />
  <AppButton disabled="disabled" />
  <AppButton :disabled="false" />
  <AppButton disabled="" />
</template>
correct
vue
<template>
  <AppButton disabled=
"false"
/>
<AppButton disabled=
"true"
/>
</template>
incorrect

Without booleanCasting, bare attributes are empty strings. The order in type: ['boolean', 'string'] does not imply casting: actual Vue declarations using [Boolean, String] and [String, Boolean] differ. An inferred string without a known literal value may either cast to Boolean or remain a string, so a Boolean-only contract can report a possible mismatch.

unknownValues ​

Type: 'ignore' | 'report'. Default: 'ignore'.

report emits unverifiablePropType for a definitely supplied prop whose type cannot be established. Presence that is itself unknown, such as a sole v-bind="extra", is left to require-component-props. Known bad branches still produce diagnostics even if another branch is unknown and this option is ignore.

Scope ​

Component matching, prop normalization, native-tag and slot-outlet exclusions, v-pre, static dynamic components, directive support, and reserved prop restrictions are shared with require-component-props. This rule checks SFC templates, not script declarations or render functions.

Expression shapeInferred category
String, numeric, Boolean, BigInt, null literalsCorresponding category
Template literalsstring
Arrays, including unknown elements/spreadsarray
Object literals, including unknown fieldsobject
Function and arrow expressionsfunction
RegExp literalsobject
void expressionundefined
undefinedundefined only when not shadowed by script or template bindings
!value, !!valueboolean
typeof valuestring
Unary +, -, ~ on known numeric valuesCorresponding numeric category; unary + on BigInt remains unknown
Conditional expressionsUnion of branch categories and any unknown branch
Identifiers, member access, calls, other operatorsUnknown

TypeScript assertions, satisfies, and non-null assertions are unwrapped rather than trusted as runtime evidence. No script initializer tracking, ref unwrapping, TypeScript type services, imported definitions, or function execution is used. Component defaults and custom transformations are not simulated. Use vue-tsc for full SFC type checking.

Default and named v-model supply their corresponding props, but .number and .trim do not establish the current parent expression's type. Object getter/setter values are unknown. Unsupported .prop and .attr bindings introduce uncertainty for the named prop.

Diagnostics and fixes ​

  • invalidPropType: one known category is not allowed.
  • possiblyInvalidPropType: a possible branch/category is not allowed.
  • unverifiablePropType: a supplied value remains unknown with unknownValues: 'report'.

Each configured prop produces at most one diagnostic per element, located at the effective attribute value, bound expression, object property value, or binding introducing uncertainty. The messages preserve configured prop spelling.

There are no automatic fixes or suggestions. Converting count="20" to :count="20" changes the value passed to the component and requires a user decision.

🔍 Implementation ​