All lint rules
All 251 source catalog entries with purpose, scope, configuration, and Bad/Good examples on this page. Each example explains the finding and repair, with current support gaps stated explicitly.
With Vite+, import defineConfig from @vizejs/vite-plugin/vite-plus, configure lint.vize.rules, and run vp run lint for Vize and Oxlint diagnostics.
Severity is the implementation default; override it with off, warn, or error. See Rule Options for rule-option support and Cross-file rules for project-graph findings.
_none_ means explicit enablement or host configuration is required. general-recommended is displayed as happy-path.
See the ESLint migration map for rule IDs, differences, and unsupported mappings.
Single-file rules (251)
| Rule | Examples | Severity | Presets | Fixable | Options | Implementation | Description | Category |
|---|---|---|---|---|---|---|---|---|
petite-vue/no-unsupported-directive |
Bad · Good | error |
none | No | No | source | Disallow directives that petite-vue does not support | Essential |
petite-vue/valid-v-effect |
Bad · Good | error |
none | No | No | source | Require v-effect to have a non-empty expression | Essential |
petite-vue/valid-v-scope |
Bad · Good | error |
none | No | No | source | Require v-scope to bind an object literal | Essential |
vue/multi-word-component-names |
Bad · Good | error |
essential, nuxt, opinionated |
No | No | source | Require component names to be multi-word | Essential |
vue/no-child-content |
Bad · Good | error |
essential, happy-path, nuxt, ecosystem, opinionated |
No | No | source | Disallow child content when using v-html or v-text | Essential |
vue/no-deprecated-filter |
Bad · Good | error |
none | No | No | source | Disallow deprecated Vue 2 filter syntax using the pipe operator | Essential |
vue/no-deprecated-functional-template |
Bad · Good | error |
ecosystem, essential, happy-path, nuxt, opinionated |
No | No | source | Disallow the functional attribute on the SFC <template> |
Essential |
vue/no-deprecated-html-element-is |
Bad · Good | error |
none | No | No | source | Disallow the is attribute on native HTML elements |
Essential |
vue/no-deprecated-inline-template |
Bad · Good | error |
none | No | No | source | Disallow the deprecated inline-template attribute |
Essential |
vue/no-deprecated-router-link-tag-prop |
Bad · Good | error |
none | No | No | source | Disallow the tag prop on <router-link> |
Essential |
vue/no-deprecated-scope-attribute |
Bad · Good | error |
essential, happy-path, nuxt, ecosystem, opinionated |
No | No | source | Disallow the deprecated scope attribute on <template> |
Essential |
vue/no-deprecated-slot-attribute |
Bad · Good | error |
essential, happy-path, nuxt, ecosystem, opinionated |
No | No | source | Disallow the deprecated slot attribute |
Essential |
vue/no-deprecated-slot-scope-attribute |
Bad · Good | error |
essential, happy-path, nuxt, ecosystem, opinionated |
No | No | source | Disallow the deprecated slot-scope attribute |
Essential |
vue/no-deprecated-v-bind-sync |
Bad · Good | error |
essential, happy-path, nuxt, ecosystem, opinionated |
No | No | source | Disallow the deprecated .sync modifier on v-bind |
Essential |
vue/no-deprecated-v-on-native-modifier |
Bad · Good | error |
essential, happy-path, nuxt, ecosystem, opinionated |
No | No | source | Disallow the deprecated .native modifier on v-on |
Essential |
vue/no-deprecated-v-on-number-modifiers |
Bad · Good | error |
ecosystem, essential, happy-path, nuxt, opinionated |
No | No | source | Disallow deprecated numeric keyCode modifiers on v-on |
Essential |
vue/no-dupe-v-else-if |
Bad · Good | error |
essential, happy-path, nuxt, ecosystem, opinionated |
No | No | source | Disallow duplicate conditions in v-if / v-else-if chains |
Essential |
vue/no-duplicate-attributes |
Bad · Good | error |
essential, happy-path, nuxt, ecosystem, opinionated |
No | No | source | Disallow duplicate attributes on the same element | Essential |
vue/no-multiple-template-root |
Bad · Good | error |
none | No | No | source | Disallow multiple root nodes in a template | Essential |
vue/no-mutating-props |
Bad · Good | error |
essential, happy-path, nuxt, ecosystem, opinionated |
No | ruleOptions |
source | Disallow mutating component props | Essential |
vue/no-reserved-component-names |
Bad · Good | error |
essential, happy-path, nuxt, ecosystem, opinionated |
No | No | source | Disallow the use of reserved names as component names | Essential |
vue/no-template-key |
Bad · Good | error |
essential, happy-path, nuxt, ecosystem, opinionated |
No | No | source | Disallow key attribute on <template> |
Essential |
vue/no-textarea-mustache |
Bad · Good | error |
essential, happy-path, nuxt, ecosystem, opinionated |
No | No | source | Disallow mustache interpolation in <textarea> |
Essential |
vue/no-unused-components |
Bad · Good | warning |
happy-path, nuxt, ecosystem, opinionated |
No | No | source | Disallow registering components that are not used inside templates | Essential |
vue/no-unused-vars |
Bad · Good | warning |
essential, happy-path, nuxt, ecosystem, opinionated |
No | No | source | Disallow unused variable definitions in v-for and v-slot directives | Essential |
vue/no-use-v-if-with-v-for |
Bad · Good | warning |
essential, happy-path, nuxt, ecosystem, opinionated |
No | No | source | Disallow using v-if on the same element as v-for |
Essential |
vue/no-useless-template-attributes |
Bad · Good | error |
essential, happy-path, nuxt, ecosystem, opinionated |
No | No | source | Disallow useless attributes on <template> elements |
Essential |
vue/no-v-for-template-key-on-child |
Bad · Good | error |
essential, happy-path, nuxt, ecosystem, opinionated |
No | No | source | Disallow key on the child of a <template v-for> |
Essential |
vue/no-v-html |
Bad · Good | warning |
essential, happy-path, nuxt, ecosystem, opinionated |
No | No | source | Warn against v-html to prevent XSS vulnerabilities | Essential |
vue/no-v-text-v-html-on-component |
Bad · Good | error |
essential, happy-path, nuxt, ecosystem, opinionated |
No | No | source | Disallow v-text / v-html on component elements | Essential |
vue/permitted-contents |
Bad · Good | error |
happy-path, nuxt, ecosystem, opinionated |
No | No | source | Enforce HTML content model rules | Essential |
vue/require-component-is |
Bad · Good | error |
essential, happy-path, nuxt, ecosystem, opinionated |
No | No | source | Require v-bind:is on <component> elements |
Essential |
vue/require-toggle-inside-transition |
Bad · Good | error |
essential, happy-path, nuxt, ecosystem, opinionated |
No | No | source | Require a toggle on the element wrapped by <transition> |
Essential |
vue/require-v-for-key |
Bad · Good | error |
essential, happy-path, nuxt, ecosystem, opinionated |
No | No | source | Require v-bind:key with v-for directives |
Essential |
vue/use-v-on-exact |
Bad · Good | warning |
essential, nuxt, opinionated |
No | No | source | Enforce .exact modifier on v-on when there are modifier-based handlers |
Essential |
vue/valid-attribute-name |
Bad · Good | error |
essential, happy-path, nuxt, ecosystem, opinionated |
No | No | source | Require valid attribute names | Essential |
vue/valid-template-root |
Bad · Good | error |
essential, happy-path, nuxt, ecosystem, opinionated |
No | No | source | Enforce a valid <template> root for Vue 3 fragment semantics |
Essential |
vue/valid-v-bind |
Bad · Good | error |
essential, happy-path, nuxt, ecosystem, opinionated |
No | No | source | Enforce valid v-bind directives |
Essential |
vue/valid-v-cloak |
Bad · Good | error |
essential, happy-path, nuxt, ecosystem, opinionated |
No | No | source | Enforce valid v-cloak directives |
Essential |
vue/valid-v-else |
Bad · Good | error |
essential, happy-path, nuxt, ecosystem, opinionated |
Yes | No | source | Enforce valid v-else directives |
Essential |
vue/valid-v-for |
Bad · Good | error |
essential, happy-path, nuxt, ecosystem, opinionated |
No | No | source | Enforce valid v-for directives |
Essential |
vue/valid-v-html |
Bad · Good | error |
essential, happy-path, nuxt, ecosystem, opinionated |
No | No | source | Enforce valid v-html directives |
Essential |
vue/valid-v-if |
Bad · Good | error |
essential, happy-path, nuxt, ecosystem, opinionated |
No | No | source | Enforce valid v-if directives |
Essential |
vue/valid-v-memo |
Bad · Good | error |
essential, happy-path, nuxt, ecosystem, opinionated |
No | No | source | Enforce valid v-memo directives |
Essential |
vue/valid-v-model |
Bad · Good | error |
essential, happy-path, nuxt, ecosystem, opinionated |
No | No | source | Enforce valid v-model directives |
Essential |
vue/valid-v-on |
Bad · Good | error |
essential, happy-path, nuxt, ecosystem, opinionated |
No | No | source | Enforce valid v-on directives |
Essential |
vue/valid-v-once |
Bad · Good | error |
essential, happy-path, nuxt, ecosystem, opinionated |
No | No | source | Enforce valid v-once directives |
Essential |
vue/valid-v-show |
Bad · Good | error |
essential, happy-path, nuxt, ecosystem, opinionated |
No | No | source | Enforce valid v-show directives |
Essential |
vue/valid-v-slot |
Bad · Good | error |
essential, happy-path, nuxt, ecosystem, opinionated |
No | ruleOptions |
source | Enforce valid v-slot directives |
Essential |
vue/valid-v-text |
Bad · Good | error |
essential, happy-path, nuxt, ecosystem, opinionated |
No | No | source | Enforce valid v-text directives |
Essential |
vue/attribute-hyphenation |
Bad · Good | warning |
happy-path, nuxt, ecosystem, opinionated |
Yes | ruleOptions |
source | Enforce attribute naming style on custom components | Strongly Recommended |
vue/component-definition-name-casing |
Bad · Good | warning |
happy-path, nuxt, ecosystem, opinionated |
No | No | source | Enforce PascalCase or kebab-case for component definition names | Strongly Recommended |
vue/html-quotes |
Bad · Good | warning |
happy-path, nuxt, ecosystem, opinionated |
Yes | No | source | Enforce quotes style of HTML attributes | Strongly Recommended |
vue/html-self-closing |
Bad · Good | warning |
nuxt, opinionated |
Yes | ruleOptions |
source | Enforce self-closing style | Strongly Recommended |
vue/mustache-interpolation-spacing |
Bad · Good | warning |
happy-path, nuxt, ecosystem, opinionated |
Yes | No | source | Enforce consistent spacing inside mustache interpolations | Strongly Recommended |
vue/no-multi-spaces |
Bad · Good | warning |
happy-path, nuxt, ecosystem, opinionated |
Yes | No | source | Disallow multiple consecutive spaces | Strongly Recommended |
vue/no-template-shadow |
Bad · Good | warning |
nuxt, opinionated |
No | No | source | Disallow variable names that shadow variables in outer scope | Strongly Recommended |
vue/no-unused-properties |
Bad · Good | warning |
happy-path, nuxt, ecosystem, opinionated |
No | No | source | Disallow unused properties defined in defineProps | Strongly Recommended |
vue/prop-name-casing |
Bad · Good | warning |
happy-path, nuxt, ecosystem, opinionated |
No | No | source | Enforce a casing for declared prop names | Strongly Recommended |
vue/v-bind-style |
Bad · Good | warning |
nuxt, opinionated |
Yes | No | source | Enforce v-bind directive style |
Strongly Recommended |
vue/v-on-style |
Bad · Good | warning |
happy-path, nuxt, ecosystem, opinionated |
Yes | No | source | Enforce v-on directive style |
Strongly Recommended |
vue/v-slot-style |
Bad · Good | warning |
happy-path, nuxt, ecosystem, opinionated |
Yes | No | source | Enforce v-slot directive style |
Strongly Recommended |
ssr/no-browser-globals-in-ssr |
Bad · Good | warning |
happy-path, nuxt, ecosystem, opinionated |
No | No | source | Disallow browser-only globals in SSR context | Recommended |
ssr/no-hydration-mismatch |
Bad · Good | warning |
happy-path, nuxt, ecosystem, opinionated |
No | No | source | Disallow non-deterministic values that cause hydration mismatch | Recommended |
vue/a11y-img-alt |
Bad · Good | warning |
none | No | No | source | Require alt attribute on images for accessibility | Recommended |
vue/attribute-order |
Bad · Good | warning |
happy-path, nuxt, ecosystem, opinionated |
No | No | source | Enforce a consistent order of attributes | Recommended |
vue/component-name-in-template-casing |
Bad · Good | warning |
nuxt, opinionated |
Yes | ruleOptions |
source | Enforce specific casing for component names in templates | Recommended |
vue/html-button-has-type |
Bad · Good | warning |
nuxt, opinionated |
No | No | source | Require an explicit valid type on button elements | Recommended |
vue/max-template-complexity |
Bad · Good | warning |
none | No | No | source | Limit a component's own template complexity (cyclomatic and cognitive) | Recommended |
vue/no-array-index-key |
Bad · Good | warning |
nuxt, opinionated |
No | No | source | Disallow using the v-for index variable directly as the :key | Recommended |
vue/no-bare-strings-in-template |
Bad · Good | warning |
none | No | No | source | Disallow raw human-readable text in the template that should be internationalized | Recommended |
vue/no-boolean-attr-value |
Bad · Good | warning |
nuxt, opinionated |
Yes | No | source | Disallow explicit values for boolean HTML attributes | Recommended |
vue/no-empty-component-block |
Bad · Good | warning |
nuxt, opinionated |
No | No | source | Disallow empty SFC blocks | Recommended |
vue/no-inline-style |
Bad · Good | warning |
nuxt, opinionated |
No | No | source | Discourage use of inline style attributes | Recommended |
vue/no-invalid-html-attribute |
Bad · Good | warning |
happy-path, nuxt, ecosystem, opinionated |
No | No | source | Disallow invalid static values for HTML attributes | Recommended |
vue/no-lone-template |
Bad · Good | warning |
happy-path, nuxt, ecosystem, opinionated |
No | No | source | Disallow unnecessary <template> elements |
Recommended |
vue/no-multiple-objects-in-class |
Bad · Good | warning |
nuxt, opinionated |
No | No | source | Disallow multiple object literals inside a :class array binding | Recommended |
vue/no-negated-v-if-condition |
Bad · Good | warning |
nuxt, opinionated |
No | No | source | Disallow a negated v-if condition when the chain has a v-else | Recommended |
vue/no-non-component-keep-alive-child |
Bad · Good | warning |
none | No | No | source | Disallow plain element wrappers directly below <KeepAlive> |
Recommended |
vue/no-preprocessor-lang |
Bad · Good | warning |
nuxt, opinionated |
Yes | No | source | Discourage CSS preprocessor usage in favor of modern CSS | Recommended |
vue/no-root-v-if |
Bad · Good | warning |
nuxt, opinionated |
No | No | source | Disallow v-if on the single root element of a template | Recommended |
vue/no-script-non-standard-lang |
Bad · Good | warning |
nuxt, opinionated |
No | No | source | Discourage non-standard script lang values | Recommended |
vue/no-src-attribute |
Bad · Good | warning |
nuxt, opinionated |
No | No | source | Discourage src attribute on SFC blocks | Recommended |
vue/no-static-inline-styles |
Bad · Good | warning |
none | No | No | source | Disallow static inline style attributes | Recommended |
vue/no-template-lang |
Bad · Good | warning |
nuxt, opinionated |
Yes | No | source | Discourage lang attribute on template block | Recommended |
vue/no-template-target-blank |
Bad · Good | warning |
happy-path, nuxt, ecosystem, opinionated |
No | No | source | Disallow target="_blank" without rel="noopener noreferrer" | Recommended |
vue/no-undefined-refs |
Bad · Good | warning |
none | No | No | source | Disallow undefined variable references in templates | Recommended |
vue/no-unsafe-url |
Bad · Good | warning |
essential, happy-path, nuxt, ecosystem, opinionated |
No | No | source | Warn about potentially unsafe URL bindings | Recommended |
vue/no-unsandboxed-iframe |
Bad · Good | warning |
happy-path, nuxt, ecosystem, opinionated |
No | No | source | Require a sandbox attribute on iframe elements | Recommended |
vue/no-unused-refs |
Bad · Good | warning |
nuxt, opinionated |
No | No | source | Report template refs (ref="x") never referenced in <script> | Recommended |
vue/no-unused-setup-bindings |
Bad · Good | warning |
none | No | No | source | Disallow unread script setup bindings | Recommended |
vue/no-use-v-else-with-v-for |
Bad · Good | warning |
none | No | No | source | Disallow using v-else-if or v-else on the same element as v-for |
Recommended |
vue/no-useless-mustaches |
Bad · Good | warning |
nuxt, opinionated |
No | No | source | Disallow a mustache interpolation whose expression is a constant string literal | Recommended |
vue/no-useless-v-bind |
Bad · Good | warning |
nuxt, opinionated |
No | No | source | Disallow a v-bind whose value is a plain string literal | Recommended |
vue/no-v-text |
Bad · Good | warning |
nuxt, opinionated |
No | No | source | Disallow the v-text directive; prefer mustache interpolation | Recommended |
vue/prefer-props-shorthand |
Bad · Good | warning |
nuxt, opinionated |
Yes | No | source | Recommend shorthand syntax for props (Vue 3.4+) | Recommended |
vue/prefer-true-attribute-shorthand |
Bad · Good | warning |
nuxt, opinionated |
No | No | source | Prefer the shorthand for a boolean attribute bound to true |
Recommended |
vue/require-component-registration |
Bad · Good | warning |
opinionated |
No | ruleOptions |
source | Require explicit import or registration for components | Recommended |
vue/require-scoped-style |
Bad · Good | warning |
happy-path, nuxt, ecosystem, opinionated |
No | No | source | Require scoped attribute on style tags | Recommended |
vue/scoped-event-names |
Bad · Good | warning |
nuxt, opinionated |
No | No | source | Recommend scoped event names using context:event format | Recommended |
vue/sfc-element-order |
Bad · Good | warning |
happy-path, nuxt, ecosystem, opinionated |
No | ruleOptions |
source | Enforce consistent order of SFC top-level elements | Recommended |
vue/single-style-block |
Bad · Good | warning |
happy-path, nuxt, ecosystem, opinionated |
No | No | source | Recommend having a single style block | Recommended |
vue/slot-name-casing |
Bad · Good | warning |
nuxt, opinionated |
No | No | source | Enforce kebab-case for named slots used via v-slot | Recommended |
vue/this-in-template |
Bad · Good | warning |
nuxt, opinionated |
No | No | source | Disallow this. in template expressions |
Recommended |
vue/v-on-event-hyphenation |
Bad · Good | warning |
nuxt, opinionated |
No | ruleOptions |
source | Enforce hyphenation of custom event names in v-on on components | Recommended |
vue/v-on-handler-style |
Bad · Good | warning |
nuxt, opinionated |
No | No | source | Enforce writing v-on handlers as a method reference or an inline function | Recommended |
vue/warn-custom-block |
Bad · Good | warning |
nuxt, opinionated |
No | No | source | Warn about custom blocks in SFC files | Recommended |
vue/warn-custom-directive |
Bad · Good | warning |
nuxt, opinionated |
No | No | source | Warn about custom directives that need registration | Recommended |
a11y/alt-text |
Bad · Good | warning |
happy-path, nuxt, ecosystem, opinionated |
No | No | source | Require alternative text for media elements | Accessibility |
a11y/anchor-has-content |
Bad · Good | warning |
happy-path, nuxt, ecosystem, opinionated |
No | No | source | Require anchor elements to have accessible content | Accessibility |
a11y/anchor-is-valid |
Bad · Good | warning |
happy-path, nuxt, ecosystem, opinionated |
No | No | source | Enforce valid href on anchor elements | Accessibility |
a11y/aria-props |
Bad · Good | error |
happy-path, nuxt, ecosystem, opinionated |
No | No | source | Disallow invalid ARIA attributes | Accessibility |
a11y/aria-role |
Bad · Good | error |
happy-path, nuxt, ecosystem, opinionated |
No | No | source | Elements with ARIA roles must use a valid, non-abstract ARIA role | Accessibility |
a11y/aria-unsupported-elements |
Bad · Good | error |
happy-path, nuxt, ecosystem, opinionated |
No | No | source | Disallow ARIA attributes on elements that do not support them | Accessibility |
a11y/click-events-have-key-events |
Bad · Good | warning |
happy-path, nuxt, ecosystem, opinionated |
No | No | source | Require keyboard event handlers with click events | Accessibility |
a11y/form-control-has-label |
Bad · Good | warning |
happy-path, nuxt, ecosystem, opinionated |
No | No | source | Require form controls to have associated labels | Accessibility |
a11y/heading-has-content |
Bad · Good | warning |
happy-path, nuxt, ecosystem, opinionated |
No | No | source | Require heading elements to have accessible content | Accessibility |
a11y/heading-levels |
Bad · Good | warning |
nuxt, opinionated |
No | No | source | Disallow skipping heading levels | Accessibility |
a11y/iframe-has-title |
Bad · Good | warning |
happy-path, nuxt, ecosystem, opinionated |
No | No | source | Require iframe elements to have a title attribute | Accessibility |
a11y/img-alt |
Bad · Good | warning |
none | No | No | source | Require alt attribute on images for accessibility | Accessibility |
a11y/interactive-supports-focus |
Bad · Good | warning |
happy-path, nuxt, ecosystem, opinionated |
No | No | source | Require interactive role elements to be focusable | Accessibility |
a11y/label-has-for |
Bad · Good | warning |
happy-path, nuxt, ecosystem, opinionated |
No | No | source | Require labels to have associated form controls | Accessibility |
a11y/landmark-roles |
Bad · Good | warning |
nuxt, opinionated |
No | No | source | Validate landmark role placement and uniqueness | Accessibility |
a11y/media-has-caption |
Bad · Good | warning |
happy-path, nuxt, ecosystem, opinionated |
No | No | source | Require media elements to have captions | Accessibility |
a11y/mouse-events-have-key-events |
Bad · Good | warning |
happy-path, nuxt, ecosystem, opinionated |
No | No | source | Require focus/blur events with mouse events | Accessibility |
a11y/no-access-key |
Bad · Good | warning |
happy-path, nuxt, ecosystem, opinionated |
No | No | source | Disallow the use of the accesskey attribute | Accessibility |
a11y/no-aria-hidden-on-focusable |
Bad · Good | error |
happy-path, nuxt, ecosystem, opinionated |
No | No | source | Disallow aria-hidden="true" on focusable elements | Accessibility |
a11y/no-autofocus |
Bad · Good | warning |
happy-path, nuxt, ecosystem, opinionated |
No | No | source | Disallow the use of the autofocus attribute | Accessibility |
a11y/no-distracting-elements |
Bad · Good | warning |
happy-path, nuxt, ecosystem, opinionated |
No | No | source | Disallow distracting elements like <marquee> and <blink> | Accessibility |
a11y/no-i-for-icon |
Bad · Good | warning |
happy-path, nuxt, ecosystem, opinionated |
No | No | source | Disallow using <i> element for icons | Accessibility |
a11y/no-redundant-roles |
Bad · Good | warning |
happy-path, nuxt, ecosystem, opinionated |
Yes | No | source | Disallow redundant ARIA roles | Accessibility |
a11y/no-refer-to-non-existent-id |
Bad · Good | warning |
happy-path, nuxt, ecosystem, opinionated |
No | No | source | Disallow references to non-existent IDs | Accessibility |
a11y/no-role-presentation-on-focusable |
Bad · Good | error |
happy-path, nuxt, ecosystem, opinionated |
No | No | source | Disallow role="presentation" or role="none" on focusable elements | Accessibility |
a11y/no-static-element-interactions |
Bad · Good | warning |
happy-path, nuxt, ecosystem, opinionated |
No | No | source | Disallow event handlers on static elements | Accessibility |
a11y/placeholder-label-option |
Bad · Good | warning |
nuxt, opinionated |
No | No | source | Require disabled or hidden on select placeholder option | Accessibility |
a11y/role-has-required-aria-props |
Bad · Good | warning |
happy-path, nuxt, ecosystem, opinionated |
No | No | source | Require ARIA roles to have required properties | Accessibility |
a11y/tabindex-no-positive |
Bad · Good | warning |
happy-path, nuxt, ecosystem, opinionated |
No | No | source | Disallow positive tabindex values | Accessibility |
a11y/use-list |
Bad · Good | warning |
nuxt, opinionated |
No | No | source | Suggest using list elements for bullet-like text | Accessibility |
vue/use-unique-element-ids |
Bad · Good | warning |
nuxt, opinionated |
No | No | source | Enforce unique element IDs using useId() instead of static literals | Accessibility |
html/deprecated-attr |
Bad · Good | warning |
happy-path, nuxt, ecosystem, opinionated |
No | No | source | Disallow deprecated HTML attributes | HTML Conformance |
html/deprecated-element |
Bad · Good | warning |
happy-path, nuxt, ecosystem, opinionated |
No | No | source | Disallow deprecated HTML elements | HTML Conformance |
html/id-duplication |
Bad · Good | error |
essential, happy-path, nuxt, ecosystem, opinionated |
No | No | source | Disallow duplicate element IDs | HTML Conformance |
html/no-consecutive-br |
Bad · Good | warning |
happy-path, nuxt, ecosystem, opinionated |
No | No | source | Disallow consecutive <br> elements | HTML Conformance |
html/no-dupe-style-properties |
Bad · Good | warning |
nuxt, opinionated |
No | No | source | Disallow duplicate properties in inline style attributes | HTML Conformance |
html/no-duplicate-class |
Bad · Good | warning |
nuxt, opinionated |
No | No | source | Disallow duplicate class names in a static class attribute | HTML Conformance |
html/no-duplicate-dt |
Bad · Good | warning |
happy-path, nuxt, ecosystem, opinionated |
No | No | source | Disallow duplicate <dt> names in <dl> | HTML Conformance |
html/no-empty-palpable-content |
Bad · Good | warning |
happy-path, nuxt, ecosystem, opinionated |
No | ruleOptions |
source | Disallow empty elements that expect visible content | HTML Conformance |
html/require-datetime |
Bad · Good | warning |
happy-path, nuxt, ecosystem, opinionated |
No | No | source | Require datetime attribute on <time> element | HTML Conformance |
type/no-floating-promises |
Bad · Good | warning |
nuxt, opinionated |
No | No | source | Disallow floating (unhandled) Promises | Type Aware |
type/no-reactivity-loss |
Bad · Good | warning |
nuxt, opinionated |
No | No | source | Disallow plain snapshots of reactive values across assignments and calls | Type Aware |
type/no-unsafe-template-binding |
Bad · Good | warning |
nuxt, opinionated |
No | No | source | Disallow template bindings that resolve to unsafe types | Type Aware |
type/require-typed-emits |
Bad · Good | warning |
happy-path, nuxt, ecosystem, opinionated |
No | No | source | Require type definition for defineEmits | Type Aware |
type/require-typed-props |
Bad · Good | warning |
happy-path, nuxt, ecosystem, opinionated |
No | No | source | Require type definition for defineProps | Type Aware |
type/strict-boolean-expressions |
Bad · Good | warning |
none | No | ruleOptions |
source | Require safe boolean expressions in script and template conditions | Type Aware |
script/no-get-current-instance |
Bad · Good | error |
opinionated |
No | No | source | Disallow getCurrentInstance() in Vapor mode (returns null) | Vapor |
script/no-next-tick |
Bad · Good | error |
none | No | No | source | Disallow nextTick() usage in Vapor-oriented components | Vapor |
script/no-options-api |
Bad · Good | error |
opinionated |
No | No | source | Disallow Options API patterns in Vapor mode | Vapor |
vapor/no-inline-template |
Bad · Good | error |
nuxt, opinionated |
No | No | source | Disallow deprecated inline-template attribute | Vapor |
vapor/no-vue-lifecycle-events |
Bad · Good | error |
happy-path, nuxt, ecosystem, opinionated |
No | No | source | Disallow @vue:xxx per-element lifecycle events (not supported in Vapor) | Vapor |
vapor/prefer-static-class |
Bad · Good | warning |
nuxt, opinionated |
Yes | No | source | Prefer static class over dynamic class binding for string literals | Vapor |
vapor/require-vapor-attribute |
Bad · Good | warning |
nuxt, opinionated |
No | No | source | Suggest adding vapor attribute to script setup | Vapor |
ecosystem/nuxt-prefer-nuxt-link |
Bad · Good | warning |
nuxt |
No | No | source | Prefer NuxtLink for internal application links | Ecosystem |
ecosystem/pinia-prefer-store-to-refs |
Bad · Good | warning |
ecosystem |
No | No | source | Prefer storeToRefs() when destructuring Pinia stores | Ecosystem |
ecosystem/router-link-require-to |
Bad · Good | error |
ecosystem |
No | No | source | Require a to target on RouterLink and NuxtLink components |
Ecosystem |
ecosystem/void-link-require-href |
Bad · Good | error |
ecosystem |
No | No | source | Require href on Void Vue Link components |
Ecosystem |
ecosystem/void-link-valid-method |
Bad · Good | warning |
ecosystem |
No | No | source | Validate static Void Vue Link method props | Ecosystem |
ecosystem/vue-i18n-no-missing-key |
Bad · Good | warning |
ecosystem |
No | No | source | Report static vue-i18n keys that are absent from local SFC messages | Ecosystem |
ecosystem/vue-router-prefer-named-link |
Bad · Good | warning |
ecosystem |
No | No | source | Prefer named route objects over static path strings in RouterLink | Ecosystem |
ecosystem/vue-router-prefer-named-push |
Bad · Good | warning |
ecosystem |
No | No | source | Prefer named route objects for Vue Router programmatic navigation | Ecosystem |
ecosystem/vue-test-utils-no-html-snapshot |
Bad · Good | warning |
ecosystem |
No | No | source | Avoid snapshotting wrapper.html() in Vue Test Utils tests | Ecosystem |
css/no-display-none |
Bad · Good | warning |
opinionated, nuxt |
No | No | source | Suggest using v-show instead of display: none | CSS |
css/no-hardcoded-values |
Bad · Good | warning |
opinionated, nuxt |
No | No | source | Suggest using CSS variables instead of hardcoded values | CSS |
css/no-id-selectors |
Bad · Good | warning |
opinionated, nuxt |
No | No | source | Discourage use of ID selectors in CSS | CSS |
css/no-important |
Bad · Good | warning |
opinionated, nuxt |
No | No | source | Discourage use of !important in CSS | CSS |
css/no-utility-classes |
Bad · Good | warning |
opinionated, nuxt |
No | No | source | Warn against implementing utility classes in component styles | CSS |
css/no-v-bind-performance |
Bad · Good | warning |
opinionated, nuxt |
No | No | source | Warn about performance cost of CSS v-bind() | CSS |
css/prefer-logical-properties |
Bad · Good | warning |
opinionated, nuxt |
No | No | source | Recommend CSS logical properties for better i18n support | CSS |
css/prefer-nested-selectors |
Bad · Good | warning |
opinionated, nuxt |
No | No | source | Recommend using CSS nesting for descendant selectors | CSS |
css/prefer-slotted |
Bad · Good | warning |
opinionated, nuxt |
No | No | source | Recommend ::v-slotted() for styling slot content | CSS |
css/require-font-display |
Bad · Good | warning |
opinionated, nuxt |
No | No | source | Require font-display in @font-face rules | CSS |
musea/no-empty-variant |
Bad · Good | warning |
none | No | No | source | Disallow empty <variant> blocks | Musea |
musea/prefer-design-tokens |
Bad · Good | warning |
none | No | ruleOptions |
source | Prefer design token CSS variables over hardcoded primitive values | Musea |
musea/require-component |
Bad · Good | warning |
none | No | No | source | Require component attribute in <art> block | Musea |
musea/require-title |
Bad · Good | error |
none | No | No | source | Require title attribute in <art> block | Musea |
musea/unique-variant-names |
Bad · Good | error |
none | No | No | source | Require unique variant names | Musea |
musea/valid-variant |
Bad · Good | error |
none | No | No | source | Require name attribute in <variant> blocks | Musea |
script/component-options-name-casing |
Bad · Good | error |
none | No | No | source | Enforce PascalCase for the component name option |
Script |
script/custom-event-name-casing |
Bad · Good | error |
none | No | ruleOptions |
source | Enforce camelCase for emitted custom event names | Script |
script/define-emits-declaration |
Bad · Good | warning |
none | No | No | source | Enforce the type-based defineEmits<{}>() form over the runtime/array form | Script |
script/define-macros-order |
Bad · Good | warning |
none | No | No | source | Enforce a consistent order of the Vue compiler macros in <script setup> | Script |
script/define-props-declaration |
Bad · Good | warning |
none | No | No | source | Enforce type-based defineProps<{ ... }>() over the runtime/object form | Script |
script/define-props-destructuring |
Bad · Good | warning |
none | No | ruleOptions |
source | Enforce consistent style for defineProps destructuring in <script setup> | Script |
script/no-arrow-functions-in-watch |
Bad · Good | error |
essential, happy-path, ecosystem, nuxt, opinionated |
No | No | source | Disallow arrow functions as Options API watch handlers | Script |
script/no-async-in-computed |
Bad · Good | error |
essential, happy-path, ecosystem, nuxt, opinionated |
No | No | source | Disallow async functions in computed properties | Script |
script/no-boolean-default |
Bad · Good | warning |
none | No | No | source | Disallow a default on a Boolean prop | Script |
script/no-deep-destructure-in-props |
Bad · Good | warning |
none | No | No | source | Disallow deeply nested destructuring in defineProps | Script |
script/no-deprecated-data-object-declaration |
Bad · Good | error |
none | No | No | source | Disallow an object literal as the component data option (Vue 3 requires a function) | Script |
script/no-deprecated-destroyed-lifecycle |
Bad · Good | error |
none | Yes | No | source | Disallow deprecated destroyed and beforeDestroy lifecycle hooks | Script |
script/no-deprecated-dollar-listeners-api |
Bad · Good | error |
none | No | No | source | Disallow the $listeners instance property removed in Vue 3 (merged into $attrs) | Script |
script/no-deprecated-dollar-scopedslots-api |
Bad · Good | error |
none | No | No | source | Disallow the $scopedSlots instance property removed in Vue 3 (use $slots) | Script |
script/no-deprecated-events-api |
Bad · Good | error |
none | No | No | source | Disallow the removed Vue 2 events API ($on / $off / $once) | Script |
script/no-deprecated-props-default-this |
Bad · Good | error |
none | No | No | source | Disallow this inside a prop default/validator function (removed in Vue 3) |
Script |
script/no-dupe-keys |
Bad · Good | error |
essential, happy-path, ecosystem, nuxt, opinionated |
No | No | source | Disallow duplicate keys across Options API props/data/computed/methods/setup/inject | Script |
script/no-duplicate-attr-inheritance |
Bad · Good | warning |
happy-path, ecosystem, nuxt, opinionated |
No | No | source | Flag a component that applies its fallthrough attributes twice | Script |
script/no-export-in-script-setup |
Bad · Good | error |
none | No | No | source | Disallow export statements inside <script setup> | Script |
script/no-import-compiler-macros |
Bad · Good | error |
essential, happy-path, ecosystem, nuxt, opinionated |
No | No | source | Disallow importing Vue compiler macros that are auto-imported | Script |
script/no-internal-imports |
Bad · Good | error |
essential, happy-path, ecosystem, nuxt, opinionated |
No | No | source | Disallow importing from Vue internal modules | Script |
script/no-multiple-slot-args |
Bad · Good | warning |
happy-path, ecosystem, nuxt, opinionated |
No | No | source | Disallow passing more than one argument to a scoped-slot function call | Script |
script/no-potential-component-option-typo |
Bad · Good | error |
essential, happy-path, ecosystem, nuxt, opinionated |
No | No | source | Flag likely typos in Options API component option names | Script |
script/no-reactive-destructure |
Bad · Good | warning |
none | No | No | source | Disallow destructuring reactive objects which loses reactivity | Script |
script/no-ref-as-operand |
Bad · Good | error |
essential, happy-path, ecosystem, nuxt, opinionated |
No | No | source | Require ref-bound variables to be accessed via .value when used as an operand |
Script |
script/no-required-prop-with-default |
Bad · Good | error |
essential, happy-path, ecosystem, nuxt, opinionated |
No | No | source | Disallow a prop that is both required: true and has a default | Script |
script/no-reserved-identifiers |
Bad · Good | error |
essential, happy-path, ecosystem, nuxt, opinionated |
No | No | source | Disallow using Vue compiler reserved identifiers | Script |
script/no-reserved-keys |
Bad · Good | error |
essential, happy-path, ecosystem, nuxt, opinionated |
No | No | source | Disallow Vue-reserved names as Options API props/data/computed/methods/setup/inject keys | Script |
script/no-reserved-props |
Bad · Good | error |
essential, happy-path, ecosystem, nuxt, opinionated |
No | No | source | Disallow reserved names in a component's props declaration | Script |
script/no-restricted-globals |
Bad · Good | error |
none | No | ruleOptions |
source | Disallow references to runtime-environment globals that must go through a typed wrapper | Script |
script/no-restricted-members |
Bad · Good | error |
none | No | ruleOptions |
source | Disallow project-configured object.property member accesses | Script |
script/no-side-effects-in-computed-properties |
Bad · Good | error |
essential, happy-path, ecosystem, nuxt, opinionated |
No | No | source | Disallow side effects in Options API computed getters | Script |
script/no-top-level-ref-in-script |
Bad · Good | error |
none | No | No | source | Disallow top-level ref/reactive to prevent Cross-Request State Pollution | Script |
script/no-unstable-nested-components |
Bad · Good | warning |
happy-path, ecosystem, nuxt, opinionated |
No | No | source | Disallow component definitions inside setup or render functions | Script |
script/no-unused-emit-declarations |
Bad · Good | warning |
none | No | No | source | Flag declared events that are never emitted | Script |
script/no-use-computed-property-like-method |
Bad · Good | error |
essential, happy-path, ecosystem, nuxt, opinionated |
No | No | source | Disallow calling an Options API computed property like a method | Script |
script/no-with-defaults |
Bad · Good | warning |
opinionated |
No | No | source | Discourage withDefaults in favor of destructuring defaults (Vue 3.5+) | Script |
script/prefer-computed |
Bad · Good | warning |
none | No | No | source | Prefer computed() for derived reactive state | Script |
script/prefer-define-options |
Bad · Good | warning |
none | No | No | source | Prefer defineOptions() over a plain <script> that only sets name/inheritAttrs | Script |
script/prefer-import-from-vue |
Bad · Good | warning |
happy-path, ecosystem, nuxt, opinionated |
Yes | No | source | Prefer importing from 'vue' instead of internal packages | Script |
script/prefer-ref-over-reactive |
Bad · Good | warning |
none | No | No | source | Recommend using ref() over reactive() for state management | Script |
script/prefer-use-attrs |
Bad · Good | warning |
none | No | No | source | Recommend using useAttrs() over context.attrs | Script |
script/prefer-use-id |
Bad · Good | warning |
none | No | No | source | Recommend using useId() for generating unique IDs (Vue 3.5+) | Script |
script/prefer-use-slots |
Bad · Good | warning |
none | No | No | source | Recommend using useSlots() over context.slots | Script |
script/prefer-use-template-ref |
Bad · Good | warning |
none | No | No | source | Recommend useTemplateRef over ref for template references (Vue 3.5+) | Script |
script/require-default-prop |
Bad · Good | error |
none | No | No | source | Require a default value for every optional, non-Boolean prop | Script |
script/require-explicit-emits |
Bad · Good | warning |
none | No | No | source | Require emitted events to be declared in defineEmits or the emits option | Script |
script/require-explicit-slots |
Bad · Good | warning |
none | No | No | source | Require slots consumed via useSlots() to be explicitly typed with defineSlots<...>() | Script |
script/require-function-return-type |
Bad · Good | warning |
none | No | No | source | Require return type annotations on functions | Script |
script/require-prop-type-constructor |
Bad · Good | error |
essential, happy-path, ecosystem, nuxt, opinionated |
No | No | source | Require prop type values to be constructors rather than string literals |
Script |
script/require-prop-types |
Bad · Good | error |
none | No | No | source | Require every prop to declare a type | Script |
script/require-symbol-provide |
Bad · Good | warning |
none | No | No | source | Recommend using Symbol as injection key for provide/inject | Script |
script/require-typed-object-prop |
Bad · Good | warning |
none | No | No | source | Require an explicit type on a prop whose runtime type is Object or Array |
Script |
script/require-typed-ref |
Bad · Good | warning |
none | No | No | source | Require an explicit type argument on a ref() initialized with no value, null, or undefined | Script |
script/require-valid-default-prop |
Bad · Good | error |
essential, happy-path, ecosystem, nuxt, opinionated |
No | No | source | Require a prop's default value to be valid for its declared type | Script |
script/return-in-computed-property |
Bad · Good | error |
essential, happy-path, ecosystem, nuxt, opinionated |
No | No | source | Require a return value in every computed getter | Script |
script/return-in-emits-validator |
Bad · Good | error |
essential, happy-path, ecosystem, nuxt, opinionated |
No | No | source | Require a return value in every Options API emits validator | Script |
script/valid-define-emits |
Bad · Good | error |
essential, happy-path, ecosystem, nuxt, opinionated |
No | No | source | Enforce valid defineEmits() usage (no type+runtime args, no local references, single call) | Script |
script/valid-define-options |
Bad · Good | error |
essential, happy-path, ecosystem, nuxt, opinionated |
No | No | source | Enforce valid defineOptions() usage (single object arg, no props/emits/expose/slots) | Script |
script/valid-define-props |
Bad · Good | error |
essential, happy-path, ecosystem, nuxt, opinionated |
No | No | source | Enforce valid defineProps() usage (single call, not both type and runtime args, no local references) | Script |
script/valid-next-tick |
Bad · Good | warning |
happy-path, ecosystem, nuxt, opinionated |
No | No | source | Require the result of a nextTick() call to be awaited, chained, or given a callback | Script |
nuxt/no-nuxt-config-test-key |
Bad · Good | error |
nuxt |
No | No | source | Disallow setting test key in Nuxt config |
Nuxt |
nuxt/no-page-meta-runtime-values |
Bad · Good | error |
nuxt |
No | No | source | Disallow runtime context values inside definePageMeta at the eager level, which is extracted into a separate chunk at build time and runs before component setup |
Nuxt |
nuxt/nuxt-config-keys-order |
Bad · Good | error |
nuxt |
Yes | No | source | Prefer recommended order of Nuxt config properties | Nuxt |
nuxt/prefer-import-meta |
Bad · Good | error |
nuxt |
Yes | No | source | Prefer using import.meta.* over process.* |
Nuxt |
Project rules and analyzer contracts (66)
These project entries supplement the 251 single-file catalog entries above. Each example includes its component/project context or an explicit tracked-graph scenario. CLI, experimental library producers, and contracts without a producer have different support boundaries; see the cross-file overview.
Single-file examples
petite-vue/no-unsupported-directive
Disallow directives that petite-vue does not support
Default severity: error
Presets: none
Automatic fix: None; review the suggested change
Applies to: HTML documents detected as petite-vue; ordinary Vue SFCs are outside this rule's scope.
Options: No rule-specific options. Severity and preset selection are configurable.
Configuration (Vite+)
import { defineConfig } from "@vizejs/vite-plugin/vite-plus";
export default defineConfig({
lint: {
vize: {
"preset": "incremental",
"rules": {
"petite-vue/no-unsupported-directive": "error"
}
},
},
});
vp run lint
Bad
v-memo, v-slot:header, and the custom v-my-directive are absent from petite-vue’s supported directive list. The petite-vue script marks this HTML as the relevant dialect.
<!doctype html>
<html><body>
<div v-memo="[a, b]"></div>
<template v-slot:header></template>
<div v-my-directive></div>
<script src="https://unpkg.com/petite-vue" init></script>
</body></html>
Good
The replacement uses supported v-scope, v-effect, v-if, v-bind, and v-on syntax instead of relying on unsupported directives.
<!doctype html>
<html><body>
<div v-scope="{ count: 0 }" v-effect="console.log(count)"></div>
<div v-if="ok" v-bind:title="title" @click="count++"></div>
<script src="https://unpkg.com/petite-vue" init></script>
</body></html>
Good avoids this rule's finding under the configuration above; other rules may still report diagnostics.
petite-vue/valid-v-effect
Require v-effect to have a non-empty expression
Default severity: error
Presets: none
Automatic fix: None; review the suggested change
Applies to: HTML documents detected as petite-vue; ordinary Vue SFCs are outside this rule's scope.
Options: No rule-specific options. Severity and preset selection are configurable.
Configuration (Vite+)
import { defineConfig } from "@vizejs/vite-plugin/vite-plus";
export default defineConfig({
lint: {
vize: {
"preset": "incremental",
"rules": {
"petite-vue/valid-v-effect": "error"
}
},
},
});
vp run lint
Bad
Each v-effect has no executable expression: its value is missing, empty, or only whitespace.
<!doctype html>
<html><body>
<div v-effect></div>
<div v-effect=""></div>
<div v-effect=" "></div>
<script src="https://unpkg.com/petite-vue" init></script>
</body></html>
Good
Both v-effect values contain an expression: one updates el.textContent, and the other increments count. This rule checks for a nonempty expression, not the effect’s business logic.
<!doctype html>
<html><body>
<div v-effect="el.textContent = count"></div>
<div v-effect="count++"></div>
<script src="https://unpkg.com/petite-vue" init></script>
</body></html>
Good avoids this rule's finding under the configuration above; other rules may still report diagnostics.
petite-vue/valid-v-scope
Require v-scope to bind an object literal
Default severity: error
Presets: none
Automatic fix: None; review the suggested change
Applies to: HTML documents detected as petite-vue; ordinary Vue SFCs are outside this rule's scope.
Options: No rule-specific options. Severity and preset selection are configurable.
Configuration (Vite+)
import { defineConfig } from "@vizejs/vite-plugin/vite-plus";
export default defineConfig({
lint: {
vize: {
"preset": "incremental",
"rules": {
"petite-vue/valid-v-scope": "error"
}
},
},
});
vp run lint
Bad
The four nonempty v-scope values are an identifier, a call, arithmetic, and a number; none parses as an object literal.
<!doctype html>
<html><body>
<div v-scope="count"></div>
<div v-scope="foo()"></div>
<div v-scope="a + b"></div>
<div v-scope="123"></div>
<script src="https://unpkg.com/petite-vue" init></script>
</body></html>
Good
A valueless v-scope uses the root scope. The other values are object literals, including the parenthesized object, which the rule accepts.
<!doctype html>
<html><body>
<div v-scope></div>
<div v-scope="{}"></div>
<div v-scope="{ count: 0 }"></div>
<div v-scope="({ count: 0 })"></div>
<script src="https://unpkg.com/petite-vue" init></script>
</body></html>
Good avoids this rule's finding under the configuration above; other rules may still report diagnostics.
vue/multi-word-component-names
Require component names to be multi-word
Default severity: error
Presets: essential, nuxt, opinionated
Automatic fix: None; review the suggested change
Applies to: Vue SFC templates and blocks, with script context where the rule requires it
Options: No rule-specific options. Severity and preset selection are configurable.
The filename is the finding. Rename the same component; changing a child tag does not fix it.
Configuration (Vite+)
import { defineConfig } from "@vizejs/vite-plugin/vite-plus";
export default defineConfig({
lint: {
vize: {
"preset": "incremental",
"rules": {
"vue/multi-word-component-names": "error"
}
},
},
});
vp run lint
Bad
Item.vue gives the component a single-word name.
Item.vue
<template><p>Item</p></template>
Good
TodoItem.vue gives the same template a multi-word component name.
TodoItem.vue
<template><p>Item</p></template>
Good avoids this rule's finding under the configuration above; other rules may still report diagnostics.
vue/no-child-content
Disallow child content when using v-html or v-text
Default severity: error
Presets: essential, happy-path, nuxt, ecosystem, opinionated
Automatic fix: None; review the suggested change
Applies to: Vue SFC templates and blocks, with script context where the rule requires it
Options: No rule-specific options. Severity and preset selection are configurable.
Configuration (Vite+)
import { defineConfig } from "@vizejs/vite-plugin/vite-plus";
export default defineConfig({
lint: {
vize: {
"preset": "incremental",
"rules": {
"vue/no-child-content": "error"
}
},
},
});
vp run lint
Bad
v-text replaces the paragraph content, so the authored fallback text cannot survive that directive.
<template>
<p v-text="message">Fallback text</p>
</template>
Good
Removing the child text leaves v-text as the single source of paragraph content.
<template>
<p v-text="message" />
</template>
Good avoids this rule's finding under the configuration above; other rules may still report diagnostics.
vue/no-deprecated-filter
Disallow deprecated Vue 2 filter syntax using the pipe operator
Default severity: error
Presets: none
Automatic fix: None; review the suggested change
Applies to: Vue SFC templates and blocks, with script context where the rule requires it
Options: No rule-specific options. Severity and preset selection are configurable.
Configuration (Vite+)
import { defineConfig } from "@vizejs/vite-plugin/vite-plus";
export default defineConfig({
lint: {
vize: {
"preset": "incremental",
"rules": {
"vue/no-deprecated-filter": "error"
}
},
},
});
vp run lint
Bad
The pipe uses the removed Vue filter syntax to apply capitalize.
<template>
{{ message | capitalize }}
</template>
Good
Calling capitalize(message) applies the transformation as an ordinary expression.
<template>
{{ capitalize(message) }}
</template>
Good avoids this rule's finding under the configuration above; other rules may still report diagnostics.
vue/no-deprecated-functional-template
Disallow the functional attribute on the SFC <template>
Default severity: error
Presets: ecosystem, essential, happy-path, nuxt, opinionated
Automatic fix: None; review the suggested change
Applies to: Vue SFC templates and blocks, with script context where the rule requires it
Options: No rule-specific options. Severity and preset selection are configurable.
Configuration (Vite+)
import { defineConfig } from "@vizejs/vite-plugin/vite-plus";
export default defineConfig({
lint: {
vize: {
"preset": "incremental",
"rules": {
"vue/no-deprecated-functional-template": "error"
}
},
},
});
vp run lint
Bad
The SFC template has the removed functional attribute and reads the old props context.
<template functional>
<div>{{ props.msg }}</div>
</template>
Good
The ordinary template omits functional and reads the component binding msg directly.
<template>
<div>{{ msg }}</div>
</template>
Good avoids this rule's finding under the configuration above; other rules may still report diagnostics.
vue/no-deprecated-html-element-is
Disallow the is attribute on native HTML elements
Default severity: error
Presets: none
Automatic fix: None; review the suggested change
Applies to: Vue SFC templates and blocks, with script context where the rule requires it
Options: No rule-specific options. Severity and preset selection are configurable.
Configuration (Vite+)
import { defineConfig } from "@vizejs/vite-plugin/vite-plus";
export default defineConfig({
lint: {
vize: {
"preset": "incremental",
"rules": {
"vue/no-deprecated-html-element-is": "error"
}
},
},
});
vp run lint
Bad
A native div uses the old unprefixed is attribute to request a Vue component.
<template>
<div is="MyComponent" />
</template>
Good
A dynamic component uses :is; the native-element spelling explicitly uses the vue: prefix.
<template>
<component :is="MyComponent" />
<div is="vue:MyComponent" />
</template>
Good avoids this rule's finding under the configuration above; other rules may still report diagnostics.
vue/no-deprecated-inline-template
Disallow the deprecated inline-template attribute
Default severity: error
Presets: none
Automatic fix: None; review the suggested change
Applies to: Vue SFC templates and blocks, with script context where the rule requires it
Options: No rule-specific options. Severity and preset selection are configurable.
Configuration (Vite+)
import { defineConfig } from "@vizejs/vite-plugin/vite-plus";
export default defineConfig({
lint: {
vize: {
"preset": "incremental",
"rules": {
"vue/no-deprecated-inline-template": "error"
}
},
},
});
vp run lint
Bad
Card uses the deprecated inline-template attribute for its supplied content.
<template>
<Card inline-template><p>Details</p></Card>
</template>
Good
The same content is passed normally without the inline-template attribute.
<template>
<Card><p>Details</p></Card>
</template>
Good avoids this rule's finding under the configuration above; other rules may still report diagnostics.
vue/no-deprecated-router-link-tag-prop
Disallow the tag prop on <router-link>
Default severity: error
Presets: none
Automatic fix: None; review the suggested change
Applies to: Vue SFC templates and blocks, with script context where the rule requires it
Options: No rule-specific options. Severity and preset selection are configurable.
Configuration (Vite+)
import { defineConfig } from "@vizejs/vite-plugin/vite-plus";
export default defineConfig({
lint: {
vize: {
"preset": "incremental",
"rules": {
"vue/no-deprecated-router-link-tag-prop": "error"
}
},
},
});
vp run lint
Bad
RouterLink uses the removed tag prop to request a button element.
<template>
<router-link to="/home" tag="button">Home</router-link>
</template>
Good
The slot provides navigate to an explicitly authored button.
<template>
<router-link to="/home" v-slot="{ navigate }">
<button @click="navigate">Home</button>
</router-link>
</template>
Good avoids this rule's finding under the configuration above; other rules may still report diagnostics.
vue/no-deprecated-scope-attribute
Disallow the deprecated scope attribute on <template>
Default severity: error
Presets: essential, happy-path, nuxt, ecosystem, opinionated
Automatic fix: None; review the suggested change
Applies to: Vue SFC templates and blocks, with script context where the rule requires it
Options: No rule-specific options. Severity and preset selection are configurable.
Configuration (Vite+)
import { defineConfig } from "@vizejs/vite-plugin/vite-plus";
export default defineConfig({
lint: {
vize: {
"preset": "incremental",
"rules": {
"vue/no-deprecated-scope-attribute": "error"
}
},
},
});
vp run lint
Bad
The slot template declares props through the deprecated scope attribute.
<template>
<Card><template scope="props">{{ props.name }}</template></Card>
</template>
Good
The default-slot directive declares the same props binding through current slot syntax.
<template>
<Card><template #default="props">{{ props.name }}</template></Card>
</template>
Good avoids this rule's finding under the configuration above; other rules may still report diagnostics.
vue/no-deprecated-slot-attribute
Disallow the deprecated slot attribute
Default severity: error
Presets: essential, happy-path, nuxt, ecosystem, opinionated
Automatic fix: None; review the suggested change
Applies to: Vue SFC templates and blocks, with script context where the rule requires it
Options: No rule-specific options. Severity and preset selection are configurable.
Configuration (Vite+)
import { defineConfig } from "@vizejs/vite-plugin/vite-plus";
export default defineConfig({
lint: {
vize: {
"preset": "incremental",
"rules": {
"vue/no-deprecated-slot-attribute": "error"
}
},
},
});
vp run lint
Bad
The header slot is selected through the old slot attribute.
<template>
<Foo>
<template slot="header"><h1>Title</h1></template>
<div :slot="name">Title</div>
</Foo>
</template>
Good
v-slot:header explicitly selects the header slot with the current directive.
<template>
<Foo>
<template v-slot:header><h1>Title</h1></template>
</Foo>
</template>
Good avoids this rule's finding under the configuration above; other rules may still report diagnostics.
vue/no-deprecated-slot-scope-attribute
Disallow the deprecated slot-scope attribute
Default severity: error
Presets: essential, happy-path, nuxt, ecosystem, opinionated
Automatic fix: None; review the suggested change
Applies to: Vue SFC templates and blocks, with script context where the rule requires it
Options: No rule-specific options. Severity and preset selection are configurable.
Configuration (Vite+)
import { defineConfig } from "@vizejs/vite-plugin/vite-plus";
export default defineConfig({
lint: {
vize: {
"preset": "incremental",
"rules": {
"vue/no-deprecated-slot-scope-attribute": "error"
}
},
},
});
vp run lint
Bad
The template receives slot props through the deprecated slot-scope attribute.
<template>
<Card><template slot-scope="props">{{ props.name }}</template></Card>
</template>
Good
The #default directive receives those props without slot-scope.
<template>
<Card><template #default="props">{{ props.name }}</template></Card>
</template>
Good avoids this rule's finding under the configuration above; other rules may still report diagnostics.
vue/no-deprecated-v-bind-sync
Disallow the deprecated .sync modifier on v-bind
Default severity: error
Presets: essential, happy-path, nuxt, ecosystem, opinionated
Automatic fix: None; review the suggested change
Applies to: Vue SFC templates and blocks, with script context where the rule requires it
Options: No rule-specific options. Severity and preset selection are configurable.
Configuration (Vite+)
import { defineConfig } from "@vizejs/vite-plugin/vite-plus";
export default defineConfig({
lint: {
vize: {
"preset": "incremental",
"rules": {
"vue/no-deprecated-v-bind-sync": "error"
}
},
},
});
vp run lint
Bad
The bindings use the removed .sync modifier, including its combination with .camel.
<template>
<MyComponent :title.sync="title" />
<MyComponent v-bind:title.sync="title" />
<MyComponent :title.sync.camel="title" />
</template>
Good
Use an ordinary one-way title binding or v-model:title when an update channel is required.
<template>
<MyComponent :title="title" />
<MyComponent v-model:title="title" />
</template>
Good avoids this rule's finding under the configuration above; other rules may still report diagnostics.
vue/no-deprecated-v-on-native-modifier
Disallow the deprecated .native modifier on v-on
Default severity: error
Presets: essential, happy-path, nuxt, ecosystem, opinionated
Automatic fix: None; review the suggested change
Applies to: Vue SFC templates and blocks, with script context where the rule requires it
Options: No rule-specific options. Severity and preset selection are configurable.
Configuration (Vite+)
import { defineConfig } from "@vizejs/vite-plugin/vite-plus";
export default defineConfig({
lint: {
vize: {
"preset": "incremental",
"rules": {
"vue/no-deprecated-v-on-native-modifier": "error"
}
},
},
});
vp run lint
Bad
The component handlers use the removed .native event modifier.
<template>
<MyComponent @click.native="handler" />
<MyComponent v-on:click.native="handler" />
<MyComponent @click.native.stop="handler" />
</template>
Good
The handlers omit .native and preserve other event modifiers such as .stop.
<template>
<MyComponent @click="handler" />
<MyComponent @click.stop="handler" />
</template>
Good avoids this rule's finding under the configuration above; other rules may still report diagnostics.
vue/no-deprecated-v-on-number-modifiers
Disallow deprecated numeric keyCode modifiers on v-on
Default severity: error
Presets: ecosystem, essential, happy-path, nuxt, opinionated
Automatic fix: None; review the suggested change
Applies to: Vue SFC templates and blocks, with script context where the rule requires it
Options: No rule-specific options. Severity and preset selection are configurable.
Configuration (Vite+)
import { defineConfig } from "@vizejs/vite-plugin/vite-plus";
export default defineConfig({
lint: {
vize: {
"preset": "incremental",
"rules": {
"vue/no-deprecated-v-on-number-modifiers": "error"
}
},
},
});
vp run lint
Bad
The keyboard handlers identify keys by the removed numeric codes 13 and 27.
<template>
<input @keyup.13="submit" />
<input v-on:keyup.27="cancel" />
<input @keyup.13.stop="submit" />
</template>
Good
The handlers use the named enter and esc key modifiers.
<template>
<input @keyup.enter="submit" />
<input @keyup.esc="cancel" />
</template>
Good avoids this rule's finding under the configuration above; other rules may still report diagnostics.
vue/no-dupe-v-else-if
Disallow duplicate conditions in v-if / v-else-if chains
Default severity: error
Presets: essential, happy-path, nuxt, ecosystem, opinionated
Automatic fix: None; review the suggested change
Applies to: Vue SFC templates and blocks, with script context where the rule requires it
Options: No rule-specific options. Severity and preset selection are configurable.
Configuration (Vite+)
import { defineConfig } from "@vizejs/vite-plugin/vite-plus";
export default defineConfig({
lint: {
vize: {
"preset": "incremental",
"rules": {
"vue/no-dupe-v-else-if": "error"
}
},
},
});
vp run lint
Bad
The else-if repeats the ready condition already tested by the first branch, making that later branch unreachable.
<template>
<p v-if="status === 'ready'">Ready</p>
<p v-else-if="status === 'ready'">Still ready</p>
</template>
Good
The second branch tests loading, a distinct state that can reach the else-if.
<template>
<p v-if="status === 'ready'">Ready</p>
<p v-else-if="status === 'loading'">Loading</p>
</template>
Good avoids this rule's finding under the configuration above; other rules may still report diagnostics.
vue/no-duplicate-attributes
Disallow duplicate attributes on the same element
Default severity: error
Presets: essential, happy-path, nuxt, ecosystem, opinionated
Automatic fix: None; review the suggested change
Applies to: Vue SFC templates and blocks, with script context where the rule requires it
Options: No rule-specific options. Severity and preset selection are configurable.
Configuration (Vite+)
import { defineConfig } from "@vizejs/vite-plugin/vite-plus";
export default defineConfig({
lint: {
vize: {
"preset": "incremental",
"rules": {
"vue/no-duplicate-attributes": "error"
}
},
},
});
vp run lint
Bad
The same button declares class twice instead of one combined class value.
<template>
<button class="primary" class="large">Save</button>
</template>
Good
Both class tokens appear in a single class attribute.
<template>
<button class="primary large">Save</button>
</template>
Good avoids this rule's finding under the configuration above; other rules may still report diagnostics.
vue/no-multiple-template-root
Disallow multiple root nodes in a template
Default severity: error
Presets: none
Automatic fix: None; review the suggested change
Applies to: Vue SFC templates and blocks, with script context where the rule requires it
Options: No rule-specific options. Severity and preset selection are configurable.
Enable only for a single-root contract. Vue 3 normally supports fragments.
Configuration (Vite+)
import { defineConfig } from "@vizejs/vite-plugin/vite-plus";
export default defineConfig({
lint: {
vize: {
"preset": "incremental",
"rules": {
"vue/no-multiple-template-root": "error"
}
},
},
});
vp run lint
Bad
The opt-in single-root convention sees two sibling paragraphs at the template root.
<template>
<p>First</p>
<p>Second</p>
</template>
Good
A section wraps the paragraphs into one root; enable this convention only when a single-root contract is intended.
<template>
<section><p>First</p><p>Second</p></section>
</template>
Good avoids this rule's finding under the configuration above; other rules may still report diagnostics.
vue/no-mutating-props
Disallow mutating component props
Default severity: error
Presets: essential, happy-path, nuxt, ecosystem, opinionated
Automatic fix: None; review the suggested change
Applies to: Vue SFC templates and blocks, with script context where the rule requires it
Options: See typed options and defaults.
Configuration (Vite+)
import { defineConfig } from "@vizejs/vite-plugin/vite-plus";
export default defineConfig({
lint: {
vize: {
"preset": "incremental",
"rules": {
"vue/no-mutating-props": "error"
}
},
},
});
vp run lint
Bad
Incrementing props.count writes directly to a value supplied by the parent.
<script setup lang="ts">
const props = defineProps<{ count: number }>();
props.count++;
</script>
Good
The component emits update:count with the next value, leaving the parent responsible for updating the prop.
<script setup lang="ts">
const props = defineProps<{ count: number }>();
const emit = defineEmits<{ "update:count": [value: number] }>();
function increment() {
emit("update:count", props.count + 1);
}
</script>
Good avoids this rule's finding under the configuration above; other rules may still report diagnostics.
vue/no-reserved-component-names
Disallow the use of reserved names as component names
Default severity: error
Presets: essential, happy-path, nuxt, ecosystem, opinionated
Automatic fix: None; review the suggested change
Applies to: Vue SFC templates and blocks, with script context where the rule requires it
Options: No rule-specific options. Severity and preset selection are configurable.
Configuration (Vite+)
import { defineConfig } from "@vizejs/vite-plugin/vite-plus";
export default defineConfig({
lint: {
vize: {
"preset": "incremental",
"rules": {
"vue/no-reserved-component-names": "error"
}
},
},
});
vp run lint
Bad
The component name button conflicts with a native HTML element name.
<script>
export default {
name: "button",
};
</script>
Good
AppButton is an application component name and does not reuse the native button name.
<script setup lang="ts">
defineOptions({ name: "AppButton" });
</script>
<template>
<Transition>
<AppButton />
</Transition>
</template>
Good avoids this rule's finding under the configuration above; other rules may still report diagnostics.
vue/no-template-key
Disallow key attribute on <template>
Default severity: error
Presets: essential, happy-path, nuxt, ecosystem, opinionated
Automatic fix: None; review the suggested change
Applies to: Vue SFC templates and blocks, with script context where the rule requires it
Options: No rule-specific options. Severity and preset selection are configurable.
Configuration (Vite+)
import { defineConfig } from "@vizejs/vite-plugin/vite-plus";
export default defineConfig({
lint: {
vize: {
"preset": "incremental",
"rules": {
"vue/no-template-key": "error"
}
},
},
});
vp run lint
Bad
A non-loop template wrapper has a key even though it is not the keyed iteration boundary.
<template>
<template :key="section"><div>Details</div></template>
</template>
Good
The key belongs to a template v-for iteration, where it identifies each repeated fragment.
<template>
<template v-for="item in items" :key="item.id"><div>{{ item.name }}</div></template>
</template>
Good avoids this rule's finding under the configuration above; other rules may still report diagnostics.
vue/no-textarea-mustache
Disallow mustache interpolation in <textarea>
Default severity: error
Presets: essential, happy-path, nuxt, ecosystem, opinionated
Automatic fix: None; review the suggested change
Applies to: Vue SFC templates and blocks, with script context where the rule requires it
Options: No rule-specific options. Severity and preset selection are configurable.
Configuration (Vite+)
import { defineConfig } from "@vizejs/vite-plugin/vite-plus";
export default defineConfig({
lint: {
vize: {
"preset": "incremental",
"rules": {
"vue/no-textarea-mustache": "error"
}
},
},
});
vp run lint
Bad
The textarea places message in child interpolation instead of binding its value.
<template>
<textarea>{{ message }}</textarea>
</template>
Good
v-model binds the editable textarea value to message.
<template>
<textarea v-model="message"></textarea>
</template>
Good avoids this rule's finding under the configuration above; other rules may still report diagnostics.
vue/no-unused-components
Disallow registering components that are not used inside templates
Default severity: warning
Presets: happy-path, nuxt, ecosystem, opinionated
Automatic fix: None; review the suggested change
Applies to: Vue SFC templates and blocks, with script context where the rule requires it
Options: No rule-specific options. Severity and preset selection are configurable.
Configuration (Vite+)
import { defineConfig } from "@vizejs/vite-plugin/vite-plus";
export default defineConfig({
lint: {
vize: {
"preset": "incremental",
"rules": {
"vue/no-unused-components": "warn"
}
},
},
});
vp run lint
Bad
UserAvatar is imported as a component but the template never renders it.
<script setup lang="ts">
import UserAvatar from "./UserAvatar.vue";
</script>
<template>
<p>{{ user.name }}</p>
</template>
Good
The template renders the imported UserAvatar and passes the user binding.
<script setup lang="ts">
import UserAvatar from "./UserAvatar.vue";
</script>
<template>
<UserAvatar :user="user" />
</template>
Good avoids this rule's finding under the configuration above; other rules may still report diagnostics.
vue/no-unused-vars
Disallow unused variable definitions in v-for and v-slot directives
Default severity: warning
Presets: essential, happy-path, nuxt, ecosystem, opinionated
Automatic fix: None; review the suggested change
Applies to: Vue SFC templates and blocks, with script context where the rule requires it
Options: No rule-specific options. Severity and preset selection are configurable.
Configuration (Vite+)
import { defineConfig } from "@vizejs/vite-plugin/vite-plus";
export default defineConfig({
lint: {
vize: {
"preset": "incremental",
"rules": {
"vue/no-unused-vars": "warn"
}
},
},
});
vp run lint
Bad
The loop declares an unused index and the slot declares foo without referencing it.
<template>
<li v-for="(item, index) in items" :key="item.id">{{ item.name }}</li>
<template v-slot="{ foo }">
<span>Hello</span>
</template>
</template>
Good
The examples consume index or mark it intentionally unused as _index, and the slot renders data. Index keys are only a usage example here, not a recommendation for stable item identity.
<template>
<li v-for="(item, index) in items" :key="index">{{ item.name }}</li>
<li v-for="(item, _index) in items" :key="item.id">{{ item.name }}</li>
<template v-slot="{ data }">
<span>{{ data }}</span>
</template>
</template>
Good avoids this rule's finding under the configuration above; other rules may still report diagnostics.
vue/no-use-v-if-with-v-for
Disallow using v-if on the same element as v-for
Default severity: warning
Presets: essential, happy-path, nuxt, ecosystem, opinionated
Automatic fix: None; review the suggested change
Applies to: Vue SFC templates and blocks, with script context where the rule requires it
Options: No rule-specific options. Severity and preset selection are configurable.
Configuration (Vite+)
import { defineConfig } from "@vizejs/vite-plugin/vite-plus";
export default defineConfig({
lint: {
vize: {
"preset": "incremental",
"rules": {
"vue/no-use-v-if-with-v-for": "warn"
}
},
},
});
vp run lint
Bad
The same list element combines v-if and v-for and tests visibility through the loop binding.
<template>
<li v-for="item in items" v-if="item.visible" :key="item.id">
{{ item.name }}
</li>
</template>
Good
A computed collection filters the visible items before the template iterates over them.
<script setup lang="ts">
const visibleItems = computed(() => items.filter((item) => item.visible));
</script>
<template>
<li v-for="item in visibleItems" :key="item.id">
{{ item.name }}
</li>
</template>
Good avoids this rule's finding under the configuration above; other rules may still report diagnostics.
vue/no-useless-template-attributes
Disallow useless attributes on <template> elements
Default severity: error
Presets: essential, happy-path, nuxt, ecosystem, opinionated
Automatic fix: None; review the suggested change
Applies to: Vue SFC templates and blocks, with script context where the rule requires it
Options: No rule-specific options. Severity and preset selection are configurable.
Configuration (Vite+)
import { defineConfig } from "@vizejs/vite-plugin/vite-plus";
export default defineConfig({
lint: {
vize: {
"preset": "incremental",
"rules": {
"vue/no-useless-template-attributes": "error"
}
},
},
});
vp run lint
Bad
The conditional template has a class, but this structural wrapper does not render a DOM element to receive it.
<template>
<section><template v-if="ready" class="notice"><p>Ready</p></template></section>
</template>
Good
The class moves to the paragraph that actually renders while v-if stays on the structural template.
<template>
<section><template v-if="ready"><p class="notice">Ready</p></template></section>
</template>
Good avoids this rule's finding under the configuration above; other rules may still report diagnostics.
vue/no-v-for-template-key-on-child
Disallow key on the child of a <template v-for>
Default severity: error
Presets: essential, happy-path, nuxt, ecosystem, opinionated
Automatic fix: None; review the suggested change
Applies to: Vue SFC templates and blocks, with script context where the rule requires it
Options: No rule-specific options. Severity and preset selection are configurable.
Configuration (Vite+)
import { defineConfig } from "@vizejs/vite-plugin/vite-plus";
export default defineConfig({
lint: {
vize: {
"preset": "incremental",
"rules": {
"vue/no-v-for-template-key-on-child": "error"
}
},
},
});
vp run lint
Bad
The child paragraph has the key while the template iteration itself has no key.
<template>
<template v-for="item in items"><p :key="item.id">{{ item.name }}</p></template>
</template>
Good
The key moves to template v-for, identifying the complete repeated fragment.
<template>
<template v-for="item in items" :key="item.id"><p>{{ item.name }}</p></template>
</template>
Good avoids this rule's finding under the configuration above; other rules may still report diagnostics.
vue/no-v-html
Warn against v-html to prevent XSS vulnerabilities
Default severity: warning
Presets: essential, happy-path, nuxt, ecosystem, opinionated
Automatic fix: None; review the suggested change
Applies to: Vue SFC templates and blocks, with script context where the rule requires it
Options: No rule-specific options. Severity and preset selection are configurable.
Configuration (Vite+)
import { defineConfig } from "@vizejs/vite-plugin/vite-plus";
export default defineConfig({
lint: {
vize: {
"preset": "incremental",
"rules": {
"vue/no-v-html": "warn"
}
},
},
});
vp run lint
Bad
v-html interprets content as HTML rather than ordinary text.
<template>
<article v-html="content" />
</template>
Good
Mustache interpolation displays content as escaped text instead of injecting HTML.
<template>
<article>{{ content }}</article>
</template>
Good avoids this rule's finding under the configuration above; other rules may still report diagnostics.
vue/no-v-text-v-html-on-component
Disallow v-text / v-html on component elements
Default severity: error
Presets: essential, happy-path, nuxt, ecosystem, opinionated
Automatic fix: None; review the suggested change
Applies to: Vue SFC templates and blocks, with script context where the rule requires it
Options: No rule-specific options. Severity and preset selection are configurable.
Configuration (Vite+)
import { defineConfig } from "@vizejs/vite-plugin/vite-plus";
export default defineConfig({
lint: {
vize: {
"preset": "incremental",
"rules": {
"vue/no-v-text-v-html-on-component": "error"
}
},
},
});
vp run lint
Bad
The component tag receives v-html or v-text, which replaces element content rather than supplying component slots.
<template>
<MyComponent v-html="content" />
<MyComponent v-text="content" />
</template>
Good
Native HTML targets can receive the directives; MyComponent receives its content through the default slot.
<template>
<div v-html="content"></div>
<component is="div" v-html="content" />
<MyComponent>{{ content }}</MyComponent>
</template>
Good avoids this rule's finding under the configuration above; other rules may still report diagnostics.
vue/permitted-contents
Enforce HTML content model rules
Default severity: error
Presets: happy-path, nuxt, ecosystem, opinionated
Automatic fix: None; review the suggested change
Applies to: Vue SFC templates and blocks, with script context where the rule requires it
Options: No rule-specific options. Severity and preset selection are configurable.
Configuration (Vite+)
import { defineConfig } from "@vizejs/vite-plugin/vite-plus";
export default defineConfig({
lint: {
vize: {
"preset": "incremental",
"rules": {
"vue/permitted-contents": "error"
}
},
},
});
vp run lint
Bad
The examples put block content in p, omit the table body, nest interactive controls, or put a div directly inside ul.
<template>
<p><div>block in a paragraph</div></p>
<table><tr><td>row without tbody</td></tr></table>
<a href="#"><button type="button">nested control</button></a>
<ul><div>not a list item</div></ul>
</template>
Good
The examples use inline paragraph content, an explicit tbody, and li children. The custom MyItem is not treated as a known native ul child.
<template>
<p><span>inline in a paragraph</span></p>
<table><tbody><tr><td>cell</td></tr></tbody></table>
<ul><li>list item</li><MyItem /></ul>
</template>
Good avoids this rule's finding under the configuration above; other rules may still report diagnostics.
vue/require-component-is
Require v-bind:is on <component> elements
Default severity: error
Presets: essential, happy-path, nuxt, ecosystem, opinionated
Automatic fix: None; review the suggested change
Applies to: Vue SFC templates and blocks, with script context where the rule requires it
Options: No rule-specific options. Severity and preset selection are configurable.
Configuration (Vite+)
import { defineConfig } from "@vizejs/vite-plugin/vite-plus";
export default defineConfig({
lint: {
vize: {
"preset": "incremental",
"rules": {
"vue/require-component-is": "error"
}
},
},
});
vp run lint
Bad
The dynamic <component> has no is target, so Vue cannot choose a component to render.
<template>
<component />
</template>
Good
:is="currentComponent" supplies the component selection; the binding may change at runtime.
<template>
<component :is="currentComponent" />
</template>
Good avoids this rule's finding under the configuration above; other rules may still report diagnostics.
vue/require-toggle-inside-transition
Require a toggle on the element wrapped by <transition>
Default severity: error
Presets: essential, happy-path, nuxt, ecosystem, opinionated
Automatic fix: None; review the suggested change
Applies to: Vue SFC templates and blocks, with script context where the rule requires it
Options: No rule-specific options. Severity and preset selection are configurable.
Configuration (Vite+)
import { defineConfig } from "@vizejs/vite-plugin/vite-plus";
export default defineConfig({
lint: {
vize: {
"preset": "incremental",
"rules": {
"vue/require-toggle-inside-transition": "error"
}
},
},
});
vp run lint
Bad
The static child inside <Transition> has no conditional visibility or dynamic selection to trigger an enter/leave change.
<template>
<transition>
<div>content</div>
</transition>
</template>
Good
v-if="show" changes whether the child exists, giving the transition an enter/leave boundary.
<template>
<transition>
<div v-if="show">content</div>
</transition>
</template>
Good avoids this rule's finding under the configuration above; other rules may still report diagnostics.
vue/require-v-for-key
Require v-bind:key with v-for directives
Default severity: error
Presets: essential, happy-path, nuxt, ecosystem, opinionated
Automatic fix: None; review the suggested change
Applies to: Vue SFC templates and blocks, with script context where the rule requires it
Options: No rule-specific options. Severity and preset selection are configurable.
Configuration (Vite+)
import { defineConfig } from "@vizejs/vite-plugin/vite-plus";
export default defineConfig({
lint: {
vize: {
"preset": "incremental",
"rules": {
"vue/require-v-for-key": "error"
}
},
},
});
vp run lint
Bad
Each repeated <li> lacks a key that identifies its corresponding item during list updates.
<template>
<li v-for="item in items">{{ item.name }}</li>
</template>
Good
:key="item.id" gives each repeated node the item's identity rather than its current position.
<template>
<li v-for="item in items" :key="item.id">{{ item.name }}</li>
</template>
Good avoids this rule's finding under the configuration above; other rules may still report diagnostics.
vue/use-v-on-exact
Enforce .exact modifier on v-on when there are modifier-based handlers
Default severity: warning
Presets: essential, nuxt, opinionated
Automatic fix: None; review the suggested change
Applies to: Vue SFC templates and blocks, with script context where the rule requires it
Options: No rule-specific options. Severity and preset selection are configurable.
Configuration (Vite+)
import { defineConfig } from "@vizejs/vite-plugin/vite-plus";
export default defineConfig({
lint: {
vize: {
"preset": "incremental",
"rules": {
"vue/use-v-on-exact": "warn"
}
},
},
});
vp run lint
Bad
The plain click handler can also run on Ctrl-click, overlapping the separate .ctrl handler.
<template>
<button type="button" @click="handleClick" @click.ctrl="handleCtrlClick">
Save
</button>
</template>
Good
.exact limits the ordinary click handler to clicks without modifier keys; the Ctrl-specific handler remains separate.
<template>
<button
type="button"
@click.exact="handleClick"
@click.ctrl="handleCtrlClick"
>
Save
</button>
</template>
Good avoids this rule's finding under the configuration above; other rules may still report diagnostics.
vue/valid-attribute-name
Require valid attribute names
Default severity: error
Presets: essential, happy-path, nuxt, ecosystem, opinionated
Automatic fix: None; review the suggested change
Applies to: Vue SFC templates and blocks, with script context where the rule requires it
Options: No rule-specific options. Severity and preset selection are configurable.
Bad diagnostic: parser/template
Malformed attribute spelling is diagnosed by parser/template before this defensive rule sees an attribute. Bad therefore reports parser/template; it does not promise a separate vue/valid-attribute-name finding.
Configuration (Vite+)
import { defineConfig } from "@vizejs/vite-plugin/vite-plus";
export default defineConfig({
lint: {
vize: {
"preset": "incremental",
"rules": {
"vue/valid-attribute-name": "error"
}
},
},
});
vp run lint
Bad
The quote inside my"attr makes the attribute name malformed. This example produces the parser's parser/template diagnostic rather than promising a separate rule diagnostic.
<template>
<div my"attr="value"></div>
</template>
Good
my-attr is a well-formed attribute name, so the template parser can read the attribute and its value.
<template>
<div my-attr="value"></div>
</template>
Good avoids this rule's finding under the configuration above; other rules may still report diagnostics.
vue/valid-template-root
Enforce a valid <template> root for Vue 3 fragment semantics
Default severity: error
Presets: essential, happy-path, nuxt, ecosystem, opinionated
Automatic fix: None; review the suggested change
Applies to: Vue SFC templates and blocks, with script context where the rule requires it
Options: No rule-specific options. Severity and preset selection are configurable.
Configuration (Vite+)
import { defineConfig } from "@vizejs/vite-plugin/vite-plus";
export default defineConfig({
lint: {
vize: {
"preset": "incremental",
"rules": {
"vue/valid-template-root": "error"
}
},
},
});
vp run lint
Bad
A plain nested <template> occupies the template root without a directive that gives it a rendering role.
<template>
<template>content</template>
</template>
Good
The <div> is a renderable root element. This example does not impose a universal single-root restriction on Vue 3 fragments.
<template>
<div>content</div>
</template>
Good avoids this rule's finding under the configuration above; other rules may still report diagnostics.
vue/valid-v-bind
Enforce valid v-bind directives
Default severity: error
Presets: essential, happy-path, nuxt, ecosystem, opinionated
Automatic fix: None; review the suggested change
Applies to: Vue SFC templates and blocks, with script context where the rule requires it
Options: No rule-specific options. Severity and preset selection are configurable.
Configuration (Vite+)
import { defineConfig } from "@vizejs/vite-plugin/vite-plus";
export default defineConfig({
lint: {
vize: {
"preset": "incremental",
"rules": {
"vue/valid-v-bind": "error"
}
},
},
});
vp run lint
Bad
The bare v-bind has no object expression, and the empty argument form has no attribute name.
<template>
<div v-bind></div>
<div :></div>
</template>
Good
Provide an attribute and expression, bind an object, or use Vue 3.4+ same-name shorthand such as :loading.
<template>
<div :class="panelClass"></div>
<div v-bind="{ class: panelClass }"></div>
<div :loading></div>
</template>
Good avoids this rule's finding under the configuration above; other rules may still report diagnostics.
vue/valid-v-cloak
Enforce valid v-cloak directives
Default severity: error
Presets: essential, happy-path, nuxt, ecosystem, opinionated
Automatic fix: None; review the suggested change
Applies to: Vue SFC templates and blocks, with script context where the rule requires it
Options: No rule-specific options. Severity and preset selection are configurable.
Configuration (Vite+)
import { defineConfig } from "@vizejs/vite-plugin/vite-plus";
export default defineConfig({
lint: {
vize: {
"preset": "incremental",
"rules": {
"vue/valid-v-cloak": "error"
}
},
},
});
vp run lint
Bad
v-cloak is given a value, argument, or modifier even though it accepts none of those.
<template>
<div v-cloak="foo"></div>
<div v-cloak:arg></div>
<div v-cloak.mod></div>
</template>
Good
Use bare v-cloak; CSS can hide the element until Vue removes that attribute after mounting.
<template>
<div v-cloak></div>
</template>
Good avoids this rule's finding under the configuration above; other rules may still report diagnostics.
vue/valid-v-else
Enforce valid v-else directives
Default severity: error
Presets: essential, happy-path, nuxt, ecosystem, opinionated
Automatic fix: Available for supported findings
Applies to: Vue SFC templates and blocks, with script context where the rule requires it
Options: No rule-specific options. Severity and preset selection are configurable.
Configuration (Vite+)
import { defineConfig } from "@vizejs/vite-plugin/vite-plus";
export default defineConfig({
lint: {
vize: {
"preset": "incremental",
"rules": {
"vue/valid-v-else": "error"
}
},
},
});
vp run lint
Bad
The examples give v-else an expression, combine it with v-if, or omit its adjacent preceding conditional branch.
<template>
<div v-else="ready"></div>
<div v-else v-if="ready"></div>
<div v-else></div>
</template>
Good
Place bare v-else immediately after the corresponding v-if branch.
<template>
<div v-if="ready"></div>
<div v-else></div>
</template>
Good avoids this rule's finding under the configuration above; other rules may still report diagnostics.
vue/valid-v-for
Enforce valid v-for directives
Default severity: error
Presets: essential, happy-path, nuxt, ecosystem, opinionated
Automatic fix: None; review the suggested change
Applies to: Vue SFC templates and blocks, with script context where the rule requires it
Options: No rule-specific options. Severity and preset selection are configurable.
Configuration (Vite+)
import { defineConfig } from "@vizejs/vite-plugin/vite-plus";
export default defineConfig({
lint: {
vize: {
"preset": "incremental",
"rules": {
"vue/valid-v-for": "error"
}
},
},
});
vp run lint
Bad
The loops omit their iteration expression or add an unsupported .stop modifier.
<template>
<div v-for></div>
<div v-for=""></div>
<div v-for.stop="item in items"></div>
</template>
Good
Use item in items or (item, index) of items with a complete iteration expression and the shown keys.
<template>
<div v-for="item in items" :key="item.id"></div>
<div v-for="(item, index) of items" :key="index"></div>
</template>
Good avoids this rule's finding under the configuration above; other rules may still report diagnostics.
vue/valid-v-html
Enforce valid v-html directives
Default severity: error
Presets: essential, happy-path, nuxt, ecosystem, opinionated
Automatic fix: None; review the suggested change
Applies to: Vue SFC templates and blocks, with script context where the rule requires it
Options: No rule-specific options. Severity and preset selection are configurable.
Configuration (Vite+)
import { defineConfig } from "@vizejs/vite-plugin/vite-plus";
export default defineConfig({
lint: {
vize: {
"preset": "incremental",
"rules": {
"vue/valid-v-html": "error"
}
},
},
});
vp run lint
Bad
v-html lacks its expression or uses an argument/modifier that this directive does not support.
<template>
<div v-html></div>
<div v-html:arg="foo"></div>
<div v-html.mod="foo"></div>
</template>
Good
v-html="html" supplies a valid expression. Syntax validity does not sanitize HTML or make untrusted content safe.
<template>
<div v-html="html"></div>
</template>
Good avoids this rule's finding under the configuration above; other rules may still report diagnostics.
vue/valid-v-if
Enforce valid v-if directives
Default severity: error
Presets: essential, happy-path, nuxt, ecosystem, opinionated
Automatic fix: None; review the suggested change
Applies to: Vue SFC templates and blocks, with script context where the rule requires it
Options: No rule-specific options. Severity and preset selection are configurable.
Configuration (Vite+)
import { defineConfig } from "@vizejs/vite-plugin/vite-plus";
export default defineConfig({
lint: {
vize: {
"preset": "incremental",
"rules": {
"vue/valid-v-if": "error"
}
},
},
});
vp run lint
Bad
The conditions omit an expression or combine v-if with an else directive on the same node.
<template>
<div v-if></div>
<div v-if=""></div>
<div v-if="ready" v-else></div>
</template>
Good
Each v-if has a nonempty condition such as ready or count > 0, without an incompatible else directive.
<template>
<div v-if="ready"></div>
<div v-if="count > 0"></div>
</template>
Good avoids this rule's finding under the configuration above; other rules may still report diagnostics.
vue/valid-v-memo
Enforce valid v-memo directives
Default severity: error
Presets: essential, happy-path, nuxt, ecosystem, opinionated
Automatic fix: None; review the suggested change
Applies to: Vue SFC templates and blocks, with script context where the rule requires it
Options: No rule-specific options. Severity and preset selection are configurable.
Configuration (Vite+)
import { defineConfig } from "@vizejs/vite-plugin/vite-plus";
export default defineConfig({
lint: {
vize: {
"preset": "incremental",
"rules": {
"vue/valid-v-memo": "error"
}
},
},
});
vp run lint
Bad
Bare v-memo gives Vue no dependency expression for deciding when to reuse the subtree.
<template>
<div v-memo></div>
</template>
Good
v-memo="[valueA, valueB]" supplies the dependency array used for memoization.
<template>
<div v-memo="[valueA, valueB]">{{ label }}</div>
</template>
Good avoids this rule's finding under the configuration above; other rules may still report diagnostics.
vue/valid-v-model
Enforce valid v-model directives
Default severity: error
Presets: essential, happy-path, nuxt, ecosystem, opinionated
Automatic fix: None; review the suggested change
Applies to: Vue SFC templates and blocks, with script context where the rule requires it
Options: No rule-specific options. Severity and preset selection are configurable.
Configuration (Vite+)
import { defineConfig } from "@vizejs/vite-plugin/vite-plus";
export default defineConfig({
lint: {
vize: {
"preset": "incremental",
"rules": {
"vue/valid-v-model": "error"
}
},
},
});
vp run lint
Bad
A native <div> cannot use v-model as a form control, and a bare input directive has no writable target expression.
<template>
<div v-model="value"></div>
<input v-model />
</template>
Good
Bind the input, select, textarea, or custom component to the shown writable variables.
<template>
<input v-model="value" />
<select v-model="selected"></select>
<textarea v-model="text"></textarea>
<MyInput v-model="value" />
</template>
Good avoids this rule's finding under the configuration above; other rules may still report diagnostics.
vue/valid-v-on
Enforce valid v-on directives
Default severity: error
Presets: essential, happy-path, nuxt, ecosystem, opinionated
Automatic fix: None; review the suggested change
Applies to: Vue SFC templates and blocks, with script context where the rule requires it
Options: No rule-specific options. Severity and preset selection are configurable.
Configuration (Vite+)
import { defineConfig } from "@vizejs/vite-plugin/vite-plus";
export default defineConfig({
lint: {
vize: {
"preset": "incremental",
"rules": {
"vue/valid-v-on": "error"
}
},
},
});
vp run lint
Bad
The listener forms omit an event argument or their required handler/object expression.
<template>
<div v-on></div>
<div @></div>
<div @click></div>
</template>
Good
Use an event with its handler, or pass a listener object to argument-free v-on.
<template>
<div @click="handleClick"></div>
<div v-on="{ click: handleClick }"></div>
</template>
Good avoids this rule's finding under the configuration above; other rules may still report diagnostics.
vue/valid-v-once
Enforce valid v-once directives
Default severity: error
Presets: essential, happy-path, nuxt, ecosystem, opinionated
Automatic fix: None; review the suggested change
Applies to: Vue SFC templates and blocks, with script context where the rule requires it
Options: No rule-specific options. Severity and preset selection are configurable.
Configuration (Vite+)
import { defineConfig } from "@vizejs/vite-plugin/vite-plus";
export default defineConfig({
lint: {
vize: {
"preset": "incremental",
"rules": {
"vue/valid-v-once": "error"
}
},
},
});
vp run lint
Bad
v-once has a value, argument, or modifier, although this directive is a value-free render-once marker.
<template>
<div v-once="foo"></div>
<div v-once:arg></div>
<div v-once.mod></div>
</template>
Good
Bare v-once marks the subtree for one-time rendering without unsupported syntax.
<template>
<div v-once></div>
</template>
Good avoids this rule's finding under the configuration above; other rules may still report diagnostics.
vue/valid-v-show
Enforce valid v-show directives
Default severity: error
Presets: essential, happy-path, nuxt, ecosystem, opinionated
Automatic fix: None; review the suggested change
Applies to: Vue SFC templates and blocks, with script context where the rule requires it
Options: No rule-specific options. Severity and preset selection are configurable.
Configuration (Vite+)
import { defineConfig } from "@vizejs/vite-plugin/vite-plus";
export default defineConfig({
lint: {
vize: {
"preset": "incremental",
"rules": {
"vue/valid-v-show": "error"
}
},
},
});
vp run lint
Bad
v-show lacks its visibility expression or is placed on a <template> that has no DOM element whose display can be changed.
<template>
<div v-show></div>
<template v-show="ready"><div></div></template>
</template>
Good
Apply the visibility expression to a rendered element such as <div>.
<template>
<div v-show="ready"></div>
<div v-show="count > 0"></div>
</template>
Good avoids this rule's finding under the configuration above; other rules may still report diagnostics.
vue/valid-v-slot
Enforce valid v-slot directives
Default severity: error
Presets: essential, happy-path, nuxt, ecosystem, opinionated
Automatic fix: None; review the suggested change
Applies to: Vue SFC templates and blocks, with script context where the rule requires it
Options: See typed options and defaults.
Configuration (Vite+)
import { defineConfig } from "@vizejs/vite-plugin/vite-plus";
export default defineConfig({
lint: {
vize: {
"preset": "incremental",
"rules": {
"vue/valid-v-slot": "error"
}
},
},
});
vp run lint
Bad
The slot directive is on a native <div> or conflicts with other default/named slot declarations.
<template>
<div v-slot:header></div>
<MyComponent v-slot v-slot:header />
<template v-slot:header v-slot:footer />
</template>
Good
Declare a component's default slot on that component, or its named slot on a child <template #header>.
<template>
<MyComponent v-slot="{ item }">{{ item }}</MyComponent>
<MyComponent>
<template #header>Header</template>
</MyComponent>
</template>
Good avoids this rule's finding under the configuration above; other rules may still report diagnostics.
vue/valid-v-text
Enforce valid v-text directives
Default severity: error
Presets: essential, happy-path, nuxt, ecosystem, opinionated
Automatic fix: None; review the suggested change
Applies to: Vue SFC templates and blocks, with script context where the rule requires it
Options: No rule-specific options. Severity and preset selection are configurable.
Configuration (Vite+)
import { defineConfig } from "@vizejs/vite-plugin/vite-plus";
export default defineConfig({
lint: {
vize: {
"preset": "incremental",
"rules": {
"vue/valid-v-text": "error"
}
},
},
});
vp run lint
Bad
v-text lacks its text expression or uses an unsupported argument/modifier.
<template>
<div v-text></div>
<div v-text:arg="foo"></div>
<div v-text.mod="foo"></div>
</template>
Good
v-text="msg" is syntactically valid. The separate vue/no-v-text style rule can still prefer interpolation.
<template>
<div v-text="msg"></div>
</template>
Good avoids this rule's finding under the configuration above; other rules may still report diagnostics.
vue/attribute-hyphenation
Enforce attribute naming style on custom components
Default severity: warning
Presets: happy-path, nuxt, ecosystem, opinionated
Automatic fix: Available for supported findings
Applies to: Vue SFC templates and blocks, with script context where the rule requires it
Options: See typed options and defaults.
Configuration (Vite+)
import { defineConfig } from "@vizejs/vite-plugin/vite-plus";
export default defineConfig({
lint: {
vize: {
"preset": "incremental",
"rules": {
"vue/attribute-hyphenation": "warn"
}
},
},
});
vp run lint
Bad
The component attribute uses the camelCase spelling firstName.
<template>
<UserCard firstName="Ada" />
</template>
Good
The first-name spelling follows the configured hyphenated component-attribute convention.
<template>
<UserCard first-name="Ada" />
</template>
Good avoids this rule's finding under the configuration above; other rules may still report diagnostics.
vue/component-definition-name-casing
Enforce PascalCase or kebab-case for component definition names
Default severity: warning
Presets: happy-path, nuxt, ecosystem, opinionated
Automatic fix: None; review the suggested change
Applies to: Vue SFC templates and blocks, with script context where the rule requires it
Options: No rule-specific options. Severity and preset selection are configurable.
The component filename is checked. PascalCase and kebab-case are accepted; mixed casing is reported.
Configuration (Vite+)
import { defineConfig } from "@vizejs/vite-plugin/vite-plus";
export default defineConfig({
lint: {
vize: {
"preset": "incremental",
"rules": {
"vue/component-definition-name-casing": "warn"
}
},
},
});
vp run lint
Bad
The filename myComponent.vue mixes a lowercase initial with an internal uppercase letter instead of using PascalCase or kebab-case.
myComponent.vue
<template><p>Content</p></template>
Good
Renaming the file to MyComponent.vue uses PascalCase; its template content is unchanged.
MyComponent.vue
<template><p>Content</p></template>
Good avoids this rule's finding under the configuration above; other rules may still report diagnostics.
vue/html-quotes
Enforce quotes style of HTML attributes
Default severity: warning
Presets: happy-path, nuxt, ecosystem, opinionated
Automatic fix: Available for supported findings
Applies to: Vue SFC templates and blocks, with script context where the rule requires it
Options: No rule-specific options. Severity and preset selection are configurable.
Configuration (Vite+)
import { defineConfig } from "@vizejs/vite-plugin/vite-plus";
export default defineConfig({
lint: {
vize: {
"preset": "incremental",
"rules": {
"vue/html-quotes": "warn"
}
},
},
});
vp run lint
Bad
The attributes use single quotes or no quotes instead of the double-quote convention.
<template>
<div class='foo'></div>
<div class=foo></div>
<div v-if='ready'></div>
</template>
Good
Both ordinary attributes and directive expressions use double quotes.
<template>
<div class="foo"></div>
<div v-if="ready"></div>
</template>
Good avoids this rule's finding under the configuration above; other rules may still report diagnostics.
vue/html-self-closing
Enforce self-closing style
Default severity: warning
Presets: nuxt, opinionated
Automatic fix: Available for supported findings
Applies to: Vue SFC templates and blocks, with script context where the rule requires it
Options: See typed options and defaults.
Configuration (Vite+)
import { defineConfig } from "@vizejs/vite-plugin/vite-plus";
export default defineConfig({
lint: {
vize: {
"preset": "incremental",
"rules": {
"vue/html-self-closing": "warn"
}
},
},
});
vp run lint
Bad
The empty component uses a closing pair, while void img and br elements omit the configured self-closing spelling.
<template>
<MyComponent></MyComponent>
<img>
<br>
</template>
Good
The component and void elements use self-closing syntax; a div with content retains its closing tag.
<template>
<MyComponent />
<div></div>
<div />
<img />
<br />
<div>content</div>
</template>
Good avoids this rule's finding under the configuration above; other rules may still report diagnostics.
vue/mustache-interpolation-spacing
Enforce consistent spacing inside mustache interpolations
Default severity: warning
Presets: happy-path, nuxt, ecosystem, opinionated
Automatic fix: Available for supported findings
Applies to: Vue SFC templates and blocks, with script context where the rule requires it
Options: No rule-specific options. Severity and preset selection are configurable.
Configuration (Vite+)
import { defineConfig } from "@vizejs/vite-plugin/vite-plus";
export default defineConfig({
lint: {
vize: {
"preset": "incremental",
"rules": {
"vue/mustache-interpolation-spacing": "warn"
}
},
},
});
vp run lint
Bad
The text interpolation is missing a space at one or both delimiter boundaries.
<template>
<div>{{text}}</div>
<div>{{ text}}</div>
<div>{{text }}</div>
</template>
Good
Spaces separate the expression from both opening and closing mustache delimiters.
<template>
<div>{{ text }}</div>
<div>{{ foo.bar }}</div>
<div>{{ foo + bar }}</div>
</template>
Good avoids this rule's finding under the configuration above; other rules may still report diagnostics.
vue/no-multi-spaces
Disallow multiple consecutive spaces
Default severity: warning
Presets: happy-path, nuxt, ecosystem, opinionated
Automatic fix: Available for supported findings
Applies to: Vue SFC templates and blocks, with script context where the rule requires it
Options: No rule-specific options. Severity and preset selection are configurable.
Configuration (Vite+)
import { defineConfig } from "@vizejs/vite-plugin/vite-plus";
export default defineConfig({
lint: {
vize: {
"preset": "incremental",
"rules": {
"vue/no-multi-spaces": "warn"
}
},
},
});
vp run lint
Bad
Two spaces separate attributes or the element name and the first attribute.
<template>
<div class="panel"></div>
<div class="panel" id="main"></div>
</template>
Good
Single spaces separate the same attributes.
<template>
<div class="panel"></div>
<div class="panel" id="main"></div>
</template>
Good avoids this rule's finding under the configuration above; other rules may still report diagnostics.
vue/no-template-shadow
Disallow variable names that shadow variables in outer scope
Default severity: warning
Presets: nuxt, opinionated
Automatic fix: None; review the suggested change
Applies to: Vue SFC templates and blocks, with script context where the rule requires it
Options: No rule-specific options. Severity and preset selection are configurable.
The current check compares nested v-for bindings. It does not report a single v-for binding merely because it shares a script binding's name.
Configuration (Vite+)
import { defineConfig } from "@vizejs/vite-plugin/vite-plus";
export default defineConfig({
lint: {
vize: {
"preset": "incremental",
"rules": {
"vue/no-template-shadow": "warn"
}
},
},
});
vp run lint
Bad
The inner v-for declares item again and hides the outer item binding inside the nested loop.
<template>
<div v-for="item in items" :key="item.id"><span v-for="item in item.children" :key="item.id">{{ item.name }}</span></div>
</template>
Good
The inner loop declares child, leaving item available for the outer row and child for the nested row.
<template>
<div v-for="item in items" :key="item.id"><span v-for="child in item.children" :key="child.id">{{ child.name }}</span></div>
</template>
Good avoids this rule's finding under the configuration above; other rules may still report diagnostics.
vue/no-unused-properties
Disallow unused properties defined in defineProps
Default severity: warning
Presets: happy-path, nuxt, ecosystem, opinionated
Automatic fix: None; review the suggested change
Applies to: Vue SFC templates and blocks, with script context where the rule requires it
Options: No rule-specific options. Severity and preset selection are configurable.
Configuration (Vite+)
import { defineConfig } from "@vizejs/vite-plugin/vite-plus";
export default defineConfig({
lint: {
vize: {
"preset": "incremental",
"rules": {
"vue/no-unused-properties": "warn"
}
},
},
});
vp run lint
Bad
The component declares description as a prop but renders only title.
<script setup lang="ts">
defineProps<{ title: string; description: string }>();
</script>
<template>
<h1>{{ title }}</h1>
</template>
Good
Both declared props are referenced by the template.
<script setup lang="ts">
defineProps<{ title: string; description: string }>();
</script>
<template>
<h1>{{ title }}</h1>
<p>{{ description }}</p>
</template>
Good avoids this rule's finding under the configuration above; other rules may still report diagnostics.
vue/prop-name-casing
Enforce a casing for declared prop names
Default severity: warning
Presets: happy-path, nuxt, ecosystem, opinionated
Automatic fix: None; review the suggested change
Applies to: Vue SFC templates and blocks, with script context where the rule requires it
Options: No rule-specific options. Severity and preset selection are configurable.
Checks declared prop names, not the casing of attributes passed to a child.
Configuration (Vite+)
import { defineConfig } from "@vizejs/vite-plugin/vite-plus";
export default defineConfig({
lint: {
vize: {
"preset": "incremental",
"rules": {
"vue/prop-name-casing": "warn"
}
},
},
});
vp run lint
Bad
The declared prop name user_name uses underscore-separated spelling.
<script setup lang="ts">
defineProps<{ user_name: string }>();
</script>
<template><p>{{ user_name }}</p></template>
Good
The declaration and its template reference use the camelCase name userName.
<script setup lang="ts">
defineProps<{ userName: string }>();
</script>
<template><p>{{ userName }}</p></template>
Good avoids this rule's finding under the configuration above; other rules may still report diagnostics.
vue/v-bind-style
Enforce v-bind directive style
Default severity: warning
Presets: nuxt, opinionated
Automatic fix: Available for supported findings
Applies to: Vue SFC templates and blocks, with script context where the rule requires it
Options: No rule-specific options. Severity and preset selection are configurable.
Configuration (Vite+)
import { defineConfig } from "@vizejs/vite-plugin/vite-plus";
export default defineConfig({
lint: {
vize: {
"preset": "incremental",
"rules": {
"vue/v-bind-style": "warn"
}
},
},
});
vp run lint
Bad
v-bind:class uses the long form where the configured binding style requires the colon shorthand.
<template>
<div v-bind:class="panelClass"></div>
</template>
Good
:class retains the same expression with the required shorthand; this rule concerns spelling rather than the value's type.
<template>
<div :class="panelClass"></div>
</template>
Good avoids this rule's finding under the configuration above; other rules may still report diagnostics.
vue/v-on-style
Enforce v-on directive style
Default severity: warning
Presets: happy-path, nuxt, ecosystem, opinionated
Automatic fix: Available for supported findings
Applies to: Vue SFC templates and blocks, with script context where the rule requires it
Options: No rule-specific options. Severity and preset selection are configurable.
Configuration (Vite+)
import { defineConfig } from "@vizejs/vite-plugin/vite-plus";
export default defineConfig({
lint: {
vize: {
"preset": "incremental",
"rules": {
"vue/v-on-style": "warn"
}
},
},
});
vp run lint
Bad
v-on:click uses the long event-listener form where the rule requires shorthand.
<template>
<div v-on:click="handleClick"></div>
</template>
Good
@click keeps the same handler while using the configured shorthand.
<template>
<div @click="handleClick"></div>
</template>
Good avoids this rule's finding under the configuration above; other rules may still report diagnostics.
vue/v-slot-style
Enforce v-slot directive style
Default severity: warning
Presets: happy-path, nuxt, ecosystem, opinionated
Automatic fix: Available for supported findings
Applies to: Vue SFC templates and blocks, with script context where the rule requires it
Options: No rule-specific options. Severity and preset selection are configurable.
Configuration (Vite+)
import { defineConfig } from "@vizejs/vite-plugin/vite-plus";
export default defineConfig({
lint: {
vize: {
"preset": "incremental",
"rules": {
"vue/v-slot-style": "warn"
}
},
},
});
vp run lint
Bad
The component uses #default and the template uses v-slot:header, opposite to the rule's context-specific styles.
<template>
<MyComponent #default="props">{{ props.item }}</MyComponent>
<MyComponent>
<template v-slot:header>Header</template>
</MyComponent>
</template>
Good
Use v-slot for the component's default slot and #header for the template's named slot.
<template>
<MyComponent v-slot="props">{{ props.item }}</MyComponent>
<MyComponent>
<template #header>Header</template>
</MyComponent>
</template>
Good avoids this rule's finding under the configuration above; other rules may still report diagnostics.
ssr/no-browser-globals-in-ssr
Disallow browser-only globals in SSR context
Default severity: warning
Presets: happy-path, nuxt, ecosystem, opinionated
Automatic fix: None; review the suggested change
Applies to: Vue SFC templates and blocks, with script context where the rule requires it
Options: No rule-specific options. Severity and preset selection are configurable.
Configuration (Vite+)
import { defineConfig } from "@vizejs/vite-plugin/vite-plus";
export default defineConfig({
lint: {
vize: {
"preset": "incremental",
"rules": {
"ssr/no-browser-globals-in-ssr": "warn"
}
},
},
});
vp run lint
Bad
Setup reads window.innerWidth immediately, although window does not exist when the component runs on the server.
<script setup lang="ts">
const width = window.innerWidth;
</script>
Good
The initial width is a server-safe ref value, and the browser access moves into onMounted, which runs on the client rather than during SSR setup.
<script setup lang="ts">
const width = ref(0);
onMounted(() => {
width.value = window.innerWidth;
});
</script>
Good avoids this rule's finding under the configuration above; other rules may still report diagnostics.
ssr/no-hydration-mismatch
Disallow non-deterministic values that cause hydration mismatch
Default severity: warning
Presets: happy-path, nuxt, ecosystem, opinionated
Automatic fix: None; review the suggested change
Applies to: Vue SFC templates and blocks, with script context where the rule requires it
Options: No rule-specific options. Severity and preset selection are configurable.
Configuration (Vite+)
import { defineConfig } from "@vizejs/vite-plugin/vite-plus";
export default defineConfig({
lint: {
vize: {
"preset": "incremental",
"rules": {
"ssr/no-hydration-mismatch": "warn"
}
},
},
});
vp run lint
Bad
The template evaluates Math.random() during rendering, so the server and client can produce different text for the same paragraph.
<template>
<p>{{ Math.random() }}</p>
</template>
Good
The paragraph renders the stable seed state instead of a fresh random result. In this Nuxt-style example, useState supplies the shared state and the initializer is the constant "stable".
<script setup lang="ts">
const seed = useState("seed", () => "stable");
</script>
<template>
<p>{{ seed }}</p>
</template>
Good avoids this rule's finding under the configuration above; other rules may still report diagnostics.
vue/a11y-img-alt
Require alt attribute on images for accessibility
Default severity: warning
Presets: none
Automatic fix: None; review the suggested change
Applies to: Vue SFC templates and blocks, with script context where the rule requires it
Options: No rule-specific options. Severity and preset selection are configurable.
Configuration (Vite+)
import { defineConfig } from "@vizejs/vite-plugin/vite-plus";
export default defineConfig({
lint: {
vize: {
"preset": "incremental",
"rules": {
"vue/a11y-img-alt": "warn"
}
},
},
});
vp run lint
Bad
Neither the static image nor the dynamically sourced image supplies an alt attribute.
<template>
<img src="/photo.jpg" />
<img :src="photo" />
</template>
Good
Informative images get descriptive alt text, decoration gets an empty alt, and the dynamic image binds its description.
<template>
<!-- Informative image -->
<img src="/photo.jpg" alt="Team photo from company retreat" />
<!-- Decorative image (empty alt) -->
<img src="/decoration.svg" alt="" />
<!-- Dynamic alt -->
<img :src="photo" :alt="photoDescription" />
</template>
Good avoids this rule's finding under the configuration above; other rules may still report diagnostics.
vue/attribute-order
Enforce a consistent order of attributes
Default severity: warning
Presets: happy-path, nuxt, ecosystem, opinionated
Automatic fix: None; review the suggested change
Applies to: Vue SFC templates and blocks, with script context where the rule requires it
Options: No rule-specific options. Severity and preset selection are configurable.
Configuration (Vite+)
import { defineConfig } from "@vizejs/vite-plugin/vite-plus";
export default defineConfig({
lint: {
vize: {
"preset": "incremental",
"rules": {
"vue/attribute-order": "warn"
}
},
},
});
vp run lint
Bad
The event handler appears before the structural v-if directive and ordinary id attribute.
<template>
<div @click="onClick" v-if="show" id="main"></div>
</template>
Good
v-if comes first, followed by id and then the event handler, following the rule ordering.
<template>
<div v-if="show" id="main" @click="onClick"></div>
</template>
Good avoids this rule's finding under the configuration above; other rules may still report diagnostics.
vue/component-name-in-template-casing
Enforce specific casing for component names in templates
Default severity: warning
Presets: nuxt, opinionated
Automatic fix: Available for supported findings
Applies to: Vue SFC templates and blocks, with script context where the rule requires it
Options: See typed options and defaults.
Configuration (Vite+)
import { defineConfig } from "@vizejs/vite-plugin/vite-plus";
export default defineConfig({
lint: {
vize: {
"preset": "incremental",
"rules": {
"vue/component-name-in-template-casing": "warn"
}
},
},
});
vp run lint
Bad
The component is written in kebab-case and camelCase under the PascalCase convention.
<script setup>
import MyComponent from "./MyComponent.vue";
</script>
<template>
<my-component />
<myComponent />
</template>
Good
MyComponent uses PascalCase; native slot syntax remains lowercase.
<script setup>
import MyComponent from "./MyComponent.vue";
</script>
<template>
<MyComponent />
<RouterView />
<slot />
</template>
Good avoids this rule's finding under the configuration above; other rules may still report diagnostics.
vue/html-button-has-type
Require an explicit valid type on button elements
Default severity: warning
Presets: nuxt, opinionated
Automatic fix: None; review the suggested change
Applies to: Vue SFC templates and blocks, with script context where the rule requires it
Options: No rule-specific options. Severity and preset selection are configurable.
Configuration (Vite+)
import { defineConfig } from "@vizejs/vite-plugin/vite-plus";
export default defineConfig({
lint: {
vize: {
"preset": "incremental",
"rules": {
"vue/html-button-has-type": "warn"
}
},
},
});
vp run lint
Bad
One button omits type and another supplies the unsupported foo type.
<template>
<button>Click</button>
<button type="foo">Click</button>
</template>
Good
Buttons specify button, submit, or reset; a bound type is treated as dynamic.
<template>
<button type="button">Click</button>
<button type="submit">Save</button>
<button type="reset">Reset</button>
<button :type="dynamicType">Click</button>
</template>
Good avoids this rule's finding under the configuration above; other rules may still report diagnostics.
vue/max-template-complexity
Limit a component's own template complexity (cyclomatic and cognitive)
Default severity: warning
Presets: none
Automatic fix: None; review the suggested change
Applies to: Vue SFC templates and blocks, with script context where the rule requires it
Options: No rule-specific options. Severity and preset selection are configurable.
Bad has cyclomatic complexity 13 and cognitive complexity 25 (limits: 11 and 16). Each component is measured separately; only inline HTML templates are supported.
See complexity scoring and component boundaries for the contributions behind the example's two scores.
Configuration (Vite+)
import { defineConfig } from "@vizejs/vite-plugin/vite-plus";
export default defineConfig({
lint: {
vize: {
"preset": "incremental",
"rules": {
"vue/max-template-complexity": "warn"
}
},
},
});
vp run lint
Bad
The parent-authored branches, loop, slot content, and expression decisions produce scores of 13 and 25, above the default limits 11 and 16.
<script setup lang="ts">
defineProps<{ rows: Row[] }>();
</script>
<template>
<section>
<h1>{{ user ? user.name : 'Guest' }}</h1>
<DataTable :rows="rows">
<template #cell="{ row, column }">
<span v-if="column.key === 'status'" :class="row.active ? 'on' : 'off'">{{ row.status ?? 'unknown' }}</span>
<a v-else-if="column.key === 'link' && row.url" :href="row.url">{{ row.label }}</a>
<template v-else>
<em v-for="tag in row.tags" :key="tag">
<b v-if="tag.pinned || tag.starred">{{ tag.hot ? '!' : '' }}</b>
</em>
</template>
</template>
</DataTable>
<p v-if="!rows.length && !loading">No data</p>
</section>
</template>
Good
The parent template delegates rendering to RowList and keeps one v-if; its own scores are 2 and 1.
<template>
<RowList v-if="ready" :rows="rows" />
</template>
Good avoids this rule's finding under the configuration above; other rules may still report diagnostics.
vue/no-array-index-key
Disallow using the v-for index variable directly as the :key
Default severity: warning
Presets: nuxt, opinionated
Automatic fix: None; review the suggested change
Applies to: Vue SFC templates and blocks, with script context where the rule requires it
Options: No rule-specific options. Severity and preset selection are configurable.
Configuration (Vite+)
import { defineConfig } from "@vizejs/vite-plugin/vite-plus";
export default defineConfig({
lint: {
vize: {
"preset": "incremental",
"rules": {
"vue/no-array-index-key": "warn"
}
},
},
});
vp run lint
Bad
The list key is its current index, so item identity changes when the list is reordered.
<template>
<li v-for="(item, index) in items" :key="index">{{ item.name }}</li>
</template>
Good
The key comes from item.id, preserving the identity of each item across position changes.
<template>
<li v-for="item in items" :key="item.id">{{ item.name }}</li>
</template>
Good avoids this rule's finding under the configuration above; other rules may still report diagnostics.
vue/no-bare-strings-in-template
Disallow raw human-readable text in the template that should be internationalized
Default severity: warning
Presets: none
Automatic fix: None; review the suggested change
Applies to: Vue SFC templates and blocks, with script context where the rule requires it
Options: No rule-specific options. Severity and preset selection are configurable.
Configuration (Vite+)
import { defineConfig } from "@vizejs/vite-plugin/vite-plus";
export default defineConfig({
lint: {
vize: {
"preset": "incremental",
"rules": {
"vue/no-bare-strings-in-template": "warn"
}
},
},
});
vp run lint
Bad
Visible text and naming attributes embed untranslated strings directly in the template.
<template>
<div>hello</div>
<img alt="a cat" />
<input placeholder="Search" />
<button title="Close">x</button>
</template>
Good
Translatable content calls $t; the punctuation and numeric-only examples are allowed exceptions.
<template>
<div>{{ $t('hello') }}</div>
<img :alt="$t('cat')" />
<div>-</div>
<div>123</div>
<button :title="$t('close')">{{ $t('x') }}</button>
</template>
Good avoids this rule's finding under the configuration above; other rules may still report diagnostics.
vue/no-boolean-attr-value
Disallow explicit values for boolean HTML attributes
Default severity: warning
Presets: nuxt, opinionated
Automatic fix: Available for supported findings
Applies to: Vue SFC templates and blocks, with script context where the rule requires it
Options: No rule-specific options. Severity and preset selection are configurable.
Configuration (Vite+)
import { defineConfig } from "@vizejs/vite-plugin/vite-plus";
export default defineConfig({
lint: {
vize: {
"preset": "incremental",
"rules": {
"vue/no-boolean-attr-value": "warn"
}
},
},
});
vp run lint
Bad
The boolean disabled and checked attributes redundantly contain string values.
<template>
<input disabled="disabled" />
<input checked="checked" />
<button disabled="true">Save</button>
</template>
Good
The presence of each boolean attribute expresses the same enabled state without a value.
<template>
<input disabled />
<input checked />
<button disabled>Save</button>
</template>
Good avoids this rule's finding under the configuration above; other rules may still report diagnostics.
vue/no-empty-component-block
Disallow empty SFC blocks
Default severity: warning
Presets: nuxt, opinionated
Automatic fix: None; review the suggested change
Applies to: Vue SFC templates and blocks, with script context where the rule requires it
Options: No rule-specific options. Severity and preset selection are configurable.
Configuration (Vite+)
import { defineConfig } from "@vizejs/vite-plugin/vite-plus";
export default defineConfig({
lint: {
vize: {
"preset": "incremental",
"rules": {
"vue/no-empty-component-block": "warn"
}
},
},
});
vp run lint
Bad
The template, script, and style blocks contain no meaningful content.
<template></template>
<script></script>
<style>
</style>
Good
Each retained block contains actual markup, script declarations, or style declarations.
<template>
<div>Hello</div>
</template>
<script setup>
const message = "Hello";
</script>
<style scoped>
.button { color: red; }
</style>
Good avoids this rule's finding under the configuration above; other rules may still report diagnostics.
vue/no-inline-style
Discourage use of inline style attributes
Default severity: warning
Presets: nuxt, opinionated
Automatic fix: None; review the suggested change
Applies to: Vue SFC templates and blocks, with script context where the rule requires it
Options: No rule-specific options. Severity and preset selection are configurable.
Configuration (Vite+)
import { defineConfig } from "@vizejs/vite-plugin/vite-plus";
export default defineConfig({
lint: {
vize: {
"preset": "incremental",
"rules": {
"vue/no-inline-style": "warn"
}
},
},
});
vp run lint
Bad
The static style attribute embeds the color declaration in the element.
<template>
<div style="color: red">Text</div>
</template>
Good
Classes express the fixed color; the ratio-dependent width remains a dynamic style binding, outside the static-attribute check.
<template>
<div class="text-red">Text</div>
<span :class="{ 'text-red': isRed }">Text</span>
<div :style="{ width: `${ratio}%` }">Text</div>
</template>
Good avoids this rule's finding under the configuration above; other rules may still report diagnostics.
vue/no-invalid-html-attribute
Disallow invalid static values for HTML attributes
Default severity: warning
Presets: happy-path, nuxt, ecosystem, opinionated
Automatic fix: None; review the suggested change
Applies to: Vue SFC templates and blocks, with script context where the rule requires it
Options: No rule-specific options. Severity and preset selection are configurable.
Configuration (Vite+)
import { defineConfig } from "@vizejs/vite-plugin/vite-plus";
export default defineConfig({
lint: {
vize: {
"preset": "incremental",
"rules": {
"vue/no-invalid-html-attribute": "warn"
}
},
},
});
vp run lint
Bad
The anchor uses stylesheet as a rel value, although that value belongs to stylesheet link elements.
<template>
<a href="/guide" rel="stylesheet">Guide</a>
</template>
Good
The anchor uses help, a rel value appropriate for a linked help resource.
<template>
<a href="/guide" rel="help">Guide</a>
</template>
Good avoids this rule's finding under the configuration above; other rules may still report diagnostics.
vue/no-lone-template
Disallow unnecessary <template> elements
Default severity: warning
Presets: happy-path, nuxt, ecosystem, opinionated
Automatic fix: None; review the suggested change
Applies to: Vue SFC templates and blocks, with script context where the rule requires it
Options: No rule-specific options. Severity and preset selection are configurable.
Configuration (Vite+)
import { defineConfig } from "@vizejs/vite-plugin/vite-plus";
export default defineConfig({
lint: {
vize: {
"preset": "incremental",
"rules": {
"vue/no-lone-template": "warn"
}
},
},
});
vp run lint
Bad
The inner template has no directive or slot role that gives it a structural purpose.
<template>
<div><template><p>Details</p></template></div>
</template>
Good
Removing the unnecessary wrapper leaves the paragraph directly inside div.
<template>
<div><p>Details</p></div>
</template>
Good avoids this rule's finding under the configuration above; other rules may still report diagnostics.
vue/no-multiple-objects-in-class
Disallow multiple object literals inside a :class array binding
Default severity: warning
Presets: nuxt, opinionated
Automatic fix: None; review the suggested change
Applies to: Vue SFC templates and blocks, with script context where the rule requires it
Options: No rule-specific options. Severity and preset selection are configurable.
Configuration (Vite+)
import { defineConfig } from "@vizejs/vite-plugin/vite-plus";
export default defineConfig({
lint: {
vize: {
"preset": "incremental",
"rules": {
"vue/no-multiple-objects-in-class": "warn"
}
},
},
});
vp run lint
Bad
A class array contains two top-level object literals that can be merged.
<template>
<div :class="[{ a }, { b }]"></div>
<div :class="[{ active: isActive }, { error: hasError }]"></div>
</template>
Good
One object contains the class conditions; arrays with one object and a string or with non-literal entries remain allowed.
<template>
<div :class="{ a, b }"></div>
<div :class="[{ active: isActive }, 'static']"></div>
<div :class="[foo, bar]"></div>
</template>
Good avoids this rule's finding under the configuration above; other rules may still report diagnostics.
vue/no-negated-v-if-condition
Disallow a negated v-if condition when the chain has a v-else
Default severity: warning
Presets: nuxt, opinionated
Automatic fix: None; review the suggested change
Applies to: Vue SFC templates and blocks, with script context where the rule requires it
Options: No rule-specific options. Severity and preset selection are configurable.
Configuration (Vite+)
import { defineConfig } from "@vizejs/vite-plugin/vite-plus";
export default defineConfig({
lint: {
vize: {
"preset": "incremental",
"rules": {
"vue/no-negated-v-if-condition": "warn"
}
},
},
});
vp run lint
Bad
The paired v-if and v-else branches begin with a negated condition.
<template>
<div v-if="!ok">A</div>
<div v-else>B</div>
</template>
Good
A positive ok condition comes first; when inverting a condition, place the original opposite branch first. A lone negated v-if and !== comparisons remain allowed.
<template>
<div v-if="ok">B</div>
<div v-else>A</div>
<div v-if="!ok">A</div>
<div v-if="a !== b">A</div>
<div v-else>B</div>
</template>
Good avoids this rule's finding under the configuration above; other rules may still report diagnostics.
vue/no-non-component-keep-alive-child
Disallow plain element wrappers directly below <KeepAlive>
Default severity: warning
Presets: none
Automatic fix: None; review the suggested change
Applies to: Vue SFC templates and blocks, with script context where the rule requires it
Options: No rule-specific options. Severity and preset selection are configurable.
Configuration (Vite+)
import { defineConfig } from "@vizejs/vite-plugin/vite-plus";
export default defineConfig({
lint: {
vize: {
"preset": "incremental",
"rules": {
"vue/no-non-component-keep-alive-child": "warn"
}
},
},
});
vp run lint
Bad
KeepAlive conditionally wraps a native div rather than directly caching UserCard.
<template>
<KeepAlive>
<div v-if="ready">
<UserCard />
</div>
</KeepAlive>
</template>
Good
The first example makes UserCard the conditional child. The v-show wrapper illustrates a shape outside this conditional-child check, not a promise that the native wrapper is cached.
<template>
<KeepAlive>
<UserCard v-if="ready" />
</KeepAlive>
<KeepAlive>
<div v-show="opened">
<UserCard />
</div>
</KeepAlive>
</template>
Good avoids this rule's finding under the configuration above; other rules may still report diagnostics.
vue/no-preprocessor-lang
Discourage CSS preprocessor usage in favor of modern CSS
Default severity: warning
Presets: nuxt, opinionated
Automatic fix: Not implemented for SFC lint
Applies to: Vue SFC templates and blocks, with script context where the rule requires it
Options: No rule-specific options. Severity and preset selection are configurable.
Current support: no-sfc-finding
This catalog entry does not currently emit its rule-specific finding through SFC lint. The Bad/Good pair describes the intended convention, not an executable finding. Enabling the ID does not supply the missing SFC check.
Configured ID (currently no SFC finding)
import { defineConfig } from "@vizejs/vite-plugin/vite-plus";
export default defineConfig({
lint: {
vize: {
"preset": "incremental",
"rules": {
"vue/no-preprocessor-lang": "warn"
}
},
},
});
vp run lint
Bad
The style block selects SCSS with lang. This describes the intended no-preprocessor convention; the current SFC path does not emit this rule.
<template><p>Notice</p></template>
<style lang="scss">
.notice { color: red; }
</style>
Good
The same CSS declarations omit the preprocessor lang. This is the convention repair, not an executable Bad/Good diagnostic difference today.
<template><p>Notice</p></template>
<style>
.notice { color: red; }
</style>
Good illustrates the intended convention; the current SFC path emits neither side's rule-specific finding.
vue/no-root-v-if
Disallow v-if on the single root element of a template
Default severity: warning
Presets: nuxt, opinionated
Automatic fix: None; review the suggested change
Applies to: Vue SFC templates and blocks, with script context where the rule requires it
Options: No rule-specific options. Severity and preset selection are configurable.
Configuration (Vite+)
import { defineConfig } from "@vizejs/vite-plugin/vite-plus";
export default defineConfig({
lint: {
vize: {
"preset": "incremental",
"rules": {
"vue/no-root-v-if": "warn"
}
},
},
});
vp run lint
Bad
The component root itself appears and disappears under v-if.
<template>
<div v-if="show">content</div>
</template>
Good
A stable outer div remains the root while the nested paragraph carries the visibility condition.
<template>
<div>
<p v-if="show">content</p>
</div>
</template>
Good avoids this rule's finding under the configuration above; other rules may still report diagnostics.
vue/no-script-non-standard-lang
Discourage non-standard script lang values
Default severity: warning
Presets: nuxt, opinionated
Automatic fix: Not implemented for SFC lint
Applies to: Vue SFC templates and blocks, with script context where the rule requires it
Options: No rule-specific options. Severity and preset selection are configurable.
Current support: no-sfc-finding
This catalog entry does not currently emit its rule-specific finding through SFC lint. The Bad/Good pair describes the intended convention, not an executable finding. Enabling the ID does not supply the missing SFC check.
Configured ID (currently no SFC finding)
import { defineConfig } from "@vizejs/vite-plugin/vite-plus";
export default defineConfig({
lint: {
vize: {
"preset": "incremental",
"rules": {
"vue/no-script-non-standard-lang": "warn"
}
},
},
});
vp run lint
Bad
The script uses CoffeeScript syntax under lang=coffee. The current SFC path does not emit this catalog rule for that language.
<script lang="coffee">
count = 0
</script>
<template><p>Notice</p></template>
Good
The script uses an ordinary TypeScript declaration with lang=ts, illustrating the intended language convention.
<script lang="ts">
const count = 0;
</script>
<template><p>Notice</p></template>
Good illustrates the intended convention; the current SFC path emits neither side's rule-specific finding.
vue/no-src-attribute
Discourage src attribute on SFC blocks
Default severity: warning
Presets: nuxt, opinionated
Automatic fix: None; review the suggested change
Applies to: Vue SFC templates and blocks, with script context where the rule requires it
Options: No rule-specific options. Severity and preset selection are configurable.
Configuration (Vite+)
import { defineConfig } from "@vizejs/vite-plugin/vite-plus";
export default defineConfig({
lint: {
vize: {
"preset": "incremental",
"rules": {
"vue/no-src-attribute": "warn"
}
},
},
});
vp run lint
Bad
The SFC blocks delegate their template, script, and style content to src files.
<template src="./template.html"></template>
<script src="./script.ts"></script>
<style src="./style.css"></style>
Good
Each SFC block contains its own content without an external src attribute.
<template>
<p>Hello</p>
</template>
<script setup lang="ts">
const label = "Hello";
</script>
<style scoped>
p {
color: red;
}
</style>
Good avoids this rule's finding under the configuration above; other rules may still report diagnostics.
vue/no-static-inline-styles
Disallow static inline style attributes
Default severity: warning
Presets: none
Automatic fix: None; review the suggested change
Applies to: Vue SFC templates and blocks, with script context where the rule requires it
Options: No rule-specific options. Severity and preset selection are configurable.
Configuration (Vite+)
import { defineConfig } from "@vizejs/vite-plugin/vite-plus";
export default defineConfig({
lint: {
vize: {
"preset": "incremental",
"rules": {
"vue/no-static-inline-styles": "warn"
}
},
},
});
vp run lint
Bad
The paragraph carries the constant color declaration in its style attribute.
<template>
<p style="color: red">Notice</p>
</template>
Good
A notice class and scoped stylesheet hold the constant color outside the template attribute.
<template><p class="notice">Notice</p></template>
<style scoped>.notice { color: red; }</style>
Good avoids this rule's finding under the configuration above; other rules may still report diagnostics.
vue/no-template-lang
Discourage lang attribute on template block
Default severity: warning
Presets: nuxt, opinionated
Automatic fix: Not implemented for SFC lint
Applies to: Vue SFC templates and blocks, with script context where the rule requires it
Options: No rule-specific options. Severity and preset selection are configurable.
Current support: no-sfc-finding
This catalog entry does not currently emit its rule-specific finding through SFC lint. The Bad/Good pair describes the intended convention, not an executable finding. Enabling the ID does not supply the missing SFC check.
Configured ID (currently no SFC finding)
import { defineConfig } from "@vizejs/vite-plugin/vite-plus";
export default defineConfig({
lint: {
vize: {
"preset": "incremental",
"rules": {
"vue/no-template-lang": "warn"
}
},
},
});
vp run lint
Bad
The template selects Pug through lang. This is an intended HTML-only convention; the current SFC path does not diagnose this catalog ID.
<template lang="pug">
p Notice
</template>
Good
An ordinary HTML template omits lang and uses the paragraph directly. This illustrates the convention without claiming a current SFC finding.
<template>
<p>Notice</p>
</template>
Good illustrates the intended convention; the current SFC path emits neither side's rule-specific finding.
vue/no-template-target-blank
Disallow target="_blank" without rel="noopener noreferrer"
Default severity: warning
Presets: happy-path, nuxt, ecosystem, opinionated
Automatic fix: None; review the suggested change
Applies to: Vue SFC templates and blocks, with script context where the rule requires it
Options: No rule-specific options. Severity and preset selection are configurable.
Configuration (Vite+)
import { defineConfig } from "@vizejs/vite-plugin/vite-plus";
export default defineConfig({
lint: {
vize: {
"preset": "incremental",
"rules": {
"vue/no-template-target-blank": "warn"
}
},
},
});
vp run lint
Bad
The external link opens a new browsing context without the expected rel protection.
<template>
<a href="https://example.com" target="_blank">x</a>
</template>
Good
The same link includes noopener noreferrer alongside target=_blank.
<template>
<a href="https://example.com" target="_blank" rel="noopener noreferrer">x</a>
</template>
Good avoids this rule's finding under the configuration above; other rules may still report diagnostics.
vue/no-undefined-refs
Disallow undefined variable references in templates
Default severity: warning
Presets: none
Automatic fix: None; review the suggested change
Applies to: Vue SFC templates and blocks, with script context where the rule requires it
Options: No rule-specific options. Severity and preset selection are configurable.
Configuration (Vite+)
import { defineConfig } from "@vizejs/vite-plugin/vite-plus";
export default defineConfig({
lint: {
vize: {
"preset": "incremental",
"rules": {
"vue/no-undefined-refs": "warn"
}
},
},
});
vp run lint
Bad
The template reads missing, although the script declares only message.
<script setup>const message = "Hello";</script>
<template>{{ missing }}</template>
Good
The interpolation reads the existing message binding.
<script setup>const message = "Hello";</script>
<template>{{ message }}</template>
Good avoids this rule's finding under the configuration above; other rules may still report diagnostics.
vue/no-unsafe-url
Warn about potentially unsafe URL bindings
Default severity: warning
Presets: essential, happy-path, nuxt, ecosystem, opinionated
Automatic fix: None; review the suggested change
Applies to: Vue SFC templates and blocks, with script context where the rule requires it
Options: No rule-specific options. Severity and preset selection are configurable.
Configuration (Vite+)
import { defineConfig } from "@vizejs/vite-plugin/vite-plus";
export default defineConfig({
lint: {
vize: {
"preset": "incremental",
"rules": {
"vue/no-unsafe-url": "warn"
}
},
},
});
vp run lint
Bad
The anchor destination begins with the executable javascript: scheme.
<template>
<a href="javascript:alert(1)">Continue</a>
</template>
Good
The anchor uses the ordinary local /next navigation destination.
<template>
<a href="/next">Continue</a>
</template>
Good avoids this rule's finding under the configuration above; other rules may still report diagnostics.
vue/no-unsandboxed-iframe
Require a sandbox attribute on iframe elements
Default severity: warning
Presets: happy-path, nuxt, ecosystem, opinionated
Automatic fix: None; review the suggested change
Applies to: Vue SFC templates and blocks, with script context where the rule requires it
Options: No rule-specific options. Severity and preset selection are configurable.
Configuration (Vite+)
import { defineConfig } from "@vizejs/vite-plugin/vite-plus";
export default defineConfig({
lint: {
vize: {
"preset": "incremental",
"rules": {
"vue/no-unsandboxed-iframe": "warn"
}
},
},
});
vp run lint
Bad
The embedded frame has no sandbox attribute limiting its capabilities.
<template>
<iframe src="/embed"></iframe>
</template>
Good
sandbox applies restrictions; allow-scripts explicitly opts into that one capability when needed.
<template>
<iframe src="/embed" sandbox></iframe>
<iframe src="/embed" sandbox="allow-scripts"></iframe>
</template>
Good avoids this rule's finding under the configuration above; other rules may still report diagnostics.
vue/no-unused-refs
Report template refs (ref="x") never referenced in <script>
Default severity: warning
Presets: nuxt, opinionated
Automatic fix: None; review the suggested change
Applies to: Vue SFC templates and blocks, with script context where the rule requires it
Options: No rule-specific options. Severity and preset selection are configurable.
Configuration (Vite+)
import { defineConfig } from "@vizejs/vite-plugin/vite-plus";
export default defineConfig({
lint: {
vize: {
"preset": "incremental",
"rules": {
"vue/no-unused-refs": "warn"
}
},
},
});
vp run lint
Bad
The template declares the unused ref name with no corresponding script reference binding.
<template><input ref="unused" /></template>
<script setup>
const x = 1
</script>
Good
The inputEl template ref has a same-named ref binding in script setup.
<template><input ref="inputEl" /></template>
<script setup>
import { ref } from 'vue'
const inputEl = ref(null)
</script>
Good avoids this rule's finding under the configuration above; other rules may still report diagnostics.
vue/no-unused-setup-bindings
Disallow unread script setup bindings
Default severity: warning
Presets: none
Automatic fix: None; review the suggested change
Applies to: Vue SFC templates and blocks, with script context where the rule requires it
Options: No rule-specific options. Severity and preset selection are configurable.
Configuration (Vite+)
import { defineConfig } from "@vizejs/vite-plugin/vite-plus";
export default defineConfig({
lint: {
vize: {
"preset": "incremental",
"rules": {
"vue/no-unused-setup-bindings": "warn"
}
},
},
});
vp run lint
Bad
The script setup message binding is never read by the template.
<script setup>const message = "Hello";</script>
<template><p>Welcome</p></template>
Good
The paragraph interpolates message, using the declared binding.
<script setup>const message = "Hello";</script>
<template><p>{{ message }}</p></template>
Good avoids this rule's finding under the configuration above; other rules may still report diagnostics.
vue/no-use-v-else-with-v-for
Disallow using v-else-if or v-else on the same element as v-for
Default severity: warning
Presets: none
Automatic fix: None; review the suggested change
Applies to: Vue SFC templates and blocks, with script context where the rule requires it
Options: No rule-specific options. Severity and preset selection are configurable.
Configuration (Vite+)
import { defineConfig } from "@vizejs/vite-plugin/vite-plus";
export default defineConfig({
lint: {
vize: {
"preset": "incremental",
"rules": {
"vue/no-use-v-else-with-v-for": "warn"
}
},
},
});
vp run lint
Bad
The else branch and v-for iteration are attached to the same paragraph.
<template>
<p v-if="ready">Ready</p>
<p v-else v-for="item in items" :key="item.id">{{ item.name }}</p>
</template>
Good
A separate template owns v-else, and its child paragraph owns v-for.
<template>
<p v-if="ready">Ready</p>
<template v-else><p v-for="item in items" :key="item.id">{{ item.name }}</p></template>
</template>
Good avoids this rule's finding under the configuration above; other rules may still report diagnostics.
vue/no-useless-mustaches
Disallow a mustache interpolation whose expression is a constant string literal
Default severity: warning
Presets: nuxt, opinionated
Automatic fix: None; review the suggested change
Applies to: Vue SFC templates and blocks, with script context where the rule requires it
Options: No rule-specific options. Severity and preset selection are configurable.
Configuration (Vite+)
import { defineConfig } from "@vizejs/vite-plugin/vite-plus";
export default defineConfig({
lint: {
vize: {
"preset": "incremental",
"rules": {
"vue/no-useless-mustaches": "warn"
}
},
},
});
vp run lint
Bad
The interpolation contains only a constant string and does not need expression evaluation.
<template>
<div>{{ 'x' }}</div>
<div>{{ "x" }}</div>
<div>{{ `x` }}</div>
</template>
Good
Literal text is written directly; variable expressions, interpolated template strings, and intentional separator whitespace remain interpolation cases.
<template>
<div>x</div>
<div>{{ x }}</div>
<div>{{ `pre-${x}` }}</div>
<span>A</span> {{ " " }} <span>B</span>
</template>
Good avoids this rule's finding under the configuration above; other rules may still report diagnostics.
vue/no-useless-v-bind
Disallow a v-bind whose value is a plain string literal
Default severity: warning
Presets: nuxt, opinionated
Automatic fix: None; review the suggested change
Applies to: Vue SFC templates and blocks, with script context where the rule requires it
Options: No rule-specific options. Severity and preset selection are configurable.
Configuration (Vite+)
import { defineConfig } from "@vizejs/vite-plugin/vite-plus";
export default defineConfig({
lint: {
vize: {
"preset": "incremental",
"rules": {
"vue/no-useless-v-bind": "warn"
}
},
},
});
vp run lint
Bad
The foo binding evaluates a constant quoted string or a template string without interpolation.
<template>
<div :foo="'bar'"></div>
<div :foo="`bar`"></div>
</template>
Good
The constant value becomes a static attribute; variable and interpolated values retain their binding.
<template>
<div foo="bar"></div>
<div :foo="bar"></div>
<div :foo="`pre-${bar}`"></div>
</template>
Good avoids this rule's finding under the configuration above; other rules may still report diagnostics.
vue/no-v-text
Disallow the v-text directive; prefer mustache interpolation
Default severity: warning
Presets: nuxt, opinionated
Automatic fix: None; review the suggested change
Applies to: Vue SFC templates and blocks, with script context where the rule requires it
Options: No rule-specific options. Severity and preset selection are configurable.
Configuration (Vite+)
import { defineConfig } from "@vizejs/vite-plugin/vite-plus";
export default defineConfig({
lint: {
vize: {
"preset": "incremental",
"rules": {
"vue/no-v-text": "warn"
}
},
},
});
vp run lint
Bad
The div's content is supplied through the v-text directive.
<template>
<div v-text="message"></div>
</template>
Good
Mustache interpolation expresses the same text binding directly in the element content.
<template>
<div>{{ message }}</div>
</template>
Good avoids this rule's finding under the configuration above; other rules may still report diagnostics.
vue/prefer-props-shorthand
Recommend shorthand syntax for props (Vue 3.4+)
Default severity: warning
Presets: nuxt, opinionated
Automatic fix: Available for supported findings
Applies to: Vue SFC templates and blocks, with script context where the rule requires it
Options: No rule-specific options. Severity and preset selection are configurable.
Configuration (Vite+)
import { defineConfig } from "@vizejs/vite-plugin/vite-plus";
export default defineConfig({
lint: {
vize: {
"preset": "incremental",
"rules": {
"vue/prefer-props-shorthand": "warn"
}
},
},
});
vp run lint
Bad
Each binding repeats the corresponding variable name, including the camelCase equivalent of a hyphenated argument.
<template>
<MyComponent :foo="foo" />
<MyComponent :user-name="userName" />
<span :style="style" />
<div :aria-label="ariaLabel" />
</template>
Good
Vue 3.4+ same-name binding shorthand removes the repeated expressions; a different source variable such as bar remains explicit.
<template>
<MyComponent :foo />
<MyComponent :user-name />
<span :style />
<div :aria-label />
<MyComponent :foo="bar" />
</template>
Good avoids this rule's finding under the configuration above; other rules may still report diagnostics.
vue/prefer-true-attribute-shorthand
Prefer the shorthand for a boolean attribute bound to true
Default severity: warning
Presets: nuxt, opinionated
Automatic fix: None; review the suggested change
Applies to: Vue SFC templates and blocks, with script context where the rule requires it
Options: No rule-specific options. Severity and preset selection are configurable.
Configuration (Vite+)
import { defineConfig } from "@vizejs/vite-plugin/vite-plus";
export default defineConfig({
lint: {
vize: {
"preset": "incremental",
"rules": {
"vue/prefer-true-attribute-shorthand": "warn"
}
},
},
});
vp run lint
Bad
A native boolean disabled attribute binds the constant true value.
<template>
<input :disabled="true" />
</template>
Good
The native attribute uses its boolean shorthand. False bindings and component props retain their explicit values.
<template>
<input disabled />
<input :disabled="false" />
<MyComponent :visible="true" />
<MyComponent :visible="isVisible" />
</template>
Good avoids this rule's finding under the configuration above; other rules may still report diagnostics.
vue/require-component-registration
Require explicit import or registration for components
Default severity: warning
Presets: opinionated
Automatic fix: None; review the suggested change
Applies to: Vue SFC templates and blocks, with script context where the rule requires it
Options: See typed options and defaults.
List explicit component names supplied by application plugins or Musea previewSetup. PascalCase and kebab-case spellings are accepted; regular expressions are not interpreted. Options do not enable the rule. Later layers replace the list; an empty list clears inherited names.
Configuration (Vite+)
import { defineConfig } from "@vizejs/vite-plugin/vite-plus";
export default defineConfig({
lint: {
vize: {
"preset": "incremental",
"rules": {
"vue/require-component-registration": "warn"
},
"ruleOptions": {
"vue/require-component-registration": {
"globals": [
"MyButton",
"MyIcon"
]
}
}
},
},
});
vp run lint
Bad
MissingWidget is neither registered nor included in the configured global-component allowlist.
<template>
<MissingWidget />
</template>
Good
MyButton is listed in the example's globals option. That option exempts a known global component; it does not register or import it.
<template>
<MyButton />
</template>
Good avoids this rule's finding under the configuration above; other rules may still report diagnostics.
vue/require-scoped-style
Require scoped attribute on style tags
Default severity: warning
Presets: happy-path, nuxt, ecosystem, opinionated
Automatic fix: None; review the suggested change
Applies to: Vue SFC templates and blocks, with script context where the rule requires it
Options: No rule-specific options. Severity and preset selection are configurable.
Configuration (Vite+)
import { defineConfig } from "@vizejs/vite-plugin/vite-plus";
export default defineConfig({
lint: {
vize: {
"preset": "incremental",
"rules": {
"vue/require-scoped-style": "warn"
}
},
},
});
vp run lint
Bad
The .button style is unscoped and can affect matching elements outside this component.
<style>
.button {
color: red;
}
</style>
Good
Adding scoped applies Vue's component scope to the same selector and declarations.
<style scoped>
.button {
color: red;
}
</style>
Good avoids this rule's finding under the configuration above; other rules may still report diagnostics.
vue/scoped-event-names
Recommend scoped event names using context:event format
Default severity: warning
Presets: nuxt, opinionated
Automatic fix: None; review the suggested change
Applies to: Vue SFC templates and blocks, with script context where the rule requires it
Options: No rule-specific options. Severity and preset selection are configurable.
Configuration (Vite+)
import { defineConfig } from "@vizejs/vite-plugin/vite-plus";
export default defineConfig({
lint: {
vize: {
"preset": "incremental",
"rules": {
"vue/scoped-event-names": "warn"
}
},
},
});
vp run lint
Bad
playAudio, pauseAudio, and reloadAudio encode their scope as camel-case suffixes rather than the rule's colon-separated event convention.
<template>
<AudioPlayer
@playAudio="play"
@pauseAudio="pause"
@reloadAudio="reload"
/>
</template>
Good
audio:play, audio:pause, and audio:reload share an explicit audio: scope. The emitting component must use the same names.
<template>
<AudioPlayer
@audio:play="play"
@audio:pause="pause"
@audio:reload="reload"
/>
</template>
Good avoids this rule's finding under the configuration above; other rules may still report diagnostics.
vue/sfc-element-order
Enforce consistent order of SFC top-level elements
Default severity: warning
Presets: happy-path, nuxt, ecosystem, opinionated
Automatic fix: None; review the suggested change
Applies to: Vue SFC templates and blocks, with script context where the rule requires it
Options: See typed options and defaults.
Configuration (Vite+)
import { defineConfig } from "@vizejs/vite-plugin/vite-plus";
export default defineConfig({
lint: {
vize: {
"preset": "incremental",
"rules": {
"vue/sfc-element-order": "warn"
}
},
},
});
vp run lint
Bad
The style block precedes the script block, contrary to the configured SFC block order.
<style scoped>
.panel {
color: red;
}
</style>
<script setup lang="ts">
const label = "Save";
</script>
Good
The blocks follow script → template → style. Projects can choose a different order through this rule's typed option.
<script setup lang="ts">
const label = "Save";
</script>
<template>
<p>{{ label }}</p>
</template>
<style scoped>
p {
color: red;
}
</style>
Good avoids this rule's finding under the configuration above; other rules may still report diagnostics.
vue/single-style-block
Recommend having a single style block
Default severity: warning
Presets: happy-path, nuxt, ecosystem, opinionated
Automatic fix: None; review the suggested change
Applies to: Vue SFC templates and blocks, with script context where the rule requires it
Options: No rule-specific options. Severity and preset selection are configurable.
Configuration (Vite+)
import { defineConfig } from "@vizejs/vite-plugin/vite-plus";
export default defineConfig({
lint: {
vize: {
"preset": "incremental",
"rules": {
"vue/single-style-block": "warn"
}
},
},
});
vp run lint
Bad
The component splits its scoped panel and title styles across two style blocks.
<style scoped>
.panel {
color: red;
}
</style>
<style scoped>
.title {
color: blue;
}
</style>
Good
Both selectors stay scoped in one style block, satisfying the single-block convention without dropping either style.
<style scoped>
.panel {
color: red;
}
.title {
color: blue;
}
</style>
Good avoids this rule's finding under the configuration above; other rules may still report diagnostics.
vue/slot-name-casing
Enforce kebab-case for named slots used via v-slot
Default severity: warning
Presets: nuxt, opinionated
Automatic fix: None; review the suggested change
Applies to: Vue SFC templates and blocks, with script context where the rule requires it
Options: No rule-specific options. Severity and preset selection are configurable.
Configuration (Vite+)
import { defineConfig } from "@vizejs/vite-plugin/vite-plus";
export default defineConfig({
lint: {
vize: {
"preset": "incremental",
"rules": {
"vue/slot-name-casing": "warn"
}
},
},
});
vp run lint
Bad
The named slot mySlot uses camelCase where the rule requires a hyphenated name.
<template>
<MyCard><template #mySlot>Content</template></MyCard>
</template>
Good
#my-slot uses kebab-case. Rename the corresponding slot outlet to the same name.
<template>
<MyCard><template #my-slot>Content</template></MyCard>
</template>
Good avoids this rule's finding under the configuration above; other rules may still report diagnostics.
vue/this-in-template
Disallow this. in template expressions
Default severity: warning
Presets: nuxt, opinionated
Automatic fix: None; review the suggested change
Applies to: Vue SFC templates and blocks, with script context where the rule requires it
Options: No rule-specific options. Severity and preset selection are configurable.
Configuration (Vite+)
import { defineConfig } from "@vizejs/vite-plugin/vite-plus";
export default defineConfig({
lint: {
vize: {
"preset": "incremental",
"rules": {
"vue/this-in-template": "warn"
}
},
},
});
vp run lint
Bad
Template expressions explicitly access this.message, this.className, and this.handleClick, although Vue exposes those bindings directly.
<template>
<div>{{ this.message }}</div>
<div :class="this.className"></div>
<button @click="this.handleClick()"></button>
</template>
Good
Use message, className, and handleClick directly. The literal string 'this.is.a.string' stays unchanged because it is not a member access.
<template>
<div>{{ message }}</div>
<div :class="className"></div>
<button @click="handleClick()"></button>
<div>{{ 'this.is.a.string' }}</div>
</template>
Good avoids this rule's finding under the configuration above; other rules may still report diagnostics.
vue/v-on-event-hyphenation
Enforce hyphenation of custom event names in v-on on components
Default severity: warning
Presets: nuxt, opinionated
Automatic fix: None; review the suggested change
Applies to: Vue SFC templates and blocks, with script context where the rule requires it
Options: See typed options and defaults.
Configuration (Vite+)
import { defineConfig } from "@vizejs/vite-plugin/vite-plus";
export default defineConfig({
lint: {
vize: {
"preset": "incremental",
"rules": {
"vue/v-on-event-hyphenation": "warn"
}
},
},
});
vp run lint
Bad
The custom component listener uses @myEvent instead of a hyphenated event name.
<template>
<MyComponent @myEvent="handler" />
<MyComponent v-on:myEvent="handler" />
</template>
Good
@my-event uses the required custom-event spelling. Native-element listeners and dynamic event arguments shown below are outside this check.
<template>
<MyComponent @my-event="handler" />
<div @myEvent="handler" />
<MyComponent @[dynamicEvent]="handler" />
</template>
Good avoids this rule's finding under the configuration above; other rules may still report diagnostics.
vue/v-on-handler-style
Enforce writing v-on handlers as a method reference or an inline function
Default severity: warning
Presets: nuxt, opinionated
Automatic fix: None; review the suggested change
Applies to: Vue SFC templates and blocks, with script context where the rule requires it
Options: No rule-specific options. Severity and preset selection are configurable.
Configuration (Vite+)
import { defineConfig } from "@vizejs/vite-plugin/vite-plus";
export default defineConfig({
lint: {
vize: {
"preset": "incremental",
"rules": {
"vue/v-on-handler-style": "warn"
}
},
},
});
vp run lint
Bad
The handlers put mutations and multiple statements directly in the event attribute.
<template>
<button @click="count++"></button>
<button @click="doThis(); doThat()"></button>
<button @click="foo = bar"></button>
</template>
Good
Use a handler reference, or an arrow/function expression when inline logic is needed. The function boundary makes the handler form explicit.
<template>
<button @click="handler"></button>
<button @click="foo.bar"></button>
<button @click="() => count++"></button>
<button @click="function () { count++ }"></button>
</template>
Good avoids this rule's finding under the configuration above; other rules may still report diagnostics.
vue/warn-custom-block
Warn about custom blocks in SFC files
Default severity: warning
Presets: nuxt, opinionated
Automatic fix: None; review the suggested change
Applies to: Vue SFC templates and blocks, with script context where the rule requires it
Options: No rule-specific options. Severity and preset selection are configurable.
Configuration (Vite+)
import { defineConfig } from "@vizejs/vite-plugin/vite-plus";
export default defineConfig({
lint: {
vize: {
"preset": "incremental",
"rules": {
"vue/warn-custom-block": "warn"
}
},
},
});
vp run lint
Bad
The SFC contains an <i18n> custom block, which needs an external integration beyond ordinary template/script/style processing.
<i18n>
{ "en": { "hello": "Hello" } }
</i18n>
<template>
<p>{{ hello }}</p>
</template>
Good
The example uses standard template and script-setup blocks. This optional portability warning does not mean every custom block is invalid Vue.
<template>
<p>{{ hello }}</p>
</template>
<script setup lang="ts">
const hello = "Hello";
</script>
Good avoids this rule's finding under the configuration above; other rules may still report diagnostics.
vue/warn-custom-directive
Warn about custom directives that need registration
Default severity: warning
Presets: nuxt, opinionated
Automatic fix: None; review the suggested change
Applies to: Vue SFC templates and blocks, with script context where the rule requires it
Options: No rule-specific options. Severity and preset selection are configurable.
Configuration (Vite+)
import { defineConfig } from "@vizejs/vite-plugin/vite-plus";
export default defineConfig({
lint: {
vize: {
"preset": "incremental",
"rules": {
"vue/warn-custom-directive": "warn"
}
},
},
});
vp run lint
Bad
v-focus, v-mask, and v-click-outside require project-specific directive implementations that this optional convention flags.
<template>
<input v-focus />
<input v-mask="'###-####'" />
<div v-click-outside="handleClose"></div>
</template>
Good
The example uses built-in v-if, v-model, and v-on. A correctly registered custom directive can still be valid Vue when this policy is disabled.
<template>
<div v-if="ready"></div>
<input v-model="value" />
<button type="button" @click="onClick">Save</button>
</template>
Good avoids this rule's finding under the configuration above; other rules may still report diagnostics.
a11y/alt-text
Require alternative text for media elements
Default severity: warning
Presets: happy-path, nuxt, ecosystem, opinionated
Automatic fix: None; review the suggested change
Applies to: Vue SFC templates and blocks, with script context where the rule requires it
Options: No rule-specific options. Severity and preset selection are configurable.
Configuration (Vite+)
import { defineConfig } from "@vizejs/vite-plugin/vite-plus";
export default defineConfig({
lint: {
vize: {
"preset": "incremental",
"rules": {
"a11y/alt-text": "warn"
}
},
},
});
vp run lint
Bad
The image submit control supplies only its image URL; it has no alt text describing the action.
<template>
<input type="image" src="/submit.png" />
</template>
Good
alt="Submit search" gives the image control an accessible name that describes submitting the search.
<template>
<input type="image" src="/submit.png" alt="Submit search" />
</template>
Good avoids this rule's finding under the configuration above; other rules may still report diagnostics.
a11y/anchor-has-content
Require anchor elements to have accessible content
Default severity: warning
Presets: happy-path, nuxt, ecosystem, opinionated
Automatic fix: None; review the suggested change
Applies to: Vue SFC templates and blocks, with script context where the rule requires it
Options: No rule-specific options. Severity and preset selection are configurable.
Configuration (Vite+)
import { defineConfig } from "@vizejs/vite-plugin/vite-plus";
export default defineConfig({
lint: {
vize: {
"preset": "incremental",
"rules": {
"a11y/anchor-has-content": "warn"
}
},
},
});
vp run lint
Bad
The /settings link has no text or other naming content, so its destination has no accessible description.
<template>
<a href="/settings"></a>
</template>
Good
The visible Settings text supplies content for the same destination link.
<template>
<a href="/settings">Settings</a>
</template>
Good avoids this rule's finding under the configuration above; other rules may still report diagnostics.
a11y/anchor-is-valid
Enforce valid href on anchor elements
Default severity: warning
Presets: happy-path, nuxt, ecosystem, opinionated
Automatic fix: None; review the suggested change
Applies to: Vue SFC templates and blocks, with script context where the rule requires it
Options: No rule-specific options. Severity and preset selection are configurable.
Configuration (Vite+)
import { defineConfig } from "@vizejs/vite-plugin/vite-plus";
export default defineConfig({
lint: {
vize: {
"preset": "incremental",
"rules": {
"a11y/anchor-is-valid": "warn"
}
},
},
});
vp run lint
Bad
The first anchor uses # for an action; the second uses a JavaScript URL. Neither provides an ordinary navigation destination.
<template>
<a href="#" @click="openPanel">Open panel</a>
<a href="JaVaScRiPt:void(0)">Run action</a>
</template>
Good
A native button performs openPanel, while the remaining anchor has the real /docs/javascript-urls destination.
<template>
<button type="button" @click="openPanel">Open panel</button>
<a href="/docs/javascript-urls">JavaScript URL guide</a>
</template>
Good avoids this rule's finding under the configuration above; other rules may still report diagnostics.
a11y/aria-props
Disallow invalid ARIA attributes
Default severity: error
Presets: happy-path, nuxt, ecosystem, opinionated
Automatic fix: None; review the suggested change
Applies to: Vue SFC templates and blocks, with script context where the rule requires it
Options: No rule-specific options. Severity and preset selection are configurable.
Configuration (Vite+)
import { defineConfig } from "@vizejs/vite-plugin/vite-plus";
export default defineConfig({
lint: {
vize: {
"preset": "incremental",
"rules": {
"a11y/aria-props": "error"
}
},
},
});
vp run lint
Bad
aria-lable is misspelled and is not a supported ARIA attribute.
<template>
<button aria-lable="Save changes">Save</button>
</template>
Good
The supported aria-label attribute supplies the button name.
<template>
<button aria-label="Save changes">Save</button>
</template>
Good avoids this rule's finding under the configuration above; other rules may still report diagnostics.
a11y/aria-role
Elements with ARIA roles must use a valid, non-abstract ARIA role
Default severity: error
Presets: happy-path, nuxt, ecosystem, opinionated
Automatic fix: None; review the suggested change
Applies to: Vue SFC templates and blocks, with script context where the rule requires it
Options: No rule-specific options. Severity and preset selection are configurable.
Configuration (Vite+)
import { defineConfig } from "@vizejs/vite-plugin/vite-plus";
export default defineConfig({
lint: {
vize: {
"preset": "incremental",
"rules": {
"a11y/aria-role": "error"
}
},
},
});
vp run lint
Bad
datepicker is not a recognized ARIA role for this section.
<template>
<section role="datepicker">...</section>
</template>
Good
The section uses the recognized dialog role and a label describing the date selection.
<template>
<section role="dialog" aria-label="Choose a date">...</section>
</template>
Good avoids this rule's finding under the configuration above; other rules may still report diagnostics.
a11y/aria-unsupported-elements
Disallow ARIA attributes on elements that do not support them
Default severity: error
Presets: happy-path, nuxt, ecosystem, opinionated
Automatic fix: None; review the suggested change
Applies to: Vue SFC templates and blocks, with script context where the rule requires it
Options: No rule-specific options. Severity and preset selection are configurable.
Configuration (Vite+)
import { defineConfig } from "@vizejs/vite-plugin/vite-plus";
export default defineConfig({
lint: {
vize: {
"preset": "incremental",
"rules": {
"a11y/aria-unsupported-elements": "error"
}
},
},
});
vp run lint
Bad
The metadata element carries aria-hidden, although meta does not support ARIA attributes.
<template>
<meta charset="utf-8" aria-hidden="true" />
</template>
Good
Removing the ARIA attribute leaves the charset declaration intact.
<template>
<meta charset="utf-8" />
</template>
Good avoids this rule's finding under the configuration above; other rules may still report diagnostics.
a11y/click-events-have-key-events
Require keyboard event handlers with click events
Default severity: warning
Presets: happy-path, nuxt, ecosystem, opinionated
Automatic fix: None; review the suggested change
Applies to: Vue SFC templates and blocks, with script context where the rule requires it
Options: No rule-specific options. Severity and preset selection are configurable.
Checks non-interactive elements without an interactive role. Native buttons and elements with an interactive ARIA role are outside this rule's finding.
Configuration (Vite+)
import { defineConfig } from "@vizejs/vite-plugin/vite-plus";
export default defineConfig({
lint: {
vize: {
"preset": "incremental",
"rules": {
"a11y/click-events-have-key-events": "warn"
}
},
},
});
vp run lint
Bad
The non-interactive div has a click handler but no keyboard event handling.
<template>
<div @click="activate">Activate</div>
</template>
Good
A native button provides keyboard activation for the same activate handler.
<template>
<button @click="activate">Activate</button>
</template>
Good avoids this rule's finding under the configuration above; other rules may still report diagnostics.
a11y/form-control-has-label
Require form controls to have associated labels
Default severity: warning
Presets: happy-path, nuxt, ecosystem, opinionated
Automatic fix: None; review the suggested change
Applies to: Vue SFC templates and blocks, with script context where the rule requires it
Options: No rule-specific options. Severity and preset selection are configurable.
Configuration (Vite+)
import { defineConfig } from "@vizejs/vite-plugin/vite-plus";
export default defineConfig({
lint: {
vize: {
"preset": "incremental",
"rules": {
"a11y/form-control-has-label": "warn"
}
},
},
});
vp run lint
Bad
The search input has no label identifying what the user should enter.
<template>
<input type="search" />
</template>
Good
Wrapping the input in a label associates the visible Search text with the control.
<template>
<label>
Search
<input type="search" />
</label>
</template>
Good avoids this rule's finding under the configuration above; other rules may still report diagnostics.
a11y/heading-has-content
Require heading elements to have accessible content
Default severity: warning
Presets: happy-path, nuxt, ecosystem, opinionated
Automatic fix: None; review the suggested change
Applies to: Vue SFC templates and blocks, with script context where the rule requires it
Options: No rule-specific options. Severity and preset selection are configurable.
Configuration (Vite+)
import { defineConfig } from "@vizejs/vite-plugin/vite-plus";
export default defineConfig({
lint: {
vize: {
"preset": "incremental",
"rules": {
"a11y/heading-has-content": "warn"
}
},
},
});
vp run lint
Bad
The h2 contributes a heading level but has no heading content.
<template>
<h2></h2>
</template>
Good
Billing settings supplies the content of the existing level-two heading.
<template>
<h2>Billing settings</h2>
</template>
Good avoids this rule's finding under the configuration above; other rules may still report diagnostics.
a11y/heading-levels
Disallow skipping heading levels
Default severity: warning
Presets: nuxt, opinionated
Automatic fix: None; review the suggested change
Applies to: Vue SFC templates and blocks, with script context where the rule requires it
Options: No rule-specific options. Severity and preset selection are configurable.
Configuration (Vite+)
import { defineConfig } from "@vizejs/vite-plugin/vite-plus";
export default defineConfig({
lint: {
vize: {
"preset": "incremental",
"rules": {
"a11y/heading-levels": "warn"
}
},
},
});
vp run lint
Bad
The heading sequence jumps directly from h1 to h3, skipping level two.
<template>
<h1>Account</h1>
<h3>Billing</h3>
</template>
Good
Changing the billing heading to h2 preserves a consecutive heading hierarchy.
<template>
<h1>Account</h1>
<h2>Billing</h2>
</template>
Good avoids this rule's finding under the configuration above; other rules may still report diagnostics.
a11y/iframe-has-title
Require iframe elements to have a title attribute
Default severity: warning
Presets: happy-path, nuxt, ecosystem, opinionated
Automatic fix: None; review the suggested change
Applies to: Vue SFC templates and blocks, with script context where the rule requires it
Options: No rule-specific options. Severity and preset selection are configurable.
Configuration (Vite+)
import { defineConfig } from "@vizejs/vite-plugin/vite-plus";
export default defineConfig({
lint: {
vize: {
"preset": "incremental",
"rules": {
"a11y/iframe-has-title": "warn"
}
},
},
});
vp run lint
Bad
The checkout frame has a source URL but no title describing the embedded content.
<template>
<iframe src="/checkout"></iframe>
</template>
Good
title="Checkout preview" names the content of that frame.
<template>
<iframe src="/checkout" title="Checkout preview"></iframe>
</template>
Good avoids this rule's finding under the configuration above; other rules may still report diagnostics.
a11y/img-alt
Require alt attribute on images for accessibility
Default severity: warning
Presets: none
Automatic fix: None; review the suggested change
Applies to: Vue SFC templates and blocks, with script context where the rule requires it
Options: No rule-specific options. Severity and preset selection are configurable.
Configuration (Vite+)
import { defineConfig } from "@vizejs/vite-plugin/vite-plus";
export default defineConfig({
lint: {
vize: {
"preset": "incremental",
"rules": {
"a11y/img-alt": "warn"
}
},
},
});
vp run lint
Bad
The avatar image is missing its alt attribute.
<template>
<img src="/avatar.png" />
</template>
Good
alt="User avatar" supplies a text alternative for the avatar.
<template>
<img src="/avatar.png" alt="User avatar" />
</template>
Good avoids this rule's finding under the configuration above; other rules may still report diagnostics.
a11y/interactive-supports-focus
Require interactive role elements to be focusable
Default severity: warning
Presets: happy-path, nuxt, ecosystem, opinionated
Automatic fix: None; review the suggested change
Applies to: Vue SFC templates and blocks, with script context where the rule requires it
Options: No rule-specific options. Severity and preset selection are configurable.
Configuration (Vite+)
import { defineConfig } from "@vizejs/vite-plugin/vite-plus";
export default defineConfig({
lint: {
vize: {
"preset": "incremental",
"rules": {
"a11y/interactive-supports-focus": "warn"
}
},
},
});
vp run lint
Bad
Giving a span the button role and a click handler does not make the element keyboard-focusable.
<template>
<span role="button" @click="open">Open</span>
</template>
Good
The native button is focusable and retains the same open action.
<template>
<button type="button" @click="open">Open</button>
</template>
Good avoids this rule's finding under the configuration above; other rules may still report diagnostics.
a11y/label-has-for
Require labels to have associated form controls
Default severity: warning
Presets: happy-path, nuxt, ecosystem, opinionated
Automatic fix: None; review the suggested change
Applies to: Vue SFC templates and blocks, with script context where the rule requires it
Options: No rule-specific options. Severity and preset selection are configurable.
Configuration (Vite+)
import { defineConfig } from "@vizejs/vite-plugin/vite-plus";
export default defineConfig({
lint: {
vize: {
"preset": "incremental",
"rules": {
"a11y/label-has-for": "warn"
}
},
},
});
vp run lint
Bad
The separate label is neither associated through for nor wrapped around the input.
<template>
<label>Email</label>
<input id="email" />
</template>
Good
for="email" matches the input ID and explicitly associates the two elements.
<template>
<label for="email">Email</label>
<input id="email" />
</template>
Good avoids this rule's finding under the configuration above; other rules may still report diagnostics.
a11y/landmark-roles
Validate landmark role placement and uniqueness
Default severity: warning
Presets: nuxt, opinionated
Automatic fix: None; review the suggested change
Applies to: Vue SFC templates and blocks, with script context where the rule requires it
Options: No rule-specific options. Severity and preset selection are configurable.
Configuration (Vite+)
import { defineConfig } from "@vizejs/vite-plugin/vite-plus";
export default defineConfig({
lint: {
vize: {
"preset": "incremental",
"rules": {
"a11y/landmark-roles": "warn"
}
},
},
});
vp run lint
Bad
Two main elements declare duplicate main landmarks in the same template.
<template>
<main>Dashboard</main>
<main>Settings</main>
</template>
Good
The dashboard remains the main landmark; the settings area becomes a named navigation landmark.
<template>
<main>Dashboard</main>
<nav aria-label="Settings">...</nav>
</template>
Good avoids this rule's finding under the configuration above; other rules may still report diagnostics.
a11y/media-has-caption
Require media elements to have captions
Default severity: warning
Presets: happy-path, nuxt, ecosystem, opinionated
Automatic fix: None; review the suggested change
Applies to: Vue SFC templates and blocks, with script context where the rule requires it
Options: No rule-specific options. Severity and preset selection are configurable.
Configuration (Vite+)
import { defineConfig } from "@vizejs/vite-plugin/vite-plus";
export default defineConfig({
lint: {
vize: {
"preset": "incremental",
"rules": {
"a11y/media-has-caption": "warn"
}
},
},
});
vp run lint
Bad
The video has playback controls but no caption track.
<template>
<video src="/demo.mp4" controls />
</template>
Good
A track with kind="captions" supplies the English captions for the same video.
<template>
<video src="/demo.mp4" controls>
<track kind="captions" src="/demo.en.vtt" srclang="en" label="English" />
</video>
</template>
Good avoids this rule's finding under the configuration above; other rules may still report diagnostics.
a11y/mouse-events-have-key-events
Require focus/blur events with mouse events
Default severity: warning
Presets: happy-path, nuxt, ecosystem, opinionated
Automatic fix: None; review the suggested change
Applies to: Vue SFC templates and blocks, with script context where the rule requires it
Options: No rule-specific options. Severity and preset selection are configurable.
Configuration (Vite+)
import { defineConfig } from "@vizejs/vite-plugin/vite-plus";
export default defineConfig({
lint: {
vize: {
"preset": "incremental",
"rules": {
"a11y/mouse-events-have-key-events": "warn"
}
},
},
});
vp run lint
Bad
Preview visibility changes only through mouse enter and leave handlers.
<template>
<div @mouseenter="showPreview" @mouseleave="hidePreview">Preview</div>
</template>
Good
The same preview actions run on focus and blur, and the button can receive keyboard focus.
<template>
<button
type="button"
@focus="showPreview"
@blur="hidePreview"
@mouseenter="showPreview"
@mouseleave="hidePreview"
>
Preview
</button>
</template>
Good avoids this rule's finding under the configuration above; other rules may still report diagnostics.
a11y/no-access-key
Disallow the use of the accesskey attribute
Default severity: warning
Presets: happy-path, nuxt, ecosystem, opinionated
Automatic fix: None; review the suggested change
Applies to: Vue SFC templates and blocks, with script context where the rule requires it
Options: No rule-specific options. Severity and preset selection are configurable.
Configuration (Vite+)
import { defineConfig } from "@vizejs/vite-plugin/vite-plus";
export default defineConfig({
lint: {
vize: {
"preset": "incremental",
"rules": {
"a11y/no-access-key": "warn"
}
},
},
});
vp run lint
Bad
The accesskey="s" shortcut may conflict with browser or assistive-technology shortcuts.
<template>
<button accesskey="s">Save</button>
</template>
Good
Removing accesskey keeps the ordinary Save button available.
<template>
<button>Save</button>
</template>
Good avoids this rule's finding under the configuration above; other rules may still report diagnostics.
a11y/no-aria-hidden-on-focusable
Disallow aria-hidden="true" on focusable elements
Default severity: error
Presets: happy-path, nuxt, ecosystem, opinionated
Automatic fix: None; review the suggested change
Applies to: Vue SFC templates and blocks, with script context where the rule requires it
Options: No rule-specific options. Severity and preset selection are configurable.
Configuration (Vite+)
import { defineConfig } from "@vizejs/vite-plugin/vite-plus";
export default defineConfig({
lint: {
vize: {
"preset": "incremental",
"rules": {
"a11y/no-aria-hidden-on-focusable": "error"
}
},
},
});
vp run lint
Bad
The focusable Close button is hidden from the accessibility tree with aria-hidden="true".
<template>
<button aria-hidden="true" @click="close">Close</button>
</template>
Good
The button remains exposed and receives a Close label instead of being hidden.
<template>
<button aria-label="Close" @click="close">Close</button>
</template>
Good avoids this rule's finding under the configuration above; other rules may still report diagnostics.
a11y/no-autofocus
Disallow the use of the autofocus attribute
Default severity: warning
Presets: happy-path, nuxt, ecosystem, opinionated
Automatic fix: None; review the suggested change
Applies to: Vue SFC templates and blocks, with script context where the rule requires it
Options: No rule-specific options. Severity and preset selection are configurable.
Configuration (Vite+)
import { defineConfig } from "@vizejs/vite-plugin/vite-plus";
export default defineConfig({
lint: {
vize: {
"preset": "incremental",
"rules": {
"a11y/no-autofocus": "warn"
}
},
},
});
vp run lint
Bad
The input requests automatic focus when it appears.
<template>
<input autofocus name="query" />
</template>
Good
Removing autofocus avoids this automatic focus request while retaining the query input.
<template>
<input name="query" />
</template>
Good avoids this rule's finding under the configuration above; other rules may still report diagnostics.
a11y/no-distracting-elements
Disallow distracting elements like <marquee> and <blink>
Default severity: warning
Presets: happy-path, nuxt, ecosystem, opinionated
Automatic fix: None; review the suggested change
Applies to: Vue SFC templates and blocks, with script context where the rule requires it
Options: No rule-specific options. Severity and preset selection are configurable.
Configuration (Vite+)
import { defineConfig } from "@vizejs/vite-plugin/vite-plus";
export default defineConfig({
lint: {
vize: {
"preset": "incremental",
"rules": {
"a11y/no-distracting-elements": "warn"
}
},
},
});
vp run lint
Bad
The marquee element introduces automatically moving text.
<template>
<marquee>Limited offer</marquee>
</template>
Good
A paragraph displays the same offer without the distracting marquee element.
<template>
<p>Limited offer</p>
</template>
Good avoids this rule's finding under the configuration above; other rules may still report diagnostics.
a11y/no-i-for-icon
Disallow using <i> element for icons
Default severity: warning
Presets: happy-path, nuxt, ecosystem, opinionated
Automatic fix: None; review the suggested change
Applies to: Vue SFC templates and blocks, with script context where the rule requires it
Options: No rule-specific options. Severity and preset selection are configurable.
Configuration (Vite+)
import { defineConfig } from "@vizejs/vite-plugin/vite-plus";
export default defineConfig({
lint: {
vize: {
"preset": "incremental",
"rules": {
"a11y/no-i-for-icon": "warn"
}
},
},
});
vp run lint
Bad
The icon is rendered through i, whose text semantics do not describe an icon-only action.
<template>
<button>
<i class="material-icons">delete</i>
</button>
</template>
Good
A decorative span hides the icon glyph, while the separate Delete item text names the button action.
<template>
<button>
<span class="material-icons" aria-hidden="true">delete</span>
<span class="sr-only">Delete item</span>
</button>
</template>
Good avoids this rule's finding under the configuration above; other rules may still report diagnostics.
a11y/no-redundant-roles
Disallow redundant ARIA roles
Default severity: warning
Presets: happy-path, nuxt, ecosystem, opinionated
Automatic fix: Available for supported findings
Applies to: Vue SFC templates and blocks, with script context where the rule requires it
Options: No rule-specific options. Severity and preset selection are configurable.
Configuration (Vite+)
import { defineConfig } from "@vizejs/vite-plugin/vite-plus";
export default defineConfig({
lint: {
vize: {
"preset": "incremental",
"rules": {
"a11y/no-redundant-roles": "warn"
}
},
},
});
vp run lint
Bad
The native button already has the button role, so role="button" repeats its implicit semantics.
<template>
<button role="button">Save</button>
</template>
Good
Removing the repeated role keeps the button semantics supplied by HTML.
<template>
<button>Save</button>
</template>
Good avoids this rule's finding under the configuration above; other rules may still report diagnostics.
a11y/no-refer-to-non-existent-id
Disallow references to non-existent IDs
Default severity: warning
Presets: happy-path, nuxt, ecosystem, opinionated
Automatic fix: None; review the suggested change
Applies to: Vue SFC templates and blocks, with script context where the rule requires it
Options: No rule-specific options. Severity and preset selection are configurable.
Configuration (Vite+)
import { defineConfig } from "@vizejs/vite-plugin/vite-plus";
export default defineConfig({
lint: {
vize: {
"preset": "incremental",
"rules": {
"a11y/no-refer-to-non-existent-id": "warn"
}
},
},
});
vp run lint
Bad
aria-labelledby points to save-label, but no element declares that ID.
<template>
<button aria-labelledby="save-label">Save</button>
</template>
Good
Adding the matching span resolves the reference and provides the button label.
<template>
<span id="save-label">Save changes</span>
<button aria-labelledby="save-label">Save</button>
</template>
Good avoids this rule's finding under the configuration above; other rules may still report diagnostics.
a11y/no-role-presentation-on-focusable
Disallow role="presentation" or role="none" on focusable elements
Default severity: error
Presets: happy-path, nuxt, ecosystem, opinionated
Automatic fix: None; review the suggested change
Applies to: Vue SFC templates and blocks, with script context where the rule requires it
Options: No rule-specific options. Severity and preset selection are configurable.
Configuration (Vite+)
import { defineConfig } from "@vizejs/vite-plugin/vite-plus";
export default defineConfig({
lint: {
vize: {
"preset": "incremental",
"rules": {
"a11y/no-role-presentation-on-focusable": "error"
}
},
},
});
vp run lint
Bad
The focusable billing link requests role=presentation, which conflicts with its interactive link role; browsers must ignore that presentation request.
<template>
<a href="/billing" role="presentation">Billing</a>
</template>
Good
Remove the conflicting presentation request and rely on the native link role and billing destination.
<template>
<a href="/billing">Billing</a>
</template>
Good avoids this rule's finding under the configuration above; other rules may still report diagnostics.
a11y/no-static-element-interactions
Disallow event handlers on static elements
Default severity: warning
Presets: happy-path, nuxt, ecosystem, opinionated
Automatic fix: None; review the suggested change
Applies to: Vue SFC templates and blocks, with script context where the rule requires it
Options: No rule-specific options. Severity and preset selection are configurable.
Configuration (Vite+)
import { defineConfig } from "@vizejs/vite-plugin/vite-plus";
export default defineConfig({
lint: {
vize: {
"preset": "incremental",
"rules": {
"a11y/no-static-element-interactions": "warn"
}
},
},
});
vp run lint
Bad
A static section receives an Enter-key action without an interactive role.
<template>
<section @keydown.enter="select">Select</section>
</template>
Good
A native button carries the same action with an appropriate interactive element.
<template>
<button type="button" @keydown.enter="select">Select</button>
</template>
Good avoids this rule's finding under the configuration above; other rules may still report diagnostics.
a11y/placeholder-label-option
Require disabled or hidden on select placeholder option
Default severity: warning
Presets: nuxt, opinionated
Automatic fix: None; review the suggested change
Applies to: Vue SFC templates and blocks, with script context where the rule requires it
Options: No rule-specific options. Severity and preset selection are configurable.
Configuration (Vite+)
import { defineConfig } from "@vizejs/vite-plugin/vite-plus";
export default defineConfig({
lint: {
vize: {
"preset": "incremental",
"rules": {
"a11y/placeholder-label-option": "warn"
}
},
},
});
vp run lint
Bad
The empty-value prompt remains selectable as if it were a country value.
<template>
<select v-model="country">
<option value="">Choose a country</option>
<option value="jp">Japan</option>
</select>
</template>
Good
Adding disabled distinguishes the prompt from the selectable Japan option.
<template>
<select v-model="country">
<option value="" disabled>Choose a country</option>
<option value="jp">Japan</option>
</select>
</template>
Good avoids this rule's finding under the configuration above; other rules may still report diagnostics.
a11y/role-has-required-aria-props
Require ARIA roles to have required properties
Default severity: warning
Presets: happy-path, nuxt, ecosystem, opinionated
Automatic fix: None; review the suggested change
Applies to: Vue SFC templates and blocks, with script context where the rule requires it
Options: No rule-specific options. Severity and preset selection are configurable.
Configuration (Vite+)
import { defineConfig } from "@vizejs/vite-plugin/vite-plus";
export default defineConfig({
lint: {
vize: {
"preset": "incremental",
"rules": {
"a11y/role-has-required-aria-props": "warn"
}
},
},
});
vp run lint
Bad
The checkbox role omits aria-checked, which conveys the checkbox state.
<template>
<span role="checkbox">Receive updates</span>
</template>
Good
aria-checked="false" supplies the state required by the checkbox role.
<template>
<span role="checkbox" aria-checked="false">Receive updates</span>
</template>
Good avoids this rule's finding under the configuration above; other rules may still report diagnostics.
a11y/tabindex-no-positive
Disallow positive tabindex values
Default severity: warning
Presets: happy-path, nuxt, ecosystem, opinionated
Automatic fix: None; review the suggested change
Applies to: Vue SFC templates and blocks, with script context where the rule requires it
Options: No rule-specific options. Severity and preset selection are configurable.
Configuration (Vite+)
import { defineConfig } from "@vizejs/vite-plugin/vite-plus";
export default defineConfig({
lint: {
vize: {
"preset": "incremental",
"rules": {
"a11y/tabindex-no-positive": "warn"
}
},
},
});
vp run lint
Bad
A positive tabindex of 3 creates a custom focus order ahead of ordinary controls.
<template>
<button tabindex="3">Save</button>
</template>
Good
The button uses its native focus order without a positive tabindex.
<template>
<button>Save</button>
</template>
Good avoids this rule's finding under the configuration above; other rules may still report diagnostics.
a11y/use-list
Suggest using list elements for bullet-like text
Default severity: warning
Presets: nuxt, opinionated
Automatic fix: None; review the suggested change
Applies to: Vue SFC templates and blocks, with script context where the rule requires it
Options: No rule-specific options. Severity and preset selection are configurable.
Configuration (Vite+)
import { defineConfig } from "@vizejs/vite-plugin/vite-plus";
export default defineConfig({
lint: {
vize: {
"preset": "incremental",
"rules": {
"a11y/use-list": "warn"
}
},
},
});
vp run lint
Bad
The task items are separate paragraphs with typed dash markers rather than list elements.
<template>
<p>- First task</p>
<p>- Second task</p>
</template>
Good
An unordered list and list items express the same tasks with list semantics.
<template>
<ul>
<li>First task</li>
<li>Second task</li>
</ul>
</template>
Good avoids this rule's finding under the configuration above; other rules may still report diagnostics.
vue/use-unique-element-ids
Enforce unique element IDs using useId() instead of static literals
Default severity: warning
Presets: nuxt, opinionated
Automatic fix: None; review the suggested change
Applies to: Vue SFC templates and blocks, with script context where the rule requires it
Options: No rule-specific options. Severity and preset selection are configurable.
Configuration (Vite+)
import { defineConfig } from "@vizejs/vite-plugin/vite-plus";
export default defineConfig({
lint: {
vize: {
"preset": "incremental",
"rules": {
"vue/use-unique-element-ids": "warn"
}
},
},
});
vp run lint
Bad
The literal email ID is reused by every instance of this component, which can misdirect its label when several instances are rendered.
<template>
<label for="email">Email</label>
<input id="email" />
</template>
Good
useId() produces the instance's emailId; bind the same value to the label's for and the input's id.
<script setup>
import { useId } from "vue";
const emailId = useId();
</script>
<template>
<label :for="emailId">Email</label>
<input :id="emailId" />
</template>
Good avoids this rule's finding under the configuration above; other rules may still report diagnostics.
html/deprecated-attr
Disallow deprecated HTML attributes
Default severity: warning
Presets: happy-path, nuxt, ecosystem, opinionated
Automatic fix: None; review the suggested change
Applies to: Vue SFC templates and blocks, with script context where the rule requires it
Options: No rule-specific options. Severity and preset selection are configurable.
Configuration (Vite+)
import { defineConfig } from "@vizejs/vite-plugin/vite-plus";
export default defineConfig({
lint: {
vize: {
"preset": "incremental",
"rules": {
"html/deprecated-attr": "warn"
}
},
},
});
vp run lint
Bad
The paragraph uses the deprecated presentational align attribute.
<template>
<p align="center">Notice</p>
</template>
Good
The class and text-align: center declaration express the alignment through CSS.
<template><p class="notice">Notice</p></template>
<style scoped>.notice { text-align: center; }</style>
Good avoids this rule's finding under the configuration above; other rules may still report diagnostics.
html/deprecated-element
Disallow deprecated HTML elements
Default severity: warning
Presets: happy-path, nuxt, ecosystem, opinionated
Automatic fix: None; review the suggested change
Applies to: Vue SFC templates and blocks, with script context where the rule requires it
Options: No rule-specific options. Severity and preset selection are configurable.
Configuration (Vite+)
import { defineConfig } from "@vizejs/vite-plugin/vite-plus";
export default defineConfig({
lint: {
vize: {
"preset": "incremental",
"rules": {
"html/deprecated-element": "warn"
}
},
},
});
vp run lint
Bad
The center element uses a deprecated HTML presentation element.
<template>
<center>Profile</center>
</template>
Good
A section and a styling class replace the deprecated element while preserving the content.
<template>
<section class="profile">Profile</section>
</template>
Good avoids this rule's finding under the configuration above; other rules may still report diagnostics.
html/id-duplication
Disallow duplicate element IDs
Default severity: error
Presets: essential, happy-path, nuxt, ecosystem, opinionated
Automatic fix: None; review the suggested change
Applies to: Vue SFC templates and blocks, with script context where the rule requires it
Options: No rule-specific options. Severity and preset selection are configurable.
Configuration (Vite+)
import { defineConfig } from "@vizejs/vite-plugin/vite-plus";
export default defineConfig({
lint: {
vize: {
"preset": "incremental",
"rules": {
"html/id-duplication": "error"
}
},
},
});
vp run lint
Bad
Both the input and help paragraph declare id="email", so the label target is ambiguous.
<template>
<label for="email">Email</label>
<input id="email" />
<p id="email">Required</p>
</template>
Good
The input keeps email; the help paragraph uses email-help, and aria-describedby refers to that distinct ID.
<template>
<label for="email">Email</label>
<input id="email" aria-describedby="email-help" />
<p id="email-help">Required</p>
</template>
Good avoids this rule's finding under the configuration above; other rules may still report diagnostics.
html/no-consecutive-br
Disallow consecutive <br> elements
Default severity: warning
Presets: happy-path, nuxt, ecosystem, opinionated
Automatic fix: None; review the suggested change
Applies to: Vue SFC templates and blocks, with script context where the rule requires it
Options: No rule-specific options. Severity and preset selection are configurable.
Configuration (Vite+)
import { defineConfig } from "@vizejs/vite-plugin/vite-plus";
export default defineConfig({
lint: {
vize: {
"preset": "incremental",
"rules": {
"html/no-consecutive-br": "warn"
}
},
},
});
vp run lint
Bad
Two consecutive break elements create spacing between blocks inside a single paragraph.
<template>
<p>First line<br /><br />Second block</p>
</template>
Good
Separate paragraphs express the two content blocks without repeated break elements.
<template>
<p>First line</p>
<p>Second block</p>
</template>
Good avoids this rule's finding under the configuration above; other rules may still report diagnostics.
html/no-dupe-style-properties
Disallow duplicate properties in inline style attributes
Default severity: warning
Presets: nuxt, opinionated
Automatic fix: None; review the suggested change
Applies to: Vue SFC templates and blocks, with script context where the rule requires it
Options: No rule-specific options. Severity and preset selection are configurable.
Configuration (Vite+)
import { defineConfig } from "@vizejs/vite-plugin/vite-plus";
export default defineConfig({
lint: {
vize: {
"preset": "incremental",
"rules": {
"html/no-dupe-style-properties": "warn"
}
},
},
});
vp run lint
Bad
Each static style repeats one property; margin and MARGIN also count as the same property.
<template>
<div style="color: red; color: blue">text</div>
<div style="margin: 0; MARGIN: 1px">text</div>
</template>
Good
The static style uses distinct color and background properties. Dynamic style bindings are outside this static-attribute check.
<template>
<div style="color: red; background: blue">text</div>
<div :style="{ color: a, color: b }">text</div>
</template>
Good avoids this rule's finding under the configuration above; other rules may still report diagnostics.
html/no-duplicate-class
Disallow duplicate class names in a static class attribute
Default severity: warning
Presets: nuxt, opinionated
Automatic fix: None; review the suggested change
Applies to: Vue SFC templates and blocks, with script context where the rule requires it
Options: No rule-specific options. Severity and preset selection are configurable.
Configuration (Vite+)
import { defineConfig } from "@vizejs/vite-plugin/vite-plus";
export default defineConfig({
lint: {
vize: {
"preset": "incremental",
"rules": {
"html/no-duplicate-class": "warn"
}
},
},
});
vp run lint
Bad
The static class list repeats the btn token.
<template>
<div class="btn btn primary">click</div>
</template>
Good
The class list keeps one btn token and the distinct primary token.
<template>
<div class="btn primary">click</div>
</template>
Good avoids this rule's finding under the configuration above; other rules may still report diagnostics.
html/no-duplicate-dt
Disallow duplicate <dt> names in <dl>
Default severity: warning
Presets: happy-path, nuxt, ecosystem, opinionated
Automatic fix: None; review the suggested change
Applies to: Vue SFC templates and blocks, with script context where the rule requires it
Options: No rule-specific options. Severity and preset selection are configurable.
Configuration (Vite+)
import { defineConfig } from "@vizejs/vite-plugin/vite-plus";
export default defineConfig({
lint: {
vize: {
"preset": "incremental",
"rules": {
"html/no-duplicate-dt": "warn"
}
},
},
});
vp run lint
Bad
The same definition list repeats the API term for two descriptions.
<template>
<dl>
<dt>API</dt>
<dd>Public interface</dd>
<dt>API</dt>
<dd>Internal service</dd>
</dl>
</template>
Good
One API term is followed by both descriptions, avoiding the repeated term.
<template>
<dl>
<dt>API</dt>
<dd>Public interface</dd>
<dd>Internal service</dd>
</dl>
</template>
Good avoids this rule's finding under the configuration above; other rules may still report diagnostics.
html/no-empty-palpable-content
Disallow empty elements that expect visible content
Default severity: warning
Presets: happy-path, nuxt, ecosystem, opinionated
Automatic fix: None; review the suggested change
Applies to: Vue SFC templates and blocks, with script context where the rule requires it
Options: See typed options and defaults.
Configuration (Vite+)
import { defineConfig } from "@vizejs/vite-plugin/vite-plus";
export default defineConfig({
lint: {
vize: {
"preset": "incremental",
"rules": {
"html/no-empty-palpable-content": "warn"
}
},
},
});
vp run lint
Bad
The paragraph, list item, and table cell all have empty palpable content.
<template>
<p></p>
<li></li>
<td></td>
</template>
Good
Text fills the paragraph, interpolation supplies the list item, and aria-label explicitly names the otherwise empty cell.
<template>
<p>Overview</p>
<li>{{ item.label }}</li>
<td aria-label="No value"></td>
</template>
Good avoids this rule's finding under the configuration above; other rules may still report diagnostics.
html/require-datetime
Require datetime attribute on <time> element
Default severity: warning
Presets: happy-path, nuxt, ecosystem, opinionated
Automatic fix: None; review the suggested change
Applies to: Vue SFC templates and blocks, with script context where the rule requires it
Options: No rule-specific options. Severity and preset selection are configurable.
Configuration (Vite+)
import { defineConfig } from "@vizejs/vite-plugin/vite-plus";
export default defineConfig({
lint: {
vize: {
"preset": "incremental",
"rules": {
"html/require-datetime": "warn"
}
},
},
});
vp run lint
Bad
The time element contains a human-readable date but no machine-readable datetime value.
<template>
<time>May 13, 2026</time>
</template>
Good
datetime="2026-05-13" supplies the corresponding machine-readable date.
<template>
<time datetime="2026-05-13">May 13, 2026</time>
</template>
Good avoids this rule's finding under the configuration above; other rules may still report diagnostics.
type/no-floating-promises
Disallow floating (unhandled) Promises
Default severity: warning
Presets: nuxt, opinionated
Automatic fix: None; review the suggested change
Applies to: Type information in Vue SFC scripts and templates, for the constructs shown below
Options: No rule-specific options. Severity and preset selection are configurable.
Type-aware checks use the native Corsa runtime and the TypeScript project. typeAware alone does not enable an opt-in rule.
Configuration (Vite+)
import { defineConfig } from "@vizejs/vite-plugin/vite-plus";
export default defineConfig({
lint: {
vize: {
"preset": "incremental",
"rules": {
"type/no-floating-promises": "warn"
},
"typeAware": true
},
},
});
vp run lint
Bad
The async save function returns a Promise, but the standalone save() call neither awaits nor returns it and does not explicitly mark intentional disposal.
<script setup lang="ts">
async function save(): Promise<void> {}
save();
</script>
Good
void save() explicitly marks the fire-and-forget intent accepted by this rule. This is an explicit disposal marker, not a rejection handler.
<script setup lang="ts">
async function save(): Promise<void> {}
void save();
</script>
Good avoids this rule's finding under the configuration above; other rules may still report diagnostics.
type/no-reactivity-loss
Disallow plain snapshots of reactive values across assignments and calls
Default severity: warning
Presets: nuxt, opinionated
Automatic fix: None; review the suggested change
Applies to: Type information in Vue SFC scripts and templates, for the constructs shown below
Options: No rule-specific options. Severity and preset selection are configurable.
Type-aware checks use the native Corsa runtime and the TypeScript project. typeAware alone does not enable an opt-in rule.
Configuration (Vite+)
import { defineConfig } from "@vizejs/vite-plugin/vite-plus";
export default defineConfig({
lint: {
vize: {
"preset": "incremental",
"rules": {
"type/no-reactivity-loss": "warn"
},
"typeAware": true
},
},
});
vp run lint
Bad
const count = state.count takes a plain numeric snapshot of the reactive property, so later updates of state.count are not reflected in that binding.
<script setup lang="ts">
import { reactive } from "vue";
const state = reactive({ count: 0 });
const count = state.count;
</script>
Good
toRef(state, "count") keeps count linked to the original reactive property rather than copying its current primitive value.
<script setup lang="ts">
import { reactive, toRef } from "vue";
const state = reactive({ count: 0 });
const count = toRef(state, "count");
</script>
Good avoids this rule's finding under the configuration above; other rules may still report diagnostics.
type/no-unsafe-template-binding
Disallow template bindings that resolve to unsafe types
Default severity: warning
Presets: nuxt, opinionated
Automatic fix: None; review the suggested change
Applies to: Type information in Vue SFC scripts and templates, for the constructs shown below
Options: No rule-specific options. Severity and preset selection are configurable.
Type-aware checks use the native Corsa runtime and the TypeScript project. typeAware alone does not enable an opt-in rule.
Configuration (Vite+)
import { defineConfig } from "@vizejs/vite-plugin/vite-plus";
export default defineConfig({
lint: {
vize: {
"preset": "incremental",
"rules": {
"type/no-unsafe-template-binding": "warn"
},
"typeAware": true
},
},
});
vp run lint
Bad
The interpolated value is explicitly typed as any, so the checker cannot give the template binding a safe concrete type.
<script setup lang="ts">
const value: any = "Hello";
</script>
<template><p>{{ value }}</p></template>
Good
Changing the annotation to string gives the same interpolation a concrete, checkable type without changing the rendered value.
<script setup lang="ts">
const value: string = "Hello";
</script>
<template><p>{{ value }}</p></template>
Good avoids this rule's finding under the configuration above; other rules may still report diagnostics.
type/require-typed-emits
Require type definition for defineEmits
Default severity: warning
Presets: happy-path, nuxt, ecosystem, opinionated
Automatic fix: None; review the suggested change
Applies to: Type information in Vue SFC scripts and templates, for the constructs shown below
Options: No rule-specific options. Severity and preset selection are configurable.
Type-aware checks use the native Corsa runtime and the TypeScript project. typeAware alone does not enable an opt-in rule.
Configuration (Vite+)
import { defineConfig } from "@vizejs/vite-plugin/vite-plus";
export default defineConfig({
lint: {
vize: {
"preset": "incremental",
"rules": {
"type/require-typed-emits": "warn"
},
"typeAware": true
},
},
});
vp run lint
Bad
The array-only defineEmits(["save"]) declares the event name without a typed payload contract.
<script setup lang="ts">
defineEmits(["save"]);
</script>
Good
defineEmits<{ save: [] }>() declares the typed save event with an empty payload tuple, explicitly stating that it takes no payload arguments.
<script setup lang="ts">
defineEmits<{ save: [] }>();
</script>
Good avoids this rule's finding under the configuration above; other rules may still report diagnostics.
type/require-typed-props
Require type definition for defineProps
Default severity: warning
Presets: happy-path, nuxt, ecosystem, opinionated
Automatic fix: None; review the suggested change
Applies to: Type information in Vue SFC scripts and templates, for the constructs shown below
Options: No rule-specific options. Severity and preset selection are configurable.
Type-aware checks use the native Corsa runtime and the TypeScript project. typeAware alone does not enable an opt-in rule.
Configuration (Vite+)
import { defineConfig } from "@vizejs/vite-plugin/vite-plus";
export default defineConfig({
lint: {
vize: {
"preset": "incremental",
"rules": {
"type/require-typed-props": "warn"
},
"typeAware": true
},
},
});
vp run lint
Bad
The array-only defineProps(["title"]) declares title by name without giving it a type.
<script setup lang="ts">
defineProps(["title"]);
</script>
Good
defineProps<{ title: string }>() gives title an explicit string type instead of a name-only runtime declaration.
<script setup lang="ts">
defineProps<{ title: string }>();
</script>
Good avoids this rule's finding under the configuration above; other rules may still report diagnostics.
type/strict-boolean-expressions
Require safe boolean expressions in script and template conditions
Default severity: warning
Presets: none
Automatic fix: None; review the suggested change
Applies to: Type information in Vue SFC scripts and templates, for the constructs shown below
Options: See typed options and defaults.
Enable typeAware and this rule explicitly. The default disallows nullable numbers, while non-null numbers are allowed.
Type-aware checks use the native Corsa runtime and the TypeScript project. typeAware alone does not enable an opt-in rule.
Configuration (Vite+)
import { defineConfig } from "@vizejs/vite-plugin/vite-plus";
export default defineConfig({
lint: {
vize: {
"preset": "incremental",
"rules": {
"type/strict-boolean-expressions": "warn"
},
"typeAware": true
},
},
});
vp run lint
Bad
if (count) relies on the truthiness of a nullable numeric binding instead of an explicit boolean test; it also conflates zero with absence.
<script setup lang="ts">
const count: number | undefined = undefined;
if (count) console.log(count);
</script>
Good
count !== undefined && count > 0 separately tests presence and positivity, producing an explicit boolean condition after narrowing the optional value.
<script setup lang="ts">
const count: number | undefined = undefined;
if (count !== undefined && count > 0) console.log(count);
</script>
Good avoids this rule's finding under the configuration above; other rules may still report diagnostics.
script/no-get-current-instance
Disallow getCurrentInstance() in Vapor mode (returns null)
Default severity: error
Presets: opinionated
Automatic fix: None; review the suggested change
Applies to: Vapor-oriented script checks; explicit enablement also applies the restriction to ordinary scripts
Options: No rule-specific options. Severity and preset selection are configurable.
Configuration (Vite+)
import { defineConfig } from "@vizejs/vite-plugin/vite-plus";
export default defineConfig({
lint: {
vize: {
"preset": "incremental",
"rules": {
"script/no-get-current-instance": "error"
}
},
},
});
vp run lint
Bad
The Vapor-marked setup imports and calls getCurrentInstance, relying on an instance API this rule disallows for Vapor-oriented components.
<script setup lang="ts" vapor>
import { getCurrentInstance } from "vue";
const instance = getCurrentInstance();
</script>
Good
inject("app-config") obtains the explicitly provided configuration without importing or calling getCurrentInstance.
<script setup lang="ts" vapor>
import { inject } from "vue";
const appConfig = inject("app-config");
</script>
Good avoids this rule's finding under the configuration above; other rules may still report diagnostics.
script/no-next-tick
Disallow nextTick() usage in Vapor-oriented components
Default severity: error
Presets: none
Automatic fix: None; review the suggested change
Applies to: Vapor-oriented script checks; explicit enablement also applies the restriction to ordinary scripts
Options: No rule-specific options. Severity and preset selection are configurable.
Configuration (Vite+)
import { defineConfig } from "@vizejs/vite-plugin/vite-plus";
export default defineConfig({
lint: {
vize: {
"preset": "incremental",
"rules": {
"script/no-next-tick": "error"
}
},
},
});
vp run lint
Bad
The Vapor-oriented component imports and awaits nextTick, introducing the DOM-flush scheduling dependency that this migration rule rejects.
<script setup lang="ts" vapor>
import { nextTick } from "vue";
await nextTick();
</script>
Good
The input is obtained through useTemplateRef and focused at onMounted. The explicit mount boundary replaces the example’s nextTick dependency.
<script setup lang="ts" vapor>
import { onMounted, useTemplateRef } from "vue";
const input = useTemplateRef<HTMLInputElement>("input");
onMounted(() => { input.value?.focus(); });
</script>
<template><input ref="input"></template>
Good avoids this rule's finding under the configuration above; other rules may still report diagnostics.
script/no-options-api
Disallow Options API patterns in Vapor mode
Default severity: error
Presets: opinionated
Automatic fix: None; review the suggested change
Applies to: Vapor-oriented script checks; explicit enablement also applies the restriction to ordinary scripts
Options: No rule-specific options. Severity and preset selection are configurable.
Configuration (Vite+)
import { defineConfig } from "@vizejs/vite-plugin/vite-plus";
export default defineConfig({
lint: {
vize: {
"preset": "incremental",
"rules": {
"script/no-options-api": "error"
}
},
},
});
vp run lint
Bad
The default-export object declares Options API data(), a component option form prohibited by this rule.
<script lang="ts">
export default {
data() {
return { count: 0 };
},
};
</script>
Good
The component state becomes a Composition API ref in Vapor <script setup>, removing the Options API object and its data option.
<script setup lang="ts" vapor>
const count = ref(0);
</script>
Good avoids this rule's finding under the configuration above; other rules may still report diagnostics.
vapor/no-inline-template
Disallow deprecated inline-template attribute
Default severity: error
Presets: nuxt, opinionated
Automatic fix: None; review the suggested change
Applies to: Vue SFC templates and blocks, with script context where the rule requires it
Options: No rule-specific options. Severity and preset selection are configurable.
Configuration (Vite+)
import { defineConfig } from "@vizejs/vite-plugin/vite-plus";
export default defineConfig({
lint: {
vize: {
"preset": "incremental",
"rules": {
"vapor/no-inline-template": "error"
}
},
},
});
vp run lint
Bad
LegacyCard uses the inline-template attribute for its child markup.
<template>
<LegacyCard inline-template>
<p>Profile</p>
</LegacyCard>
</template>
Good
The markup is passed through the default slot instead of an inline template.
<template>
<LegacyCard>
<template #default>
<p>Profile</p>
</template>
</LegacyCard>
</template>
Good avoids this rule's finding under the configuration above; other rules may still report diagnostics.
vapor/no-vue-lifecycle-events
Disallow @vue:xxx per-element lifecycle events (not supported in Vapor)
Default severity: error
Presets: happy-path, nuxt, ecosystem, opinionated
Automatic fix: None; review the suggested change
Applies to: Vue SFC templates and blocks, with script context where the rule requires it
Options: No rule-specific options. Severity and preset selection are configurable.
Configuration (Vite+)
import { defineConfig } from "@vizejs/vite-plugin/vite-plus";
export default defineConfig({
lint: {
vize: {
"preset": "incremental",
"rules": {
"vapor/no-vue-lifecycle-events": "error"
}
},
},
});
vp run lint
Bad
The input uses the @vue:mounted template lifecycle event.
<template>
<input @vue:mounted="focusInput" />
</template>
Good
onMounted accesses the named template reference and focuses the input through the supported script lifecycle hook.
<script setup lang="ts" vapor>
const input = useTemplateRef<HTMLInputElement>("input");
onMounted(() => {
input.value?.focus();
});
</script>
<template>
<input ref="input" />
</template>
Good avoids this rule's finding under the configuration above; other rules may still report diagnostics.
vapor/prefer-static-class
Prefer static class over dynamic class binding for string literals
Default severity: warning
Presets: nuxt, opinionated
Automatic fix: Available for supported findings
Applies to: Vue SFC templates and blocks, with script context where the rule requires it
Options: No rule-specific options. Severity and preset selection are configurable.
Configuration (Vite+)
import { defineConfig } from "@vizejs/vite-plugin/vite-plus";
export default defineConfig({
lint: {
vize: {
"preset": "incremental",
"rules": {
"vapor/prefer-static-class": "warn"
}
},
},
});
vp run lint
Bad
The class binding evaluates a constant string even though the class does not change.
<template>
<section :class="'panel panel-primary'">Profile</section>
</template>
Good
A static class attribute expresses the same panel classes without a binding.
<template>
<section class="panel panel-primary">Profile</section>
</template>
Good avoids this rule's finding under the configuration above; other rules may still report diagnostics.
vapor/require-vapor-attribute
Suggest adding vapor attribute to script setup
Default severity: warning
Presets: nuxt, opinionated
Automatic fix: Not implemented for SFC lint
Applies to: Vue SFC templates and blocks, with script context where the rule requires it
Options: No rule-specific options. Severity and preset selection are configurable.
Current support: no-sfc-finding
This rule is a placeholder with an empty callback. Adding vapor selects Vapor compilation; the current linter does not report this catalog ID for its absence.
Configured ID (currently no SFC finding)
import { defineConfig } from "@vizejs/vite-plugin/vite-plus";
export default defineConfig({
lint: {
vize: {
"preset": "incremental",
"rules": {
"vapor/require-vapor-attribute": "warn"
}
},
},
});
vp run lint
Bad
The script setup block lacks the Vapor compilation attribute. This is an intended convention: the current empty rule callback does not diagnose it.
<script setup>
const count = 0;
</script>
<template><p>{{ count }}</p></template>
Good
Adding vapor selects Vapor compilation. It demonstrates the intended repair and does not imply that the current linter emits this catalog rule.
<script setup vapor>
const count = 0;
</script>
<template><p>{{ count }}</p></template>
Good illustrates the intended convention; the current SFC path emits neither side's rule-specific finding.
ecosystem/nuxt-prefer-nuxt-link
Prefer NuxtLink for internal application links
Default severity: warning
Presets: nuxt
Automatic fix: None; review the suggested change
Applies to: Vue SFC templates and blocks, with script context where the rule requires it
Options: No rule-specific options. Severity and preset selection are configurable.
Configuration (Vite+)
import { defineConfig } from "@vizejs/vite-plugin/vite-plus";
export default defineConfig({
lint: {
vize: {
"preset": "incremental",
"rules": {
"ecosystem/nuxt-prefer-nuxt-link": "warn"
}
},
},
});
vp run lint
Bad
The internal settings destination uses a plain anchor in a Nuxt application.
<template>
<a href="/settings">Settings</a>
</template>
Good
NuxtLink handles the same internal destination through the Nuxt router.
<template>
<NuxtLink to="/settings">Settings</NuxtLink>
</template>
Good avoids this rule's finding under the configuration above; other rules may still report diagnostics.
ecosystem/pinia-prefer-store-to-refs
Prefer storeToRefs() when destructuring Pinia stores
Default severity: warning
Presets: ecosystem
Automatic fix: None; review the suggested change
Applies to: JS/TS scripts in Vue SFCs; examples show the relevant Options API or script setup form
Options: No rule-specific options. Severity and preset selection are configurable.
Configuration (Vite+)
import { defineConfig } from "@vizejs/vite-plugin/vite-plus";
export default defineConfig({
lint: {
vize: {
"preset": "incremental",
"rules": {
"ecosystem/pinia-prefer-store-to-refs": "warn"
}
},
},
});
vp run lint
Bad
Destructuring name directly from the store separates the value from its reactive store access.
<script setup lang="ts">
const { name } = useUserStore();
</script>
Good
The store remains intact and storeToRefs creates a reactive reference for name.
<script setup lang="ts">
const store = useUserStore();
const { name } = storeToRefs(store);
</script>
Good avoids this rule's finding under the configuration above; other rules may still report diagnostics.
ecosystem/router-link-require-to
Require a to target on RouterLink and NuxtLink components
Default severity: error
Presets: ecosystem
Automatic fix: None; review the suggested change
Applies to: Vue SFC templates and blocks, with script context where the rule requires it
Options: No rule-specific options. Severity and preset selection are configurable.
A single SFC root link may inherit its target from parent attributes. This example uses a nested link, whose target must be explicit.
Configuration (Vite+)
import { defineConfig } from "@vizejs/vite-plugin/vite-plus";
export default defineConfig({
lint: {
vize: {
"preset": "incremental",
"rules": {
"ecosystem/router-link-require-to": "error"
}
},
},
});
vp run lint
Bad
The nested RouterLink has no to destination; it cannot rely on root attribute fallthrough.
<template>
<nav><RouterLink>Settings</RouterLink></nav>
</template>
Good
to="/settings" explicitly supplies the nested link destination.
<template>
<nav><RouterLink to="/settings">Settings</RouterLink></nav>
</template>
Good avoids this rule's finding under the configuration above; other rules may still report diagnostics.
ecosystem/void-link-require-href
Require href on Void Vue Link components
Default severity: error
Presets: ecosystem
Automatic fix: None; review the suggested change
Applies to: Vue SFC templates and blocks, with script context where the rule requires it
Options: No rule-specific options. Severity and preset selection are configurable.
Configuration (Vite+)
import { defineConfig } from "@vizejs/vite-plugin/vite-plus";
export default defineConfig({
lint: {
vize: {
"preset": "incremental",
"rules": {
"ecosystem/void-link-require-href": "error"
}
},
},
});
vp run lint
Bad
The Link imported from @void/vue omits its href destination.
<script setup>
import { Link } from "@void/vue";
</script>
<template>
<Link>Settings</Link>
</template>
Good
The same imported Link receives the settings destination through href.
<script setup>
import { Link } from "@void/vue";
</script>
<template>
<Link href="/settings">Settings</Link>
</template>
Good avoids this rule's finding under the configuration above; other rules may still report diagnostics.
ecosystem/void-link-valid-method
Validate static Void Vue Link method props
Default severity: warning
Presets: ecosystem
Automatic fix: None; review the suggested change
Applies to: Vue SFC templates and blocks, with script context where the rule requires it
Options: No rule-specific options. Severity and preset selection are configurable.
Configuration (Vite+)
import { defineConfig } from "@vizejs/vite-plugin/vite-plus";
export default defineConfig({
lint: {
vize: {
"preset": "incremental",
"rules": {
"ecosystem/void-link-valid-method": "warn"
}
},
},
});
vp run lint
Bad
The DELETE action requests prefetching, although prefetch is intended for navigation requests.
<script setup>
import { Link } from "@void/vue";
</script>
<template>
<Link href="/posts/1" method="DELETE" prefetch>Delete</Link>
</template>
Good
Removing prefetch keeps the DELETE action without prefetching that non-GET request.
<script setup>
import { Link } from "@void/vue";
</script>
<template>
<Link href="/posts/1" method="DELETE">Delete</Link>
</template>
Good avoids this rule's finding under the configuration above; other rules may still report diagnostics.
ecosystem/vue-i18n-no-missing-key
Report static vue-i18n keys that are absent from local SFC messages
Default severity: warning
Presets: ecosystem
Automatic fix: None; review the suggested change
Applies to: Vue SFC templates and blocks, with script context where the rule requires it
Options: No rule-specific options. Severity and preset selection are configurable.
Configuration (Vite+)
import { defineConfig } from "@vizejs/vite-plugin/vite-plus";
export default defineConfig({
lint: {
vize: {
"preset": "incremental",
"rules": {
"ecosystem/vue-i18n-no-missing-key": "warn"
}
},
},
});
vp run lint
Bad
The template requests auth.missing, but the local English messages declare only auth.login.
<template>{{ $t("auth.missing") }}</template>
<i18n lang="json">
{ "en": { "auth": { "login": "Log in" } } }
</i18n>
Good
The template requests the auth.login key that exists in the local messages.
<template>{{ $t("auth.login") }}</template>
<i18n lang="json">
{ "en": { "auth": { "login": "Log in" } } }
</i18n>
Good avoids this rule's finding under the configuration above; other rules may still report diagnostics.
ecosystem/vue-router-prefer-named-link
Prefer named route objects over static path strings in RouterLink
Default severity: warning
Presets: ecosystem
Automatic fix: None; review the suggested change
Applies to: Vue SFC templates and blocks, with script context where the rule requires it
Options: No rule-specific options. Severity and preset selection are configurable.
Configuration (Vite+)
import { defineConfig } from "@vizejs/vite-plugin/vite-plus";
export default defineConfig({
lint: {
vize: {
"preset": "incremental",
"rules": {
"ecosystem/vue-router-prefer-named-link": "warn"
}
},
},
});
vp run lint
Bad
The RouterLink destination is a literal path rather than a named route.
<template>
<RouterLink to="/settings">Settings</RouterLink>
</template>
Good
The bound route object identifies the destination by its settings route name.
<template>
<RouterLink :to="{ name: 'settings' }">Settings</RouterLink>
</template>
Good avoids this rule's finding under the configuration above; other rules may still report diagnostics.
ecosystem/vue-router-prefer-named-push
Prefer named route objects for Vue Router programmatic navigation
Default severity: warning
Presets: ecosystem
Automatic fix: None; review the suggested change
Applies to: JS/TS scripts in Vue SFCs; examples show the relevant Options API or script setup form
Options: No rule-specific options. Severity and preset selection are configurable.
Configuration (Vite+)
import { defineConfig } from "@vizejs/vite-plugin/vite-plus";
export default defineConfig({
lint: {
vize: {
"preset": "incremental",
"rules": {
"ecosystem/vue-router-prefer-named-push": "warn"
}
},
},
});
vp run lint
Bad
router.push receives a path string that is tied to the current URL spelling.
<script setup lang="ts">
router.push("/settings");
</script>
Good
router.push receives a route object with the stable settings name.
<script setup lang="ts">
router.push({ name: "settings" });
</script>
Good avoids this rule's finding under the configuration above; other rules may still report diagnostics.
ecosystem/vue-test-utils-no-html-snapshot
Avoid snapshotting wrapper.html() in Vue Test Utils tests
Default severity: warning
Presets: ecosystem
Automatic fix: None; review the suggested change
Applies to: JS/TS scripts in Vue SFCs; examples show the relevant Options API or script setup form
Options: No rule-specific options. Severity and preset selection are configurable.
Configuration (Vite+)
import { defineConfig } from "@vizejs/vite-plugin/vite-plus";
export default defineConfig({
lint: {
vize: {
"preset": "incremental",
"rules": {
"ecosystem/vue-test-utils-no-html-snapshot": "warn"
}
},
},
});
vp run lint
Bad
The assertion snapshots the complete wrapper HTML instead of checking the expected behavior.
<script setup lang="ts">
expect(wrapper.html()).toMatchSnapshot();
</script>
Good
The assertion checks that the rendered text contains Saved.
<script setup lang="ts">
expect(wrapper.text()).toContain("Saved");
</script>
Good avoids this rule's finding under the configuration above; other rules may still report diagnostics.
css/no-display-none
Suggest using v-show instead of display: none
Default severity: warning
Presets: opinionated, nuxt
Automatic fix: None; review the suggested change
Applies to: CSS inside SFC style blocks
Options: No rule-specific options. Severity and preset selection are configurable.
Configuration (Vite+)
import { defineConfig } from "@vizejs/vite-plugin/vite-plus";
export default defineConfig({
lint: {
vize: {
"preset": "incremental",
"rules": {
"css/no-display-none": "warn"
}
},
},
});
vp run lint
Bad
The .message declaration hides the local paragraph through CSS rather than a template visibility condition.
<template>
<p class="message">Saved</p>
</template>
<style scoped>
.message {
display: none;
}
</style>
Good
v-show="isSaved" makes the visibility condition explicit on the local paragraph and removes display: none.
<template>
<p v-show="isSaved" class="message">Saved</p>
</template>
Good avoids this rule's finding under the configuration above; other rules may still report diagnostics.
css/no-hardcoded-values
Suggest using CSS variables instead of hardcoded values
Default severity: warning
Presets: opinionated, nuxt
Automatic fix: None; review the suggested change
Applies to: CSS inside SFC style blocks
Options: No rule-specific options. Severity and preset selection are configurable.
Configuration (Vite+)
import { defineConfig } from "@vizejs/vite-plugin/vite-plus";
export default defineConfig({
lint: {
vize: {
"preset": "incremental",
"rules": {
"css/no-hardcoded-values": "warn"
}
},
},
});
vp run lint
Bad
The button embeds spacing numbers and a hexadecimal color directly in the declarations.
<style scoped>
.button {
padding: 12px 16px;
color: #174ea6;
}
</style>
Good
The declarations refer to named spacing and color custom properties, so these values can be maintained as tokens.
<style scoped>
.button {
padding: var(--space-3) var(--space-4);
color: var(--color-action-text);
}
</style>
Good avoids this rule's finding under the configuration above; other rules may still report diagnostics.
css/no-id-selectors
Discourage use of ID selectors in CSS
Default severity: warning
Presets: opinionated, nuxt
Automatic fix: None; review the suggested change
Applies to: CSS inside SFC style blocks
Options: No rule-specific options. Severity and preset selection are configurable.
Configuration (Vite+)
import { defineConfig } from "@vizejs/vite-plugin/vite-plus";
export default defineConfig({
lint: {
vize: {
"preset": "incremental",
"rules": {
"css/no-id-selectors": "warn"
}
},
},
});
vp run lint
Bad
#submit ties the style rule to an ID selector.
<style scoped>
#submit {
font-weight: 600;
}
</style>
Good
The .submit class expresses the reusable styling hook without an ID selector.
<style scoped>
.submit {
font-weight: 600;
}
</style>
Good avoids this rule's finding under the configuration above; other rules may still report diagnostics.
css/no-important
Discourage use of !important in CSS
Default severity: warning
Presets: opinionated, nuxt
Automatic fix: None; review the suggested change
Applies to: CSS inside SFC style blocks
Options: No rule-specific options. Severity and preset selection are configurable.
Configuration (Vite+)
import { defineConfig } from "@vizejs/vite-plugin/vite-plus";
export default defineConfig({
lint: {
vize: {
"preset": "incremental",
"rules": {
"css/no-important": "warn"
}
},
},
});
vp run lint
Bad
The color declaration overrides normal cascade priority with !important.
<style scoped>
.button {
color: red !important;
}
</style>
Good
The color comes from a custom property without an important declaration.
<style scoped>
.button {
color: var(--button-color);
}
</style>
Good avoids this rule's finding under the configuration above; other rules may still report diagnostics.
css/no-utility-classes
Warn against implementing utility classes in component styles
Default severity: warning
Presets: opinionated, nuxt
Automatic fix: None; review the suggested change
Applies to: CSS inside SFC style blocks
Options: No rule-specific options. Severity and preset selection are configurable.
Configuration (Vite+)
import { defineConfig } from "@vizejs/vite-plugin/vite-plus";
export default defineConfig({
lint: {
vize: {
"preset": "incremental",
"rules": {
"css/no-utility-classes": "warn"
}
},
},
});
vp run lint
Bad
The authored selectors use utility-shaped names such as .flex, .mt-4, and .text-center.
<style scoped>
.flex { display: flex; }
.mt-4 { margin-top: 1rem; }
.text-center { text-align: center; }
</style>
Good
A component-specific .my-component selector groups the component styling under one semantic name.
<style scoped>
.my-component { display: flex; margin-top: 1rem; }
</style>
Good avoids this rule's finding under the configuration above; other rules may still report diagnostics.
css/no-v-bind-performance
Warn about performance cost of CSS v-bind()
Default severity: warning
Presets: opinionated, nuxt
Automatic fix: None; review the suggested change
Applies to: CSS inside SFC style blocks
Options: No rule-specific options. Severity and preset selection are configurable.
Configuration (Vite+)
import { defineConfig } from "@vizejs/vite-plugin/vite-plus";
export default defineConfig({
lint: {
vize: {
"preset": "incremental",
"rules": {
"css/no-v-bind-performance": "warn"
}
},
},
});
vp run lint
Bad
The stylesheet reads the changing offset through the SFC CSS v-bind() mechanism.
<style scoped>
.card {
transform: translateX(v-bind(offset));
}
</style>
Good
The element receives the changing transform directly through its style binding.
<template>
<article :style="{ transform: `translateX(${offset}px)` }" class="card" />
</template>
Good avoids this rule's finding under the configuration above; other rules may still report diagnostics.
css/prefer-logical-properties
Recommend CSS logical properties for better i18n support
Default severity: warning
Presets: opinionated, nuxt
Automatic fix: None; review the suggested change
Applies to: CSS inside SFC style blocks
Options: No rule-specific options. Severity and preset selection are configurable.
Configuration (Vite+)
import { defineConfig } from "@vizejs/vite-plugin/vite-plus";
export default defineConfig({
lint: {
vize: {
"preset": "incremental",
"rules": {
"css/prefer-logical-properties": "warn"
}
},
},
});
vp run lint
Bad
margin-left fixes the margin to a physical side regardless of writing direction.
<style scoped>
.panel {
margin-left: 1rem;
}
</style>
Good
margin-inline-start follows the start of the inline direction instead.
<style scoped>
.panel {
margin-inline-start: 1rem;
}
</style>
Good avoids this rule's finding under the configuration above; other rules may still report diagnostics.
css/prefer-nested-selectors
Recommend using CSS nesting for descendant selectors
Default severity: warning
Presets: opinionated, nuxt
Automatic fix: None; review the suggested change
Applies to: CSS inside SFC style blocks
Options: No rule-specific options. Severity and preset selection are configurable.
Configuration (Vite+)
import { defineConfig } from "@vizejs/vite-plugin/vite-plus";
export default defineConfig({
lint: {
vize: {
"preset": "incremental",
"rules": {
"css/prefer-nested-selectors": "warn"
}
},
},
});
vp run lint
Bad
The .card .title descendant selector repeats the parent selector in a flat rule.
<style scoped>
.card .title { color: red; }
</style>
Good
The .title rule is nested inside .card, keeping the parent-child styling relationship together.
<style scoped>
.card { .title { color: red; } }
</style>
Good avoids this rule's finding under the configuration above; other rules may still report diagnostics.
css/prefer-slotted
Recommend ::v-slotted() for styling slot content
Default severity: warning
Presets: opinionated, nuxt
Automatic fix: None; review the suggested change
Applies to: CSS inside SFC style blocks
Options: No rule-specific options. Severity and preset selection are configurable.
Configuration (Vite+)
import { defineConfig } from "@vizejs/vite-plugin/vite-plus";
export default defineConfig({
lint: {
vize: {
"preset": "incremental",
"rules": {
"css/prefer-slotted": "warn"
}
},
},
});
vp run lint
Bad
The scoped stylesheet targets the slot outlet rather than the elements supplied through the slot.
<style scoped>
slot { color: red; }
</style>
Good
:slotted(.label) targets the supplied label element through the scoped slot selector.
<style scoped>
:slotted(.label) { color: red; }
</style>
Good avoids this rule's finding under the configuration above; other rules may still report diagnostics.
css/require-font-display
Require font-display in @font-face rules
Default severity: warning
Presets: opinionated, nuxt
Automatic fix: None; review the suggested change
Applies to: CSS inside SFC style blocks
Options: No rule-specific options. Severity and preset selection are configurable.
Configuration (Vite+)
import { defineConfig } from "@vizejs/vite-plugin/vite-plus";
export default defineConfig({
lint: {
vize: {
"preset": "incremental",
"rules": {
"css/require-font-display": "warn"
}
},
},
});
vp run lint
Bad
The font-face declaration defines the font source but omits its font-display policy.
<style>
@font-face {
font-family: "Inter";
src: url("/inter.woff2") format("woff2");
}
</style>
Good
font-display: swap explicitly selects the fallback-to-font display policy.
<style>
@font-face {
font-family: "Inter";
src: url("/inter.woff2") format("woff2");
font-display: swap;
}
</style>
Good avoids this rule's finding under the configuration above; other rules may still report diagnostics.
musea/no-empty-variant
Disallow empty <variant> blocks
Default severity: warning
Presets: none
Automatic fix: None; review the suggested change
Applies to: Musea .art.vue art, variant, and style blocks
Options: No rule-specific options. Severity and preset selection are configurable.
Configuration (Vite+)
import { defineConfig } from "@vizejs/vite-plugin/vite-plus";
export default defineConfig({
lint: {
vize: {
"preset": "incremental",
"rules": {
"musea/no-empty-variant": "warn"
}
},
},
});
vp run lint
Bad
The named primary variant is empty, so it provides no preview content.
<art title="Button" component="./Button.vue">
<variant name="primary" />
</art>
Good
The variant renders a primary Button with its Save content.
<art title="Button" component="./Button.vue">
<variant name="primary">
<Button tone="primary">Save</Button>
</variant>
</art>
Good avoids this rule's finding under the configuration above; other rules may still report diagnostics.
musea/prefer-design-tokens
Prefer design token CSS variables over hardcoded primitive values
Default severity: warning
Presets: none
Automatic fix: None; review the suggested change
Applies to: Musea .art.vue art, variant, and style blocks
Options: See typed options and defaults.
Requires an .art.vue file and the token inventory shown below. It does not infer a token from an arbitrary color.
Configuration (Vite+)
import { defineConfig } from "@vizejs/vite-plugin/vite-plus";
export default defineConfig({
lint: {
vize: {
"preset": "incremental",
"rules": {
"musea/prefer-design-tokens": "warn"
},
"ruleOptions": {
"musea/prefer-design-tokens": {
"tokens": [
{
"path": "color.primary",
"value": "#3b82f6",
"tier": "semantic"
}
]
}
}
},
},
});
vp run lint
Bad
The art example uses the literal blue color instead of the configured primary design token.
Button.art.vue
<art title="Button" component="Button">
<variant name="Primary"><Button /></variant>
</art>
<style scoped>
.button {
color: #3b82f6;
}
</style>
Good
The style refers to --color-primary, the token configured for this example.
Button.art.vue
<art title="Button" component="Button">
<variant name="Primary"><Button /></variant>
</art>
<style scoped>
.button {
color: var(--color-primary);
}
</style>
Good avoids this rule's finding under the configuration above; other rules may still report diagnostics.
musea/require-component
Require component attribute in <art> block
Default severity: warning
Presets: none
Automatic fix: None; review the suggested change
Applies to: Musea .art.vue art, variant, and style blocks
Options: No rule-specific options. Severity and preset selection are configurable.
Configuration (Vite+)
import { defineConfig } from "@vizejs/vite-plugin/vite-plus";
export default defineConfig({
lint: {
vize: {
"preset": "incremental",
"rules": {
"musea/require-component": "warn"
}
},
},
});
vp run lint
Bad
The art block supplies a title but does not identify the component being previewed.
<art title="Button">
<variant name="primary" />
</art>
Good
defineArt supplies ./Button.vue as the component for the art block.
<script setup>
defineArt("./Button.vue", { title: "Button" });
</script>
<art>
<variant name="primary" />
</art>
Good avoids this rule's finding under the configuration above; other rules may still report diagnostics.
musea/require-title
Require title attribute in <art> block
Default severity: error
Presets: none
Automatic fix: None; review the suggested change
Applies to: Musea .art.vue art, variant, and style blocks
Options: No rule-specific options. Severity and preset selection are configurable.
Configuration (Vite+)
import { defineConfig } from "@vizejs/vite-plugin/vite-plus";
export default defineConfig({
lint: {
vize: {
"preset": "incremental",
"rules": {
"musea/require-title": "error"
}
},
},
});
vp run lint
Bad
The art block identifies Button.vue but supplies no title.
<art component="./Button.vue">
<variant name="primary" />
</art>
Good
The defineArt options supply the Button title for the art block.
<script setup>
defineArt("./Button.vue", { title: "Button" });
</script>
<art>
<variant name="primary" />
</art>
Good avoids this rule's finding under the configuration above; other rules may still report diagnostics.
musea/unique-variant-names
Require unique variant names
Default severity: error
Presets: none
Automatic fix: None; review the suggested change
Applies to: Musea .art.vue art, variant, and style blocks
Options: No rule-specific options. Severity and preset selection are configurable.
Configuration (Vite+)
import { defineConfig } from "@vizejs/vite-plugin/vite-plus";
export default defineConfig({
lint: {
vize: {
"preset": "incremental",
"rules": {
"musea/unique-variant-names": "error"
}
},
},
});
vp run lint
Bad
Two variants in the same art block both use the primary name.
<art title="Button" component="./Button.vue">
<variant name="primary" />
<variant name="primary" />
</art>
Good
The variants have distinct primary and secondary names.
<art title="Button" component="./Button.vue">
<variant name="primary" />
<variant name="secondary" />
</art>
Good avoids this rule's finding under the configuration above; other rules may still report diagnostics.
musea/valid-variant
Require name attribute in <variant> blocks
Default severity: error
Presets: none
Automatic fix: None; review the suggested change
Applies to: Musea .art.vue art, variant, and style blocks
Options: No rule-specific options. Severity and preset selection are configurable.
Configuration (Vite+)
import { defineConfig } from "@vizejs/vite-plugin/vite-plus";
export default defineConfig({
lint: {
vize: {
"preset": "incremental",
"rules": {
"musea/valid-variant": "error"
}
},
},
});
vp run lint
Bad
The variant omits the name needed to identify the preview.
<art title="Button" component="./Button.vue">
<variant />
</art>
Good
The primary name identifies that variant.
<art title="Button" component="./Button.vue">
<variant name="primary" />
</art>
Good avoids this rule's finding under the configuration above; other rules may still report diagnostics.
script/component-options-name-casing
Enforce PascalCase for the component name option
Default severity: error
Presets: none
Automatic fix: None; review the suggested change
Applies to: JS/TS scripts in Vue SFCs; examples show the relevant Options API or script setup form
Options: No rule-specific options. Severity and preset selection are configurable.
Configuration (Vite+)
import { defineConfig } from "@vizejs/vite-plugin/vite-plus";
export default defineConfig({
lint: {
vize: {
"preset": "incremental",
"rules": {
"script/component-options-name-casing": "error"
}
},
},
});
vp run lint
Bad
The component option name: 'my-component' is kebab-case, whereas this rule requires a literal component name in PascalCase.
<script lang="ts">
export default {
name: 'my-component' // kebab-case
}
</script>
Good
MyComponent begins with an uppercase letter and contains only alphanumeric characters, satisfying the name check.
<script lang="ts">
export default {
name: 'MyComponent'
}
</script>
Good avoids this rule's finding under the configuration above; other rules may still report diagnostics.
script/custom-event-name-casing
Enforce camelCase for emitted custom event names
Default severity: error
Presets: none
Automatic fix: None; review the suggested change
Applies to: JS/TS scripts in Vue SFCs; examples show the relevant Options API or script setup form
Options: See typed options and defaults.
Configuration (Vite+)
import { defineConfig } from "@vizejs/vite-plugin/vite-plus";
export default defineConfig({
lint: {
vize: {
"preset": "incremental",
"rules": {
"script/custom-event-name-casing": "error"
}
},
},
});
vp run lint
Bad
The emitted string my-event contains a hyphen and violates the default camelCase event naming policy.
<script setup lang="ts">
const emit = defineEmits(['my-event'])
emit('my-event') // kebab-case → report
</script>
Good
Both the declaration and call use myEvent, preserving agreement between the event name and its emission while satisfying the default casing policy. A configured kebab-case policy has a different expectation.
<script setup lang="ts">
const emit = defineEmits(['myEvent'])
emit('myEvent')
</script>
Good avoids this rule's finding under the configuration above; other rules may still report diagnostics.
script/define-emits-declaration
Enforce the type-based defineEmits<{}>() form over the runtime/array form
Default severity: warning
Presets: none
Automatic fix: None; review the suggested change
Applies to: JS/TS scripts in Vue SFCs; examples show the relevant Options API or script setup form
Options: No rule-specific options. Severity and preset selection are configurable.
Configuration (Vite+)
import { defineConfig } from "@vizejs/vite-plugin/vite-plus";
export default defineConfig({
lint: {
vize: {
"preset": "incremental",
"rules": {
"script/define-emits-declaration": "warn"
}
},
},
});
vp run lint
Bad
defineEmits(["change"]) uses a runtime array declaration; this style rule prefers a type-based declaration.
<script setup lang="ts">
const emit = defineEmits(["change"]);
emit("change", 1);
</script>
Good
defineEmits<{ change: [id: number] }>() moves the event declaration into a type argument and explicitly describes the numeric payload used by emit("change", 1).
<script setup lang="ts">
const emit = defineEmits<{ change: [id: number] }>();
emit("change", 1);
</script>
Good avoids this rule's finding under the configuration above; other rules may still report diagnostics.
script/define-macros-order
Enforce a consistent order of the Vue compiler macros in <script setup>
Default severity: warning
Presets: none
Automatic fix: None; review the suggested change
Applies to: JS/TS scripts in Vue SFCs; examples show the relevant Options API or script setup form
Options: No rule-specific options. Severity and preset selection are configurable.
Configuration (Vite+)
import { defineConfig } from "@vizejs/vite-plugin/vite-plus";
export default defineConfig({
lint: {
vize: {
"preset": "incremental",
"rules": {
"script/define-macros-order": "warn"
}
},
},
});
vp run lint
Bad
defineProps appears before defineModel, although defineModel has the earlier rank in the canonical macro order.
<script setup lang="ts">
// defineProps before defineModel (out of canonical order)
const props = defineProps<{ count: number }>()
const model = defineModel<string>()
</script>
Good
The declarations follow the exact sequence defineOptions, defineModel, defineProps, defineEmits, defineSlots, before unrelated runtime statements.
<script setup lang="ts">
defineOptions({ name: 'MyComponent' })
const model = defineModel<string>()
const props = defineProps<{ count: number }>()
const emit = defineEmits<{ change: [value: string] }>()
defineSlots<{ default(props: {}): any }>()
</script>
Good avoids this rule's finding under the configuration above; other rules may still report diagnostics.
script/define-props-declaration
Enforce type-based defineProps<{ ... }>() over the runtime/object form
Default severity: warning
Presets: none
Automatic fix: None; review the suggested change
Applies to: JS/TS scripts in Vue SFCs; examples show the relevant Options API or script setup form
Options: No rule-specific options. Severity and preset selection are configurable.
Configuration (Vite+)
import { defineConfig } from "@vizejs/vite-plugin/vite-plus";
export default defineConfig({
lint: {
vize: {
"preset": "incremental",
"rules": {
"script/define-props-declaration": "warn"
}
},
},
});
vp run lint
Bad
defineProps({ title: String }) supplies a runtime object, which conflicts with this rule’s preference for type-based props.
<script setup lang="ts">
const props = defineProps({ title: String });
console.log(props.title);
</script>
Good
defineProps<{ title: string }>() declares title in the type argument and retains the props.title access without a runtime declaration argument.
<script setup lang="ts">
const props = defineProps<{ title: string }>();
console.log(props.title);
</script>
Good avoids this rule's finding under the configuration above; other rules may still report diagnostics.
script/define-props-destructuring
Enforce consistent style for defineProps destructuring in <script setup>
Default severity: warning
Presets: none
Automatic fix: None; review the suggested change
Applies to: JS/TS scripts in Vue SFCs; examples show the relevant Options API or script setup form
Options: See typed options and defaults.
Configuration (Vite+)
import { defineConfig } from "@vizejs/vite-plugin/vite-plus";
export default defineConfig({
lint: {
vize: {
"preset": "incremental",
"rules": {
"script/define-props-destructuring": "warn"
}
},
},
});
vp run lint
Bad
defineProps is assigned to the single props binding rather than destructured, contrary to the default destructuring preference.
<script setup lang="ts">
const props = defineProps<{ foo: string }>()
</script>
Good
The object pattern binds foo and bar directly and gives the optional bar a default. This relies on Vue 3.5+ reactive props destructuring; the configurable never mode prefers the opposite form.
<script setup lang="ts">
const { foo, bar = 'default' } = defineProps<{ foo: string; bar?: string }>()
</script>
Good avoids this rule's finding under the configuration above; other rules may still report diagnostics.
script/no-arrow-functions-in-watch
Disallow arrow functions as Options API watch handlers
Default severity: error
Presets: essential, happy-path, ecosystem, nuxt, opinionated
Automatic fix: None; review the suggested change
Applies to: JS/TS scripts in Vue SFCs; examples show the relevant Options API or script setup form
Options: No rule-specific options. Severity and preset selection are configurable.
Configuration (Vite+)
import { defineConfig } from "@vizejs/vite-plugin/vite-plus";
export default defineConfig({
lint: {
vize: {
"preset": "incremental",
"rules": {
"script/no-arrow-functions-in-watch": "error"
}
},
},
});
vp run lint
Bad
The Options API watcher value and the nested other.handler are arrow functions. An arrow captures its surrounding this rather than receiving the component instance.
<script lang="ts">
export default {
watch: {
// `this` is not the component instance inside an arrow function.
value: () => {
this.doSomething()
},
other: {
handler: () => {}
}
}
}
</script>
Good
Both handlers become ordinary methods, allowing Vue to bind this to the component. The deep: true watcher option remains compatible with the object form.
<script lang="ts">
export default {
watch: {
value(newValue, oldValue) {
this.doSomething()
},
other: {
handler(newValue) {},
deep: true
}
}
}
</script>
Good avoids this rule's finding under the configuration above; other rules may still report diagnostics.
script/no-async-in-computed
Disallow async functions in computed properties
Default severity: error
Presets: essential, happy-path, ecosystem, nuxt, opinionated
Automatic fix: None; review the suggested change
Applies to: JS/TS scripts in Vue SFCs; examples show the relevant Options API or script setup form
Options: No rule-specific options. Severity and preset selection are configurable.
Configuration (Vite+)
import { defineConfig } from "@vizejs/vite-plugin/vite-plus";
export default defineConfig({
lint: {
vize: {
"preset": "incremental",
"rules": {
"script/no-async-in-computed": "error"
}
},
},
});
vp run lint
Bad
The computed getter is async, so the fetch produces a Promise instead of a synchronously derived computed value.
<script setup lang="ts">
import { computed } from "vue";
const data = computed(async () => {
const response = await fetch("/api/data");
return response.json();
});
</script>
Good
The asynchronous fetch moves into watch and stores its result in data.value. Cleanup aborts the old request and prevents an inactive callback from writing a stale result; no async computed getter remains.
<script setup lang="ts">
import { ref, watch } from "vue";
const query = ref("");
const data = ref<unknown>(null);
watch(query, async (value, _oldValue, onCleanup) => {
const controller = new AbortController();
let active = true;
onCleanup(() => { active = false; controller.abort(); });
const response = await fetch(`/api/data?q=${encodeURIComponent(value)}`, { signal: controller.signal });
const next: unknown = await response.json();
if (active) data.value = next;
});
</script>
Good avoids this rule's finding under the configuration above; other rules may still report diagnostics.
script/no-boolean-default
Disallow a default on a Boolean prop
Default severity: warning
Presets: none
Automatic fix: None; review the suggested change
Applies to: JS/TS scripts in Vue SFCs; examples show the relevant Options API or script setup form
Options: No rule-specific options. Severity and preset selection are configurable.
Configuration (Vite+)
import { defineConfig } from "@vizejs/vite-plugin/vite-plus";
export default defineConfig({
lint: {
vize: {
"preset": "incremental",
"rules": {
"script/no-boolean-default": "warn"
}
},
},
});
vp run lint
Bad
Both disabled and checked declare a default on a prop whose sole constructor is Boolean; the rule rejects even an explicit false default.
<script lang="ts">
export default {
props: {
// Boolean props already default to false; an explicit default is confusing.
disabled: { type: Boolean, default: true },
checked: { type: Boolean, default: false }
}
}
</script>
Good
The Boolean-only props omit default, using Vue’s implicit false value. The [Boolean, String] union and the Number prop illustrate that this check is limited to the sole Boolean constructor.
<script lang="ts">
export default {
props: {
// No explicit default: defaults to false.
disabled: { type: Boolean },
disabled2: Boolean,
// Union type may legitimately need a default.
value: { type: [Boolean, String], default: '' },
// Non-Boolean prop.
count: { type: Number, default: 0 }
}
}
</script>
Good avoids this rule's finding under the configuration above; other rules may still report diagnostics.
script/no-deep-destructure-in-props
Disallow deeply nested destructuring in defineProps
Default severity: warning
Presets: none
Automatic fix: None; review the suggested change
Applies to: JS/TS scripts in Vue SFCs; examples show the relevant Options API or script setup form
Options: No rule-specific options. Severity and preset selection are configurable.
Configuration (Vite+)
import { defineConfig } from "@vizejs/vite-plugin/vite-plus";
export default defineConfig({
lint: {
vize: {
"preset": "incremental",
"rules": {
"script/no-deep-destructure-in-props": "warn"
}
},
},
});
vp run lint
Bad
The binding pattern descends through user to destructure name, exceeding the default shallow props-destructuring depth.
<script setup lang="ts">
const { user: { name } } = defineProps<{ user: { name: string } }>();
</script>
Good
The props object remains intact, and a computed getter reads props.user.name. The nested access stays explicit without a deeply nested binding pattern.
<script setup lang="ts">
import { computed } from "vue";
const props = defineProps<{ user: { name: string } }>();
const userName = computed(() => props.user.name);
</script>
Good avoids this rule's finding under the configuration above; other rules may still report diagnostics.
script/no-deprecated-data-object-declaration
Disallow an object literal as the component data option (Vue 3 requires a function)
Default severity: error
Presets: none
Automatic fix: None; review the suggested change
Applies to: JS/TS scripts in Vue SFCs; examples show the relevant Options API or script setup form
Options: No rule-specific options. Severity and preset selection are configurable.
Configuration (Vite+)
import { defineConfig } from "@vizejs/vite-plugin/vite-plus";
export default defineConfig({
lint: {
vize: {
"preset": "incremental",
"rules": {
"script/no-deprecated-data-object-declaration": "error"
}
},
},
});
vp run lint
Bad
The Options API data option is an object literal, a Vue 2 form that Vue 3 no longer accepts.
<script lang="ts">
export default {
// `data` must be a function in Vue 3, not an object literal.
data: {
count: 0
}
}
</script>
Good
data() returns a new { count: 0 } object, providing the function-based data declaration required by Vue 3.
<script lang="ts">
export default {
data() {
return { count: 0 }
}
}
</script>
Good avoids this rule's finding under the configuration above; other rules may still report diagnostics.
script/no-deprecated-destroyed-lifecycle
Disallow deprecated destroyed and beforeDestroy lifecycle hooks
Default severity: error
Presets: none
Automatic fix: Available for supported findings
Applies to: JS/TS scripts in Vue SFCs; examples show the relevant Options API or script setup form
Options: No rule-specific options. Severity and preset selection are configurable.
Configuration (Vite+)
import { defineConfig } from "@vizejs/vite-plugin/vite-plus";
export default defineConfig({
lint: {
vize: {
"preset": "incremental",
"rules": {
"script/no-deprecated-destroyed-lifecycle": "error"
}
},
},
});
vp run lint
Bad
beforeDestroy is the removed Vue 2 lifecycle option used for the timer cleanup.
<script lang="ts">
export default { beforeDestroy() { clearTimeout(this.timer); } };
</script>
Good
Renaming the hook to beforeUnmount preserves the cleanup body under its Vue 3 lifecycle name.
<script lang="ts">
export default { beforeUnmount() { clearTimeout(this.timer); } };
</script>
Good avoids this rule's finding under the configuration above; other rules may still report diagnostics.
script/no-deprecated-dollar-listeners-api
Disallow the $listeners instance property removed in Vue 3 (merged into $attrs)
Default severity: error
Presets: none
Automatic fix: None; review the suggested change
Applies to: JS/TS scripts in Vue SFCs; examples show the relevant Options API or script setup form
Options: No rule-specific options. Severity and preset selection are configurable.
Configuration (Vite+)
import { defineConfig } from "@vizejs/vite-plugin/vite-plus";
export default defineConfig({
lint: {
vize: {
"preset": "incremental",
"rules": {
"script/no-deprecated-dollar-listeners-api": "error"
}
},
},
});
vp run lint
Bad
The member reads and the bare argument reference all use $listeners, which Vue 3 removed after merging listeners into attributes.
<script setup lang="ts">
const handlers = this.$listeners
const forwarded = ctx.$listeners
emit('input', $listeners)
</script>
Good
The reads move to this.$attrs and setup-context ctx.attrs. These replace the removed listener surface; the illustrated receivers must exist in the surrounding component context.
<script setup lang="ts">
const handlers = this.$attrs
const forwarded = ctx.attrs
</script>
Good avoids this rule's finding under the configuration above; other rules may still report diagnostics.
script/no-deprecated-dollar-scopedslots-api
Disallow the $scopedSlots instance property removed in Vue 3 (use $slots)
Default severity: error
Presets: none
Automatic fix: None; review the suggested change
Applies to: JS/TS scripts in Vue SFCs; examples show the relevant Options API or script setup form
Options: No rule-specific options. Severity and preset selection are configurable.
Configuration (Vite+)
import { defineConfig } from "@vizejs/vite-plugin/vite-plus";
export default defineConfig({
lint: {
vize: {
"preset": "incremental",
"rules": {
"script/no-deprecated-dollar-scopedslots-api": "error"
}
},
},
});
vp run lint
Bad
this.$scopedSlots, ctx.$scopedSlots, and the bare $scopedSlots reference use the Vue 2 scoped-slot API removed in Vue 3.
<script setup lang="ts">
const header = this.$scopedSlots.header
const footer = ctx.$scopedSlots.footer
render($scopedSlots.default)
</script>
Good
Replacing $scopedSlots with $slots uses the unified slot surface. The example removes the deprecated spelling rather than establishing a setup context for the receivers.
<script setup lang="ts">
const header = this.$slots.header
const footer = ctx.$slots.footer
render($slots.default)
</script>
Good avoids this rule's finding under the configuration above; other rules may still report diagnostics.
script/no-deprecated-events-api
Disallow the removed Vue 2 events API ($on / $off / $once)
Default severity: error
Presets: none
Automatic fix: None; review the suggested change
Applies to: JS/TS scripts in Vue SFCs; examples show the relevant Options API or script setup form
Options: No rule-specific options. Severity and preset selection are configurable.
Configuration (Vite+)
import { defineConfig } from "@vizejs/vite-plugin/vite-plus";
export default defineConfig({
lint: {
vize: {
"preset": "incremental",
"rules": {
"script/no-deprecated-events-api": "error"
}
},
},
});
vp run lint
Bad
The $on, $once, and $off calls use the instance event-bus methods removed in Vue 3.
<script setup lang="ts">
this.$on('event', handler)
this.$once('event', handler)
this.$off('event', handler)
emitter.$off('event')
</script>
Good
$emit remains valid, while event-bus subscription moves to the external emitter’s on method. The repair separates parent-directed emission from an external event bus.
<script setup lang="ts">
// $emit is still valid in Vue 3
this.$emit('event', payload)
// Use an external emitter instead
import mitt from 'mitt'
const emitter = mitt()
emitter.on('event', handler)
</script>
Good avoids this rule's finding under the configuration above; other rules may still report diagnostics.
script/no-deprecated-props-default-this
Disallow this inside a prop default/validator function (removed in Vue 3)
Default severity: error
Presets: none
Automatic fix: None; review the suggested change
Applies to: JS/TS scripts in Vue SFCs; examples show the relevant Options API or script setup form
Options: No rule-specific options. Severity and preset selection are configurable.
Configuration (Vite+)
import { defineConfig } from "@vizejs/vite-plugin/vite-plus";
export default defineConfig({
lint: {
vize: {
"preset": "incremental",
"rules": {
"script/no-deprecated-props-default-this": "error"
}
},
},
});
vp run lint
Bad
The prop default and validator read this, but those functions cannot rely on the component instance in Vue 3.
<script lang="ts">
export default {
props: {
size: {
type: Number,
// `this` is not the component instance in Vue 3.
default() {
return this.defaultSize
}
},
value: {
type: Number,
validator() {
return this.value > 0
}
}
}
}
</script>
Good
The default reads props.baseSize from its argument, and the validator tests its value argument. Both stop depending on an unavailable instance receiver.
<script lang="ts">
export default {
props: {
size: {
type: Number,
// Vue 3 passes the raw props as the first argument instead.
default(props) {
return props.baseSize
}
},
value: {
type: Number,
validator(value) {
return value > 0
}
}
}
}
</script>
Good avoids this rule's finding under the configuration above; other rules may still report diagnostics.
script/no-dupe-keys
Disallow duplicate keys across Options API props/data/computed/methods/setup/inject
Default severity: error
Presets: essential, happy-path, ecosystem, nuxt, opinionated
Automatic fix: None; review the suggested change
Applies to: JS/TS scripts in Vue SFCs; examples show the relevant Options API or script setup form
Options: No rule-specific options. Severity and preset selection are configurable.
Configuration (Vite+)
import { defineConfig } from "@vizejs/vite-plugin/vite-plus";
export default defineConfig({
lint: {
vize: {
"preset": "incremental",
"rules": {
"script/no-dupe-keys": "error"
}
},
},
});
vp run lint
Bad
foo is declared by both props and data, and bar by both computed and methods. Those declarations compete for the same component instance keys.
<script lang="ts">
export default {
props: ['foo'],
data() {
return { foo: 1 } // duplicate of prop `foo`
},
computed: {
bar() { return 2 }
},
methods: {
bar() {} // duplicate of computed `bar`
}
}
</script>
Good
The prop, data, and computed declarations use distinct names (foo, bar, and baz), eliminating both cross-option collisions.
<script lang="ts">
export default {
props: ['foo'],
data() {
return { bar: 1 }
},
computed: {
baz() { return 2 }
}
}
</script>
Good avoids this rule's finding under the configuration above; other rules may still report diagnostics.
script/no-duplicate-attr-inheritance
Flag a component that applies its fallthrough attributes twice
Default severity: warning
Presets: happy-path, ecosystem, nuxt, opinionated
Automatic fix: None; review the suggested change
Applies to: JS/TS scripts in Vue SFCs; examples show the relevant Options API or script setup form
Options: No rule-specific options. Severity and preset selection are configurable.
Configuration (Vite+)
import { defineConfig } from "@vizejs/vite-plugin/vite-plus";
export default defineConfig({
lint: {
vize: {
"preset": "incremental",
"rules": {
"script/no-duplicate-attr-inheritance": "warn"
}
},
},
});
vp run lint
Bad
The explicit inheritAttrs: true values restate Vue’s default. This rule reports that redundant literal even when no root $attrs spread is shown.
<script lang="ts">
defineOptions({ inheritAttrs: true })
export default { inheritAttrs: true }
</script>
Good
inheritAttrs: false expresses a real opt-out, while the empty options object leaves default inheritance implicit. Neither restates the redundant true value.
<script lang="ts">
defineOptions({ inheritAttrs: false }) // intentional opt-out
export default {} // default inheritance, unstated
</script>
Good avoids this rule's finding under the configuration above; other rules may still report diagnostics.
script/no-export-in-script-setup
Disallow export statements inside <script setup>
Default severity: error
Presets: none
Automatic fix: None; review the suggested change
Applies to: JS/TS scripts in Vue SFCs; examples show the relevant Options API or script setup form
Options: No rule-specific options. Severity and preset selection are configurable.
Configuration (Vite+)
import { defineConfig } from "@vizejs/vite-plugin/vite-plus";
export default defineConfig({
lint: {
vize: {
"preset": "incremental",
"rules": {
"script/no-export-in-script-setup": "error"
}
},
},
});
vp run lint
Bad
export const count attempts to expose a module export from <script setup>, where runtime exports are prohibited.
<script setup lang="ts">
export const count = 1;
</script>
Good
Removing export keeps count as a setup binding rather than a module export.
<script setup lang="ts">
const count = 1;
</script>
Good avoids this rule's finding under the configuration above; other rules may still report diagnostics.
script/no-import-compiler-macros
Disallow importing Vue compiler macros that are auto-imported
Default severity: error
Presets: essential, happy-path, ecosystem, nuxt, opinionated
Automatic fix: None; review the suggested change
Applies to: JS/TS scripts in Vue SFCs; examples show the relevant Options API or script setup form
Options: No rule-specific options. Severity and preset selection are configurable.
Configuration (Vite+)
import { defineConfig } from "@vizejs/vite-plugin/vite-plus";
export default defineConfig({
lint: {
vize: {
"preset": "incremental",
"rules": {
"script/no-import-compiler-macros": "error"
}
},
},
});
vp run lint
Bad
The vue import includes defineProps and defineEmits, although these are compiler macros available directly in <script setup>.
<script setup lang="ts">
import { defineProps, defineEmits } from "vue";
const props = defineProps<{ title: string }>();
const emit = defineEmits<{ save: [id: number] }>();
</script>
Good
Removing the macro imports leaves both typed macro calls intact; no runtime import is needed for either declaration.
<script setup lang="ts">
const props = defineProps<{ title: string }>();
const emit = defineEmits<{ save: [id: number] }>();
</script>
Good avoids this rule's finding under the configuration above; other rules may still report diagnostics.
script/no-internal-imports
Disallow importing from Vue internal modules
Default severity: error
Presets: essential, happy-path, ecosystem, nuxt, opinionated
Automatic fix: None; review the suggested change
Applies to: JS/TS scripts in Vue SFCs; examples show the relevant Options API or script setup form
Options: No rule-specific options. Severity and preset selection are configurable.
Configuration (Vite+)
import { defineConfig } from "@vizejs/vite-plugin/vite-plus";
export default defineConfig({
lint: {
vize: {
"preset": "incremental",
"rules": {
"script/no-internal-imports": "error"
}
},
},
});
vp run lint
Bad
Both imports address internal dist files rather than Vue’s public package entry point, coupling the component to build-file paths.
<script setup lang="ts">
import { foo } from '@vue/runtime-core/dist/runtime-core.esm-bundler'
import { bar } from 'vue/dist/vue.esm-bundler'
</script>
Good
Importing the required helpers from vue removes the dependency on internal distribution file locations.
<script setup lang="ts">
import { ref, computed } from 'vue'
</script>
Good avoids this rule's finding under the configuration above; other rules may still report diagnostics.
script/no-multiple-slot-args
Disallow passing more than one argument to a scoped-slot function call
Default severity: warning
Presets: happy-path, ecosystem, nuxt, opinionated
Automatic fix: None; review the suggested change
Applies to: JS/TS scripts in Vue SFCs; examples show the relevant Options API or script setup form
Options: No rule-specific options. Severity and preset selection are configurable.
Configuration (Vite+)
import { defineConfig } from "@vizejs/vite-plugin/vite-plus";
export default defineConfig({
lint: {
vize: {
"preset": "incremental",
"rules": {
"script/no-multiple-slot-args": "warn"
}
},
},
});
vp run lint
Bad
The slot calls pass multiple positional arguments or spread an unknown argument list. Vue slots receive one props object, not a positional parameter list.
<script setup lang="ts">
slots.default(foo, bar)
$slots.header(a, b)
this.$scopedSlots.item(x, y)
useSlots().default(a, b)
slots.default(...args)
</script>
Good
{ foo, bar } combines the data into one argument; slotProps and the argument-free call also stay within the supported slot-call shape.
<script setup lang="ts">
slots.default({ foo, bar })
slots.default(slotProps)
slots.default()
</script>
Good avoids this rule's finding under the configuration above; other rules may still report diagnostics.
script/no-potential-component-option-typo
Flag likely typos in Options API component option names
Default severity: error
Presets: essential, happy-path, ecosystem, nuxt, opinionated
Automatic fix: None; review the suggested change
Applies to: JS/TS scripts in Vue SFCs; examples show the relevant Options API or script setup form
Options: No rule-specific options. Severity and preset selection are configurable.
Configuration (Vite+)
import { defineConfig } from "@vizejs/vite-plugin/vite-plus";
export default defineConfig({
lint: {
vize: {
"preset": "incremental",
"rules": {
"script/no-potential-component-option-typo": "error"
}
},
},
});
vp run lint
Bad
The option is spelled method, one edit away from the recognized methods option; Vue would not treat it as the intended methods declaration.
<script lang="ts">
export default { method: { save() {} } };
</script>
Good
Changing the key to methods places save() under the recognized component option.
<script lang="ts">
export default { methods: { save() {} } };
</script>
Good avoids this rule's finding under the configuration above; other rules may still report diagnostics.
script/no-reactive-destructure
Disallow destructuring reactive objects which loses reactivity
Default severity: warning
Presets: none
Automatic fix: None; review the suggested change
Applies to: JS/TS scripts in Vue SFCs; examples show the relevant Options API or script setup form
Options: No rule-specific options. Severity and preset selection are configurable.
Configuration (Vite+)
import { defineConfig } from "@vizejs/vite-plugin/vite-plus";
export default defineConfig({
lint: {
vize: {
"preset": "incremental",
"rules": {
"script/no-reactive-destructure": "warn"
}
},
},
});
vp run lint
Bad
const { count, name } = state copies primitive properties out of the reactive object, losing their connection to subsequent property changes.
<script setup lang="ts">
import { reactive } from "vue";
const state = reactive({ count: 0, name: "Ada" });
const { count, name } = state;
</script>
Good
Destructuring toRefs(state) creates refs for count and name, keeping each binding linked to the original reactive property.
<script setup lang="ts">
import { reactive, toRefs } from "vue";
const state = reactive({ count: 0, name: "Ada" });
const { count, name } = toRefs(state);
</script>
Good avoids this rule's finding under the configuration above; other rules may still report diagnostics.
script/no-ref-as-operand
Require ref-bound variables to be accessed via .value when used as an operand
Default severity: error
Presets: essential, happy-path, ecosystem, nuxt, opinionated
Automatic fix: None; review the suggested change
Applies to: JS/TS scripts in Vue SFCs; examples show the relevant Options API or script setup form
Options: No rule-specific options. Severity and preset selection are configurable.
Configuration (Vite+)
import { defineConfig } from "@vizejs/vite-plugin/vite-plus";
export default defineConfig({
lint: {
vize: {
"preset": "incremental",
"rules": {
"script/no-ref-as-operand": "error"
}
},
},
});
vp run lint
Bad
count + 1 uses the ref object itself as the arithmetic operand instead of the number it wraps.
<script setup lang="ts">
import { ref } from "vue";
const count = ref(0);
const next = count + 1;
</script>
Good
count.value + 1 reads the wrapped number before adding one; script arithmetic requires this explicit ref access.
<script setup lang="ts">
import { ref } from "vue";
const count = ref(0);
const next = count.value + 1;
</script>
Good avoids this rule's finding under the configuration above; other rules may still report diagnostics.
script/no-required-prop-with-default
Disallow a prop that is both required: true and has a default
Default severity: error
Presets: essential, happy-path, ecosystem, nuxt, opinionated
Automatic fix: None; review the suggested change
Applies to: JS/TS scripts in Vue SFCs; examples show the relevant Options API or script setup form
Options: No rule-specific options. Severity and preset selection are configurable.
Configuration (Vite+)
import { defineConfig } from "@vizejs/vite-plugin/vite-plus";
export default defineConfig({
lint: {
vize: {
"preset": "incremental",
"rules": {
"script/no-required-prop-with-default": "error"
}
},
},
});
vp run lint
Bad
title is both required and given the fallback "Untitled", combining a required-input contract with a default intended for missing input.
<script lang="ts">
export default { props: { title: { type: String, required: true, default: "Untitled" } } };
</script>
Good
Removing required: true makes title optional and leaves "Untitled" as its coherent fallback.
<script lang="ts">
export default { props: { title: { type: String, default: "Untitled" } } };
</script>
Good avoids this rule's finding under the configuration above; other rules may still report diagnostics.
script/no-reserved-identifiers
Disallow using Vue compiler reserved identifiers
Default severity: error
Presets: essential, happy-path, ecosystem, nuxt, opinionated
Automatic fix: None; review the suggested change
Applies to: JS/TS scripts in Vue SFCs; examples show the relevant Options API or script setup form
Options: No rule-specific options. Severity and preset selection are configurable.
Configuration (Vite+)
import { defineConfig } from "@vizejs/vite-plugin/vite-plus";
export default defineConfig({
lint: {
vize: {
"preset": "incremental",
"rules": {
"script/no-reserved-identifiers": "error"
}
},
},
});
vp run lint
Bad
The bindings __props, __emit, and __sfc__ use identifiers reserved for generated Vue compiler code.
<script setup lang="ts">
const __props = { name: "Ada" };
const __emit = () => {};
const __sfc__ = {};
</script>
Good
The ordinary names props, emit, and componentData avoid those generated identifiers while retaining props and emits declarations.
<script setup lang="ts">
const props = defineProps<{ name: string }>();
const emit = defineEmits<{ save: [] }>();
const componentData = {};
</script>
Good avoids this rule's finding under the configuration above; other rules may still report diagnostics.
script/no-reserved-keys
Disallow Vue-reserved names as Options API props/data/computed/methods/setup/inject keys
Default severity: error
Presets: essential, happy-path, ecosystem, nuxt, opinionated
Automatic fix: None; review the suggested change
Applies to: JS/TS scripts in Vue SFCs; examples show the relevant Options API or script setup form
Options: No rule-specific options. Severity and preset selection are configurable.
Configuration (Vite+)
import { defineConfig } from "@vizejs/vite-plugin/vite-plus";
export default defineConfig({
lint: {
vize: {
"preset": "incremental",
"rules": {
"script/no-reserved-keys": "error"
}
},
},
});
vp run lint
Bad
The returned data key $el collides with Vue’s built-in component-instance property and also uses a reserved $ prefix.
<script lang="ts">
export default { data() { return { $el: "custom" }; } };
</script>
Good
Renaming the application data to elementLabel avoids the built-in instance surface and reserved prefix.
<script lang="ts">
export default { data() { return { elementLabel: "custom" }; } };
</script>
Good avoids this rule's finding under the configuration above; other rules may still report diagnostics.
script/no-reserved-props
Disallow reserved names in a component's props declaration
Default severity: error
Presets: essential, happy-path, ecosystem, nuxt, opinionated
Automatic fix: None; review the suggested change
Applies to: JS/TS scripts in Vue SFCs; examples show the relevant Options API or script setup form
Options: No rule-specific options. Severity and preset selection are configurable.
Configuration (Vite+)
import { defineConfig } from "@vizejs/vite-plugin/vite-plus";
export default defineConfig({
lint: {
vize: {
"preset": "incremental",
"rules": {
"script/no-reserved-props": "error"
}
},
},
});
vp run lint
Bad
The object-form ref and $foo, plus the array-form key, are reserved prop names. ref and key are framework controls, and $-prefixed names are rejected.
<script lang="ts">
export default {
props: {
ref: String, // reserved
$foo: Number // `$`-prefixed names are reserved
}
}
export default {
props: ['key'] // reserved (array form)
}
</script>
Good
The ordinary prop names name and refValue avoid the reserved names in both spelling and prefix.
<script lang="ts">
export default {
props: {
name: String,
refValue: Number
}
}
</script>
Good avoids this rule's finding under the configuration above; other rules may still report diagnostics.
script/no-restricted-globals
Disallow references to runtime-environment globals that must go through a typed wrapper
Default severity: error
Presets: none
Automatic fix: None; review the suggested change
Applies to: JS/TS scripts in Vue SFCs; examples show the relevant Options API or script setup form
Options: See typed options and defaults.
Configuration (Vite+)
import { defineConfig } from "@vizejs/vite-plugin/vite-plus";
export default defineConfig({
lint: {
vize: {
"preset": "incremental",
"rules": {
"script/no-restricted-globals": "error"
}
},
},
});
vp run lint
Bad
The example reads the default restricted globals process, localStorage, and sessionStorage directly, bypassing the project’s explicit config and storage helpers.
<script setup lang="ts">
const flag = process.env.FEATURE_FLAG
const token = localStorage.getItem('auth.token')
sessionStorage.setItem('view.scroll', String(window.scrollY))
</script>
Good
useFeatureFlag, authStorage.read, and viewStorage.write remove those direct restricted-global references. The remaining window.scrollY is not a default restriction of this rule; SSR safety is a separate concern.
<script setup lang="ts">
// Use a typed config helper that distinguishes server vs. client.
const flag = useFeatureFlag('FEATURE_FLAG')
// Use a typed wrapper that scopes keys and handles SSR / disabled storage.
const token = authStorage.read('auth.token')
viewStorage.write('view.scroll', String(window.scrollY))
</script>
Good avoids this rule's finding under the configuration above; other rules may still report diagnostics.
script/no-restricted-members
Disallow project-configured object.property member accesses
Default severity: error
Presets: none
Automatic fix: None; review the suggested change
Applies to: JS/TS scripts in Vue SFCs; examples show the relevant Options API or script setup form
Options: See typed options and defaults.
This example configures window.localStorage. The rule has no default deny list; enabling it alone does not report a member.
Configuration (Vite+)
import { defineConfig } from "@vizejs/vite-plugin/vite-plus";
export default defineConfig({
lint: {
vize: {
"preset": "incremental",
"rules": {
"script/no-restricted-members": "error"
},
"ruleOptions": {
"script/no-restricted-members": {
"members": [
{
"object": "window",
"property": "localStorage"
}
]
}
}
},
},
});
vp run lint
Bad
With { object: "window", property: "localStorage" } configured in ruleOptions, window.localStorage accesses the forbidden object/member pair. This rule has no default forbidden members.
<script setup lang="ts">
const token = window.localStorage.getItem("token");
</script>
Good
authStorage.read("token") delegates the read to the application’s storage helper and no longer accesses the configured window.localStorage member.
<script setup lang="ts">
const token = authStorage.read("token");
</script>
Good avoids this rule's finding under the configuration above; other rules may still report diagnostics.
script/no-side-effects-in-computed-properties
Disallow side effects in Options API computed getters
Default severity: error
Presets: essential, happy-path, ecosystem, nuxt, opinionated
Automatic fix: None; review the suggested change
Applies to: JS/TS scripts in Vue SFCs; examples show the relevant Options API or script setup form
Options: No rule-specific options. Severity and preset selection are configurable.
Configuration (Vite+)
import { defineConfig } from "@vizejs/vite-plugin/vite-plus";
export default defineConfig({
lint: {
vize: {
"preset": "incremental",
"rules": {
"script/no-side-effects-in-computed-properties": "error"
}
},
},
});
vp run lint
Bad
doubled assigns to this.count, and reversed mutates this.items through reverse(). Both getters modify the state they are supposed to derive from.
<script lang="ts">
export default {
data() {
return { count: 0, items: [] }
},
computed: {
doubled() {
this.count = this.count * 2 // side effect: assigns to data
return this.count
},
reversed() {
return this.items.reverse() // side effect: mutates the array
}
}
}
</script>
Good
doubled returns the multiplication without assignment. reversed copies the array before reversing it, so the original component state is unchanged by the getter.
<script lang="ts">
export default {
data() {
return { count: 0, items: [] }
},
computed: {
doubled() {
return this.count * 2
},
reversed() {
return [...this.items].reverse() // operate on a copy
}
}
}
</script>
Good avoids this rule's finding under the configuration above; other rules may still report diagnostics.
script/no-top-level-ref-in-script
Disallow top-level ref/reactive to prevent Cross-Request State Pollution
Default severity: error
Presets: none
Automatic fix: None; review the suggested change
Applies to: JS/TS scripts in Vue SFCs; examples show the relevant Options API or script setup form
Options: No rule-specific options. Severity and preset selection are configurable.
Configuration (Vite+)
import { defineConfig } from "@vizejs/vite-plugin/vite-plus";
export default defineConfig({
lint: {
vize: {
"preset": "incremental",
"rules": {
"script/no-top-level-ref-in-script": "error"
}
},
},
});
vp run lint
Bad
The ordinary <script> initializes count and user at module scope. During SSR, these state objects can be shared across component instances and requests.
<script>
// This state is shared across all requests in SSR!
const count = ref(0)
const user = reactive({ name: '' })
export default {
setup() {
return { count, user }
}
}
</script>
Good
The setup ref is initialized per component instance; the ordinary script keeps only a constant, a state-producing function, and a ref created inside setup(). None creates reactive state at ordinary module scope.
<script setup>
// Script setup creates fresh state per request
const count = ref(0)
</script>
<script>
// Constants are fine
const API_URL = 'https://api.example.com'
// Functions that create state are fine
function createState() {
return reactive({ count: 0 })
}
export default {
setup() {
// Create state inside setup
const count = ref(0)
return { count }
}
}
</script>
Good avoids this rule's finding under the configuration above; other rules may still report diagnostics.
script/no-unstable-nested-components
Disallow component definitions inside setup or render functions
Default severity: warning
Presets: happy-path, ecosystem, nuxt, opinionated
Automatic fix: None; review the suggested change
Applies to: JS/TS scripts in Vue SFCs; examples show the relevant Options API or script setup form
Options: No rule-specific options. Severity and preset selection are configurable.
Configuration (Vite+)
import { defineConfig } from "@vizejs/vite-plugin/vite-plus";
export default defineConfig({
lint: {
vize: {
"preset": "incremental",
"rules": {
"script/no-unstable-nested-components": "warn"
}
},
},
});
vp run lint
Bad
defineComponent runs inside the parent’s setup(), creating a new Child component definition whenever that setup executes.
<script lang="ts">
import { defineComponent } from "vue";
export default { setup() { const Child = defineComponent({ render() { return null; } }); return { Child }; } };
</script>
Good
The Child definition moves to module scope, and setup() returns that existing definition instead of recreating it.
<script lang="ts">
import { defineComponent } from "vue";
const Child = defineComponent({ render() { return null; } });
export default { setup() { return { Child }; } };
</script>
Good avoids this rule's finding under the configuration above; other rules may still report diagnostics.
script/no-unused-emit-declarations
Flag declared events that are never emitted
Default severity: warning
Presets: none
Automatic fix: None; review the suggested change
Applies to: JS/TS scripts in Vue SFCs; examples show the relevant Options API or script setup form
Options: No rule-specific options. Severity and preset selection are configurable.
Configuration (Vite+)
import { defineConfig } from "@vizejs/vite-plugin/vite-plus";
export default defineConfig({
lint: {
vize: {
"preset": "incremental",
"rules": {
"script/no-unused-emit-declarations": "warn"
}
},
},
});
vp run lint
Bad
defineEmits declares both change and unused, but the captured emit function only emits the literal event change.
<script setup lang="ts">
const emit = defineEmits(['change', 'unused'])
emit('change')
// `unused` is never emitted
</script>
Good
Removing unused makes the declared event list match the observed emission. The example uses a captured, unescaped emit binding so this local usage conclusion is available.
<script setup lang="ts">
const emit = defineEmits(['change'])
emit('change')
</script>
Good avoids this rule's finding under the configuration above; other rules may still report diagnostics.
script/no-use-computed-property-like-method
Disallow calling an Options API computed property like a method
Default severity: error
Presets: essential, happy-path, ecosystem, nuxt, opinionated
Automatic fix: None; review the suggested change
Applies to: JS/TS scripts in Vue SFCs; examples show the relevant Options API or script setup form
Options: No rule-specific options. Severity and preset selection are configurable.
Configuration (Vite+)
import { defineConfig } from "@vizejs/vite-plugin/vite-plus";
export default defineConfig({
lint: {
vize: {
"preset": "incremental",
"rules": {
"script/no-use-computed-property-like-method": "error"
}
},
},
});
vp run lint
Bad
this.total() calls the value exposed by the computed getter; the getter returns 3, which is not callable.
<script lang="ts">
export default { computed: { total() { return 3; } }, methods: { log() { console.log(this.total()); } } };
</script>
Good
this.total reads the computed value without call parentheses, so log prints the derived number.
<script lang="ts">
export default { computed: { total() { return 3; } }, methods: { log() { console.log(this.total); } } };
</script>
Good avoids this rule's finding under the configuration above; other rules may still report diagnostics.
script/no-with-defaults
Discourage withDefaults in favor of destructuring defaults (Vue 3.5+)
Default severity: warning
Presets: opinionated
Automatic fix: None; review the suggested change
Applies to: JS/TS scripts in Vue SFCs; examples show the relevant Options API or script setup form
Options: No rule-specific options. Severity and preset selection are configurable.
Configuration (Vite+)
import { defineConfig } from "@vizejs/vite-plugin/vite-plus";
export default defineConfig({
lint: {
vize: {
"preset": "incremental",
"rules": {
"script/no-with-defaults": "warn"
}
},
},
});
vp run lint
Bad
withDefaults wraps the typed props declaration solely to supply count and name defaults, instead of the Vue 3.5+ destructuring-default style preferred here.
<script setup lang="ts">
const props = withDefaults(defineProps<{ count?: number; name?: string }>(), { count: 0, name: "Ada" });
</script>
Good
The destructuring pattern puts count = 0 and name = "Ada" beside their bindings and removes the withDefaults wrapper.
<script setup lang="ts">
const { count = 0, name = "Ada" } = defineProps<{ count?: number; name?: string }>();
</script>
Good avoids this rule's finding under the configuration above; other rules may still report diagnostics.
script/prefer-computed
Prefer computed() for derived reactive state
Default severity: warning
Presets: none
Automatic fix: None; review the suggested change
Applies to: JS/TS scripts in Vue SFCs; examples show the relevant Options API or script setup form
Options: No rule-specific options. Severity and preset selection are configurable.
The watcher must only derive the destination. Editable copies and callbacks with other side effects are allowed.
Configuration (Vite+)
import { defineConfig } from "@vizejs/vite-plugin/vite-plus";
export default defineConfig({
lint: {
vize: {
"preset": "incremental",
"rules": {
"script/prefer-computed": "warn"
}
},
},
});
vp run lint
Bad
The watcher only copies a derivation of count into a second ref, doubled, so the derived state is maintained through manual synchronization.
<script setup lang="ts">
import { ref, watch } from "vue";
const count = ref(0);
const doubled = ref(0);
watch(count, (value) => { doubled.value = value * 2; });
</script>
Good
computed(() => count.value * 2) expresses the derivation directly and removes both the extra writable ref and its synchronization watcher.
<script setup lang="ts">
import { ref, computed } from "vue";
const count = ref(0);
const doubled = computed(() => count.value * 2);
</script>
Good avoids this rule's finding under the configuration above; other rules may still report diagnostics.
script/prefer-define-options
Prefer defineOptions() over a plain <script> that only sets name/inheritAttrs
Default severity: warning
Presets: none
Automatic fix: None; review the suggested change
Applies to: JS/TS scripts in Vue SFCs; examples show the relevant Options API or script setup form
Options: No rule-specific options. Severity and preset selection are configurable.
Configuration (Vite+)
import { defineConfig } from "@vizejs/vite-plugin/vite-plus";
export default defineConfig({
lint: {
vize: {
"preset": "incremental",
"rules": {
"script/prefer-define-options": "warn"
}
},
},
});
vp run lint
Bad
The plain script’s only meaningful statement exports an object containing just name and inheritAttrs; these options can be expressed by defineOptions.
<script lang="ts">
export default { name: 'MyComponent', inheritAttrs: false }
</script>
Good
The shown data() method makes the script carry real Options API logic, so it falls outside this rule’s conservative options-only suggestion. This Good demonstrates an allowed exception; the direct migration would put defineOptions({ name: 'MyComponent', inheritAttrs: false }) in <script setup>.
<script lang="ts">
// Real options logic — keep the plain script.
export default {
name: 'MyComponent',
data() { return { count: 0 } },
}
</script>
Good avoids this rule's finding under the configuration above; other rules may still report diagnostics.
script/prefer-import-from-vue
Prefer importing from 'vue' instead of internal packages
Default severity: warning
Presets: happy-path, ecosystem, nuxt, opinionated
Automatic fix: Available for supported findings
Applies to: JS/TS scripts in Vue SFCs; examples show the relevant Options API or script setup form
Options: No rule-specific options. Severity and preset selection are configurable.
Configuration (Vite+)
import { defineConfig } from "@vizejs/vite-plugin/vite-plus";
export default defineConfig({
lint: {
vize: {
"preset": "incremental",
"rules": {
"script/prefer-import-from-vue": "warn"
}
},
},
});
vp run lint
Bad
ref and h are imported from the internal @vue/runtime-core and @vue/runtime-dom packages rather than the public vue package.
<script setup lang="ts">
import { ref } from '@vue/runtime-core'
import { h } from '@vue/runtime-dom'
</script>
Good
Both helpers are imported together from vue, using the public package entry point instead of either internal package.
<script setup lang="ts">
import { ref, h } from 'vue'
</script>
Good avoids this rule's finding under the configuration above; other rules may still report diagnostics.
script/prefer-ref-over-reactive
Recommend using ref() over reactive() for state management
Default severity: warning
Presets: none
Automatic fix: None; review the suggested change
Applies to: JS/TS scripts in Vue SFCs; examples show the relevant Options API or script setup form
Options: No rule-specific options. Severity and preset selection are configurable.
Configuration (Vite+)
import { defineConfig } from "@vizejs/vite-plugin/vite-plus";
export default defineConfig({
lint: {
vize: {
"preset": "incremental",
"rules": {
"script/prefer-ref-over-reactive": "warn"
}
},
},
});
vp run lint
Bad
The state is created with reactive, contrary to this opinionated rule’s preference for refs. The example illustrates a style preference, not an inherently invalid reactive object.
<script setup lang="ts">
// reactive requires careful handling to avoid losing reactivity
const state = reactive({
count: 0,
name: 'foo'
})
</script>
Good
The examples create both scalar and object state with ref; related fields may also be split into separate refs. This satisfies the preferred state-construction form.
<script setup lang="ts">
// ref is more explicit and safer
const count = ref(0)
const name = ref('foo')
// For objects, ref still works
const user = ref({ name: 'foo', age: 20 })
// Or use multiple refs for related data
const userName = ref('foo')
const userAge = ref(20)
</script>
Good avoids this rule's finding under the configuration above; other rules may still report diagnostics.
script/prefer-use-attrs
Recommend using useAttrs() over context.attrs
Default severity: warning
Presets: none
Automatic fix: None; review the suggested change
Applies to: JS/TS scripts in Vue SFCs; examples show the relevant Options API or script setup form
Options: No rule-specific options. Severity and preset selection are configurable.
Configuration (Vite+)
import { defineConfig } from "@vizejs/vite-plugin/vite-plus";
export default defineConfig({
lint: {
vize: {
"preset": "incremental",
"rules": {
"script/prefer-use-attrs": "warn"
}
},
},
});
vp run lint
Bad
setup obtains attrs by destructuring its context parameter, which this rule asks to replace with the Composition API helper.
<script lang="ts">
export default { setup(_props, { attrs }) { console.log(attrs.class); } };
</script>
Good
useAttrs() supplies attrs inside setup, retaining the attrs.class read without depending on the second setup parameter.
<script lang="ts">
import { useAttrs } from "vue";
export default { setup() { const attrs = useAttrs(); console.log(attrs.class); } };
</script>
Good avoids this rule's finding under the configuration above; other rules may still report diagnostics.
script/prefer-use-id
Recommend using useId() for generating unique IDs (Vue 3.5+)
Default severity: warning
Presets: none
Automatic fix: None; review the suggested change
Applies to: JS/TS scripts in Vue SFCs; examples show the relevant Options API or script setup form
Options: No rule-specific options. Severity and preset selection are configurable.
Configuration (Vite+)
import { defineConfig } from "@vizejs/vite-plugin/vite-plus";
export default defineConfig({
lint: {
vize: {
"preset": "incremental",
"rules": {
"script/prefer-use-id": "warn"
}
},
},
});
vp run lint
Bad
id contains Math.random(), so the generated input/label identifier can differ between server and client rendering. Its ID-named binding is the rule’s recognized generation context.
<script setup lang="ts">
const id = `input-${Math.random()}`;
</script>
<template><label :for="id">Name</label><input :id="id" /></template>
Good
Vue 3.5+ useId() generates the identifier, and both :for and :id continue reading the same binding instead of independently generating random values.
<script setup lang="ts">
import { useId } from "vue";
const id = useId();
</script>
<template><label :for="id">Name</label><input :id="id" /></template>
Good avoids this rule's finding under the configuration above; other rules may still report diagnostics.
script/prefer-use-slots
Recommend using useSlots() over context.slots
Default severity: warning
Presets: none
Automatic fix: None; review the suggested change
Applies to: JS/TS scripts in Vue SFCs; examples show the relevant Options API or script setup form
Options: No rule-specific options. Severity and preset selection are configurable.
Configuration (Vite+)
import { defineConfig } from "@vizejs/vite-plugin/vite-plus";
export default defineConfig({
lint: {
vize: {
"preset": "incremental",
"rules": {
"script/prefer-use-slots": "warn"
}
},
},
});
vp run lint
Bad
setup destructures slots from its context argument, the access form this rule prefers to replace.
<script lang="ts">
import { defineComponent, h } from "vue";
export default defineComponent({
setup(_props, { slots }) { return () => h("div", slots.default?.()); },
});
</script>
Good
useSlots() retrieves the slots inside setup, preserving the render function and its optional default-slot call without a context parameter.
<script lang="ts">
import { defineComponent, h, useSlots } from "vue";
export default defineComponent({
setup() {
const slots = useSlots();
return () => h("div", slots.default?.());
},
});
</script>
Good avoids this rule's finding under the configuration above; other rules may still report diagnostics.
script/prefer-use-template-ref
Recommend useTemplateRef over ref for template references (Vue 3.5+)
Default severity: warning
Presets: none
Automatic fix: None; review the suggested change
Applies to: JS/TS scripts in Vue SFCs; examples show the relevant Options API or script setup form
Options: No rule-specific options. Severity and preset selection are configurable.
Configuration (Vite+)
import { defineConfig } from "@vizejs/vite-plugin/vite-plus";
export default defineConfig({
lint: {
vize: {
"preset": "incremental",
"rules": {
"script/prefer-use-template-ref": "warn"
}
},
},
});
vp run lint
Bad
The nullable input ref is paired with the template’s literal ref="input", identifying it as an element reference rather than ordinary nullable data.
<script setup lang="ts">
import { ref } from 'vue'
const input = ref<HTMLInputElement | null>(null)
</script>
<template>
<input ref="input" />
</template>
Good
Vue 3.5+ useTemplateRef<HTMLInputElement>('input') makes that template reference explicit. The unpaired error = ref(null) remains ordinary data and is intentionally outside this rule.
<script setup lang="ts">
import { ref, useTemplateRef } from 'vue'
// Paired with the template ref below.
const input = useTemplateRef<HTMLInputElement>('input')
// A nullable data ref the template never binds as a ref.
const error = ref(null)
</script>
<template>
<input ref="input" />
<p>{{ error }}</p>
</template>
Good avoids this rule's finding under the configuration above; other rules may still report diagnostics.
script/require-default-prop
Require a default value for every optional, non-Boolean prop
Default severity: error
Presets: none
Automatic fix: None; review the suggested change
Applies to: JS/TS scripts in Vue SFCs; examples show the relevant Options API or script setup form
Options: No rule-specific options. Severity and preset selection are configurable.
Configuration (Vite+)
import { defineConfig } from "@vizejs/vite-plugin/vite-plus";
export default defineConfig({
lint: {
vize: {
"preset": "incremental",
"rules": {
"script/require-default-prop": "error"
}
},
},
});
vp run lint
Bad
name and age are optional non-Boolean runtime props without defaults, leaving their omitted-input values unspecified.
<script lang="ts">
export default {
props: {
// optional, non-Boolean, no default
name: String,
age: { type: Number },
}
}
</script>
Good
name receives default: ''. enabled uses Boolean’s implicit false default, and required id needs no fallback, illustrating both exemptions.
<script lang="ts">
export default {
props: {
name: { type: String, default: '' },
enabled: Boolean, // Boolean defaults to false
id: { type: Number, required: true },
}
}
</script>
Good avoids this rule's finding under the configuration above; other rules may still report diagnostics.
script/require-explicit-emits
Require emitted events to be declared in defineEmits or the emits option
Default severity: warning
Presets: none
Automatic fix: None; review the suggested change
Applies to: JS/TS scripts in Vue SFCs; examples show the relevant Options API or script setup form
Options: No rule-specific options. Severity and preset selection are configurable.
Configuration (Vite+)
import { defineConfig } from "@vizejs/vite-plugin/vite-plus";
export default defineConfig({
lint: {
vize: {
"preset": "incremental",
"rules": {
"script/require-explicit-emits": "warn"
}
},
},
});
vp run lint
Bad
The captured emit function emits save, but defineEmits([]) declares no such event.
<script setup lang="ts">
const emit = defineEmits([]);
emit("save");
</script>
Good
Adding "save" to the declaration makes the emitted literal event part of the component’s explicit event contract.
<script setup lang="ts">
const emit = defineEmits(["save"]);
emit("save");
</script>
Good avoids this rule's finding under the configuration above; other rules may still report diagnostics.
script/require-explicit-slots
Require slots consumed via useSlots() to be explicitly typed with defineSlots<...>()
Default severity: warning
Presets: none
Automatic fix: None; review the suggested change
Applies to: JS/TS scripts in Vue SFCs; examples show the relevant Options API or script setup form
Options: No rule-specific options. Severity and preset selection are configurable.
Configuration (Vite+)
import { defineConfig } from "@vizejs/vite-plugin/vite-plus";
export default defineConfig({
lint: {
vize: {
"preset": "incremental",
"rules": {
"script/require-explicit-slots": "warn"
}
},
},
});
vp run lint
Bad
The typed defineProps<{ id: number }>() establishes TypeScript syntax, but setup uses useSlots() without a defineSlots declaration. The rule therefore finds consumed slots without an explicit slot contract.
<script setup lang="ts">
const props = defineProps<{ id: number }>()
const slots = useSlots()
</script>
Good
defineSlots declares a default slot whose props include msg: string; useSlots() now appears alongside an explicit typed slot contract.
<script setup lang="ts">
defineSlots<{ default(props: { msg: string }): unknown }>()
const slots = useSlots()
</script>
Good avoids this rule's finding under the configuration above; other rules may still report diagnostics.
script/require-function-return-type
Require return type annotations on functions
Default severity: warning
Presets: none
Automatic fix: None; review the suggested change
Applies to: JS/TS scripts in Vue SFCs; examples show the relevant Options API or script setup form
Options: No rule-specific options. Severity and preset selection are configurable.
Configuration (Vite+)
import { defineConfig } from "@vizejs/vite-plugin/vite-plus";
export default defineConfig({
lint: {
vize: {
"preset": "incremental",
"rules": {
"script/require-function-return-type": "warn"
}
},
},
});
vp run lint
Bad
Both add and greet annotate their parameters but omit a return-type annotation; inferred returns do not satisfy this explicit-annotation policy.
<script setup lang="ts">
const add = (a: number, b: number) => {
return a + b
}
function greet(name: string) {
return `Hello, ${name}`
}
</script>
Good
add declares : number, and greet declares : string, making the return contracts explicit without changing either body.
<script setup lang="ts">
const add = (a: number, b: number): number => {
return a + b
}
function greet(name: string): string {
return `Hello, ${name}`
}
</script>
Good avoids this rule's finding under the configuration above; other rules may still report diagnostics.
script/require-prop-type-constructor
Require prop type values to be constructors rather than string literals
Default severity: error
Presets: essential, happy-path, ecosystem, nuxt, opinionated
Automatic fix: None; review the suggested change
Applies to: JS/TS scripts in Vue SFCs; examples show the relevant Options API or script setup form
Options: No rule-specific options. Severity and preset selection are configurable.
Configuration (Vite+)
import { defineConfig } from "@vizejs/vite-plugin/vite-plus";
export default defineConfig({
lint: {
vize: {
"preset": "incremental",
"rules": {
"script/require-prop-type-constructor": "error"
}
},
},
});
vp run lint
Bad
The prop declarations use strings "String" and "Number" as runtime types, including inside the constructor array. Those strings are not constructor functions.
<script lang="ts">
export default {
props: {
// The type should be the `String` constructor, not the string "String".
name: "String",
age: { type: "Number" },
id: { type: ["String", "Number"] }
}
}
</script>
Good
The declarations use the actual String and Number identifiers, including the union array [String, Number].
<script lang="ts">
export default {
props: {
name: String,
age: { type: Number },
id: { type: [String, Number] }
}
}
</script>
Good avoids this rule's finding under the configuration above; other rules may still report diagnostics.
script/require-prop-types
Require every prop to declare a type
Default severity: error
Presets: none
Automatic fix: None; review the suggested change
Applies to: JS/TS scripts in Vue SFCs; examples show the relevant Options API or script setup form
Options: No rule-specific options. Severity and preset selection are configurable.
Configuration (Vite+)
import { defineConfig } from "@vizejs/vite-plugin/vite-plus";
export default defineConfig({
lint: {
vize: {
"preset": "incremental",
"rules": {
"script/require-prop-types": "error"
}
},
},
});
vp run lint
Bad
The array entry declares only the name status; the null value and empty descriptor declare no runtime prop type either.
<script lang="ts">
export default {
props: ['status'] // array form: no types
}
export default {
props: {
status: null, // no type
other: {} // empty descriptor: no type
}
}
</script>
Good
status: String supplies a shorthand constructor, and other supplies type: Number inside its descriptor. Both props now carry type declarations.
<script lang="ts">
export default {
props: {
status: String,
other: { type: Number, default: 0 }
}
}
</script>
Good avoids this rule's finding under the configuration above; other rules may still report diagnostics.
script/require-symbol-provide
Recommend using Symbol as injection key for provide/inject
Default severity: warning
Presets: none
Automatic fix: None; review the suggested change
Applies to: JS/TS scripts in Vue SFCs; examples show the relevant Options API or script setup form
Options: No rule-specific options. Severity and preset selection are configurable.
Configuration (Vite+)
import { defineConfig } from "@vizejs/vite-plugin/vite-plus";
export default defineConfig({
lint: {
vize: {
"preset": "incremental",
"rules": {
"script/require-symbol-provide": "warn"
}
},
},
});
vp run lint
Bad
provide and inject use literal string keys such as 'user' and 'theme', which can collide with another provider using the same spelling.
<script setup lang="ts">
// String keys can collide
provide('user', user)
const user = inject('user')
// Magic strings are error-prone
provide('theme', { dark: true })
</script>
Good
The shared UserKey is created with Symbol and annotated as InjectionKey<User>; both calls pass that key instead of a literal string.
<script lang="ts">
// Define injection key with Symbol
export const UserKey: InjectionKey<User> = Symbol('user')
// Provide with Symbol
provide(UserKey, user)
// Inject with Symbol
const user = inject(UserKey)
</script>
Good avoids this rule's finding under the configuration above; other rules may still report diagnostics.
script/require-typed-object-prop
Require an explicit type on a prop whose runtime type is Object or Array
Default severity: warning
Presets: none
Automatic fix: None; review the suggested change
Applies to: JS/TS scripts in Vue SFCs; examples show the relevant Options API or script setup form
Options: No rule-specific options. Severity and preset selection are configurable.
Configuration (Vite+)
import { defineConfig } from "@vizejs/vite-plugin/vite-plus";
export default defineConfig({
lint: {
vize: {
"preset": "incremental",
"rules": {
"script/require-typed-object-prop": "warn"
}
},
},
});
vp run lint
Bad
Bare Object and Array constructors describe only broad runtime categories, so neither user nor the items element shape has an explicit static type.
<script setup lang="ts">
const props = defineProps({ user: Object, items: { type: Array } });
</script>
Good
PropType<User> and PropType<User[]> add the object and element types while retaining the same runtime constructors.
<script setup lang="ts">
import type { PropType } from "vue";
interface User { name: string }
const props = defineProps({
user: Object as PropType<User>,
items: { type: Array as PropType<User[]> },
});
</script>
Good avoids this rule's finding under the configuration above; other rules may still report diagnostics.
script/require-typed-ref
Require an explicit type argument on a ref() initialized with no value, null, or undefined
Default severity: warning
Presets: none
Automatic fix: None; review the suggested change
Applies to: JS/TS scripts in Vue SFCs; examples show the relevant Options API or script setup form
Options: No rule-specific options. Severity and preset selection are configurable.
Configuration (Vite+)
import { defineConfig } from "@vizejs/vite-plugin/vite-plus";
export default defineConfig({
lint: {
vize: {
"preset": "incremental",
"rules": {
"script/require-typed-ref": "warn"
}
},
},
});
vp run lint
Bad
The imported ref calls have neither a type argument nor a useful initial value: no argument, null, and undefined cannot infer the intended future value type.
<script setup lang="ts">
import { ref } from 'vue'
const a = ref() // Ref<undefined>
const b = ref(null) // Ref<null>
const c = ref(undefined) // Ref<undefined>
</script>
Good
Explicit type arguments describe the string and nullable User refs. ref(0) already has a concrete numeric initializer and can rely on inference.
<script setup lang="ts">
import { ref } from 'vue'
const a = ref<string>()
const b = ref<User | null>(null)
const c = ref(0) // inferred Ref<number>
</script>
Good avoids this rule's finding under the configuration above; other rules may still report diagnostics.
script/require-valid-default-prop
Require a prop's default value to be valid for its declared type
Default severity: error
Presets: essential, happy-path, ecosystem, nuxt, opinionated
Automatic fix: None; review the suggested change
Applies to: JS/TS scripts in Vue SFCs; examples show the relevant Options API or script setup form
Options: No rule-specific options. Severity and preset selection are configurable.
Configuration (Vite+)
import { defineConfig } from "@vizejs/vite-plugin/vite-plus";
export default defineConfig({
lint: {
vize: {
"preset": "incremental",
"rules": {
"script/require-valid-default-prop": "error"
}
},
},
});
vp run lint
Bad
The Number and Boolean props receive mismatched scalar defaults, and the Array and Object props use shared literal values instead of factories.
<script lang="ts">
export default {
props: {
count: { type: Number, default: '0' }, // string default for Number
enabled: { type: Boolean, default: 1 }, // non-boolean default for Boolean
items: { type: Array, default: [] }, // literal must be a factory
config: { type: Object, default: {} } // literal must be a factory
}
}
</script>
Good
The scalar defaults become 0 and false; the array and object defaults become functions returning fresh values. The [String, Number] example accepts its string default because it matches one declared type.
<script lang="ts">
export default {
props: {
count: { type: Number, default: 0 },
enabled: { type: Boolean, default: false },
items: { type: Array, default: () => [] },
config: { type: Object, default: () => ({}) },
label: { type: [String, Number], default: '' }
}
}
</script>
Good avoids this rule's finding under the configuration above; other rules may still report diagnostics.
script/return-in-computed-property
Require a return value in every computed getter
Default severity: error
Presets: essential, happy-path, ecosystem, nuxt, opinionated
Automatic fix: None; review the suggested change
Applies to: JS/TS scripts in Vue SFCs; examples show the relevant Options API or script setup form
Options: No rule-specific options. Severity and preset selection are configurable.
Configuration (Vite+)
import { defineConfig } from "@vizejs/vite-plugin/vite-plus";
export default defineConfig({
lint: {
vize: {
"preset": "incremental",
"rules": {
"script/return-in-computed-property": "error"
}
},
},
});
vp run lint
Bad
The block-bodied computed getter evaluates 1 + 2 but never returns it, leaving the computed value undefined.
<script setup lang="ts">
import { computed } from "vue";
const total = computed(() => { 1 + 2; });
</script>
Good
return 1 + 2 turns the expression into the getter’s returned value. The rule looks for a value-returning return in the getter itself, not merely an expression statement.
<script setup lang="ts">
import { computed } from "vue";
const total = computed(() => { return 1 + 2; });
</script>
Good avoids this rule's finding under the configuration above; other rules may still report diagnostics.
script/return-in-emits-validator
Require a return value in every Options API emits validator
Default severity: error
Presets: essential, happy-path, ecosystem, nuxt, opinionated
Automatic fix: None; review the suggested change
Applies to: JS/TS scripts in Vue SFCs; examples show the relevant Options API or script setup form
Options: No rule-specific options. Severity and preset selection are configurable.
Use a block-body arrow for the currently supported SFC filter. The underlying validator also handles method shorthand, but the current SFC prefilter does not reliably dispatch that shape.
Configuration (Vite+)
import { defineConfig } from "@vizejs/vite-plugin/vite-plus";
export default defineConfig({
lint: {
vize: {
"preset": "incremental",
"rules": {
"script/return-in-emits-validator": "error"
}
},
},
});
vp run lint
Bad
The submit validator logs the payload but does not return a validation result, so its block body yields undefined.
<script lang="ts">
export default { emits: { submit: (payload: unknown) => { console.log(payload); } } };
</script>
Good
return payload != null supplies a boolean validation result for the submitted payload instead of ending without a returned value.
<script lang="ts">
export default { emits: { submit: (payload: unknown) => { return payload != null; } } };
</script>
Good avoids this rule's finding under the configuration above; other rules may still report diagnostics.
script/valid-define-emits
Enforce valid defineEmits() usage (no type+runtime args, no local references, single call)
Default severity: error
Presets: essential, happy-path, ecosystem, nuxt, opinionated
Automatic fix: None; review the suggested change
Applies to: JS/TS scripts in Vue SFCs; examples show the relevant Options API or script setup form
Options: No rule-specific options. Severity and preset selection are configurable.
Configuration (Vite+)
import { defineConfig } from "@vizejs/vite-plugin/vite-plus";
export default defineConfig({
lint: {
vize: {
"preset": "incremental",
"rules": {
"script/valid-define-emits": "error"
}
},
},
});
vp run lint
Bad
The same defineEmits call supplies both a type argument and the runtime array ["save"], mixing two mutually exclusive declarations.
<script setup lang="ts">
defineEmits<{ save: [] }>(["save"]);
</script>
Good
Removing the runtime argument leaves a single type-based event declaration for save.
<script setup lang="ts">
defineEmits<{ save: [] }>();
</script>
Good avoids this rule's finding under the configuration above; other rules may still report diagnostics.
script/valid-define-options
Enforce valid defineOptions() usage (single object arg, no props/emits/expose/slots)
Default severity: error
Presets: essential, happy-path, ecosystem, nuxt, opinionated
Automatic fix: None; review the suggested change
Applies to: JS/TS scripts in Vue SFCs; examples show the relevant Options API or script setup form
Options: No rule-specific options. Severity and preset selection are configurable.
Configuration (Vite+)
import { defineConfig } from "@vizejs/vite-plugin/vite-plus";
export default defineConfig({
lint: {
vize: {
"preset": "incremental",
"rules": {
"script/valid-define-options": "error"
}
},
},
});
vp run lint
Bad
The first call puts the dedicated props declaration inside defineOptions; the later calls also repeat the macro and include a non-object argument. These illustrate the forbidden shape and repeated-call constraints.
<script setup lang="ts">
defineOptions({ props: ['foo'] }) // use defineProps instead
defineOptions({ name: 'Foo' })
defineOptions({ name: 'Bar' }) // duplicate call
defineOptions('Foo') // not an object literal
</script>
Good
One defineOptions call receives an object containing only the supported ordinary options name and inheritAttrs.
<script setup lang="ts">
defineOptions({ name: 'Foo', inheritAttrs: false })
</script>
Good avoids this rule's finding under the configuration above; other rules may still report diagnostics.
script/valid-define-props
Enforce valid defineProps() usage (single call, not both type and runtime args, no local references)
Default severity: error
Presets: essential, happy-path, ecosystem, nuxt, opinionated
Automatic fix: None; review the suggested change
Applies to: JS/TS scripts in Vue SFCs; examples show the relevant Options API or script setup form
Options: No rule-specific options. Severity and preset selection are configurable.
Configuration (Vite+)
import { defineConfig } from "@vizejs/vite-plugin/vite-plus";
export default defineConfig({
lint: {
vize: {
"preset": "incremental",
"rules": {
"script/valid-define-props": "error"
}
},
},
});
vp run lint
Bad
The same defineProps call supplies both { title: string } as a type argument and { title: String } as a runtime argument, which the compiler does not permit together.
<script setup lang="ts">
defineProps<{ title: string }>({ title: String });
</script>
Good
Removing the runtime object leaves one type-based declaration for title instead of combining both declaration forms.
<script setup lang="ts">
defineProps<{ title: string }>();
</script>
Good avoids this rule's finding under the configuration above; other rules may still report diagnostics.
script/valid-next-tick
Require the result of a nextTick() call to be awaited, chained, or given a callback
Default severity: warning
Presets: happy-path, ecosystem, nuxt, opinionated
Automatic fix: None; review the suggested change
Applies to: JS/TS scripts in Vue SFCs; examples show the relevant Options API or script setup form
Options: No rule-specific options. Severity and preset selection are configurable.
Configuration (Vite+)
import { defineConfig } from "@vizejs/vite-plugin/vite-plus";
export default defineConfig({
lint: {
vize: {
"preset": "incremental",
"rules": {
"script/valid-next-tick": "warn"
}
},
},
});
vp run lint
Bad
The imported nextTick() is a bare expression with no callback, so its returned Promise is ignored and no work waits for the DOM flush.
<script setup lang="ts">
import { nextTick } from "vue";
nextTick();
</script>
Good
await nextTick() consumes the Promise and explicitly waits for the next DOM update before subsequent setup code continues.
<script setup lang="ts">
import { nextTick } from "vue";
await nextTick();
</script>
Good avoids this rule's finding under the configuration above; other rules may still report diagnostics.
nuxt/no-nuxt-config-test-key
Disallow setting test key in Nuxt config
Default severity: error
Presets: nuxt
Automatic fix: None; review the suggested change
Applies to: Nuxt configuration files (nuxt.config.ts)
Options: No rule-specific options. Severity and preset selection are configurable.
Configuration (Vite+)
import { defineConfig } from "@vizejs/vite-plugin/vite-plus";
export default defineConfig({
lint: {
vize: {
"preset": "incremental",
"rules": {
"nuxt/no-nuxt-config-test-key": "error"
}
},
},
});
vp run lint
Bad
The exported Nuxt config sets the identifier key test to the boolean true, the obsolete config shape this rule rejects.
nuxt.config.ts
export default defineNuxtConfig({ test: true });
Good
The empty config removes that boolean test property. This example does not forbid a test configuration object.
nuxt.config.ts
export default defineNuxtConfig({});
Good avoids this rule's finding under the configuration above; other rules may still report diagnostics.
nuxt/no-page-meta-runtime-values
Disallow runtime context values inside definePageMeta at the eager level, which is extracted into a separate chunk at build time and runs before component setup
Default severity: error
Presets: nuxt
Automatic fix: None; review the suggested change
Applies to: JS/TS scripts in Vue SFCs; examples show the relevant Options API or script setup form
Options: No rule-specific options. Severity and preset selection are configurable.
Configuration (Vite+)
import { defineConfig } from "@vizejs/vite-plugin/vite-plus";
export default defineConfig({
lint: {
vize: {
"preset": "incremental",
"rules": {
"nuxt/no-page-meta-runtime-values": "error"
}
},
},
});
vp run lint
Bad
useRoute() is evaluated immediately while building the definePageMeta object, although the macro hoists that metadata outside the setup runtime context.
<script setup lang="ts">
definePageMeta({ title: useRoute() });
</script>
Good
validate receives a callback, so its useRoute().params.id access is deferred until the callback runs. The rule distinguishes deferred function bodies from eager metadata values.
<script setup lang="ts">
definePageMeta({ validate: () => Boolean(useRoute().params.id) });
</script>
Good avoids this rule's finding under the configuration above; other rules may still report diagnostics.
nuxt/nuxt-config-keys-order
Prefer recommended order of Nuxt config properties
Default severity: error
Presets: nuxt
Automatic fix: Available for supported findings
Applies to: Nuxt configuration files (nuxt.config.ts)
Options: No rule-specific options. Severity and preset selection are configurable.
Configuration (Vite+)
import { defineConfig } from "@vizejs/vite-plugin/vite-plus";
export default defineConfig({
lint: {
vize: {
"preset": "incremental",
"rules": {
"nuxt/nuxt-config-keys-order": "error"
}
},
},
});
vp run lint
Bad
The config places ssr before modules, reversing their order in the rule’s recommended Nuxt config key sequence.
nuxt.config.ts
export default defineNuxtConfig({ ssr: true, modules: [] });
Good
Putting modules before ssr preserves both values while satisfying the prescribed order; the repair changes layout rather than either option’s meaning.
nuxt.config.ts
export default defineNuxtConfig({ modules: [], ssr: true });
Good avoids this rule's finding under the configuration above; other rules may still report diagnostics.
nuxt/prefer-import-meta
Prefer using import.meta.* over process.*
Default severity: error
Presets: nuxt
Automatic fix: Available for supported findings
Applies to: JS/TS scripts in Vue SFCs; examples show the relevant Options API or script setup form
Options: No rule-specific options. Severity and preset selection are configurable.
Configuration (Vite+)
import { defineConfig } from "@vizejs/vite-plugin/vite-plus";
export default defineConfig({
lint: {
vize: {
"preset": "incremental",
"rules": {
"nuxt/prefer-import-meta": "error"
}
},
},
});
vp run lint
Bad
process.client uses a legacy Nuxt environment flag that the rule asks to migrate to import.meta.
<script setup lang="ts">
if (process.client) console.log("browser");
</script>
Good
import.meta.client keeps the browser-only branch explicit using the replacement environment flag.
<script setup lang="ts">
if (import.meta.client) console.log("browser");
</script>
Good avoids this rule's finding under the configuration above; other rules may still report diagnostics.
Project examples
ecosystem/vue-router-unknown-route
The name is absent from the complete installed router.
Default severity: error
Applies to: Reachable project declarations and imported components
Options: crossFile; rule severity (off/warn/error)
Automatic fix: None
The complete installed router must be reachable from the application's createApp(...).use(router). Unknown/dynamic route tables do not prove unknown-name findings. Missing params are warnings because navigation may inherit a value from the current route.
import { defineConfig } from "@vizejs/vite-plugin/vite-plus";
export default defineConfig({
lint: {
vize: { preset: "incremental", crossFile: true,
rules: { "ecosystem/vue-router-unknown-route": "warn" },
},
},
});
vp run lint
Shared project files
Use these unchanged files in both Bad and Good. Install the imported packages in the project: Vue, plus vue-router or Pinia where shown. Follow any version-specific support note. The entry root makes the component relationship explicit.
index.html
<div id="app"></div>
<script type="module" src="/src/main.ts"></script>
src/main.ts
import { createApp } from "vue";
import App from "./App.vue";
import { router } from "./router";
createApp(App).use(router).mount("#app");
src/App.vue
<script setup lang="ts">
import { RouterView } from "vue-router";
</script>
<template><RouterView /></template>
src/router.ts
import { createRouter, createWebHistory } from "vue-router";
import UserPost from "./UserPost.vue";
export const router = createRouter({
history: createWebHistory(),
routes: [{ path: "/users/:userId/posts/:postId", name: "user-post", component: UserPost }],
});
Bad
The reachable installed router declares user-post, but navigation uses the misspelled user-posts name.
src/UserPost.vue
<script setup lang="ts">
import { useRouter } from "vue-router";
const router = useRouter();
router.push({ name: "user-posts", params: { userId: "1", postId: "2" } });
</script>
<template><p>Post</p></template>
Good
Use the registered user-post name while retaining both declared path parameters.
src/UserPost.vue
<script setup lang="ts">
import { useRouter } from "vue-router";
const router = useRouter();
router.push({ name: "user-post", params: { userId: "1", postId: "2" } });
</script>
<template><p>Post</p></template>
The Good files demonstrate the change described above; other diagnostics can still apply to the complete project.
ecosystem/vue-router-extra-param
The route does not declare tab; Vue Router discards it.
Default severity: error
Applies to: Reachable project declarations and imported components
Options: crossFile; rule severity (off/warn/error)
Automatic fix: None
The complete installed router must be reachable from the application's createApp(...).use(router). Unknown/dynamic route tables do not prove unknown-name findings. Missing params are warnings because navigation may inherit a value from the current route.
import { defineConfig } from "@vizejs/vite-plugin/vite-plus";
export default defineConfig({
lint: {
vize: { preset: "incremental", crossFile: true,
rules: { "ecosystem/vue-router-extra-param": "warn" },
},
},
});
vp run lint
Shared project files
Use these unchanged files in both Bad and Good. Install the imported packages in the project: Vue, plus vue-router or Pinia where shown. Follow any version-specific support note. The entry root makes the component relationship explicit.
index.html
<div id="app"></div>
<script type="module" src="/src/main.ts"></script>
src/main.ts
import { createApp } from "vue";
import App from "./App.vue";
import { router } from "./router";
createApp(App).use(router).mount("#app");
src/App.vue
<script setup lang="ts">
import { RouterView } from "vue-router";
</script>
<template><RouterView /></template>
src/router.ts
import { createRouter, createWebHistory } from "vue-router";
import UserPost from "./UserPost.vue";
export const router = createRouter({
history: createWebHistory(),
routes: [{ path: "/users/:userId/posts/:postId", name: "user-post", component: UserPost }],
});
Bad
The user-post path declares userId and postId, but the navigation also supplies undeclared tab as a path parameter.
src/UserPost.vue
<script setup lang="ts">
import { useRouter } from "vue-router";
const router = useRouter();
router.push({ name: "user-post", params: { userId: "1", postId: "2", tab: "a" } });
</script>
<template><p>Post</p></template>
Good
Remove tab from params and retain only the keys present in the route path. Use query separately if the application needs a tab selection.
src/UserPost.vue
<script setup lang="ts">
import { useRouter } from "vue-router";
const router = useRouter();
router.push({ name: "user-post", params: { userId: "1", postId: "2" } });
</script>
<template><p>Post</p></template>
The Good files demonstrate the change described above; other diagnostics can still apply to the complete project.
ecosystem/vue-router-param-type
postId is not repeatable, so an array is invalid.
Default severity: error
Applies to: Reachable project declarations and imported components
Options: crossFile; rule severity (off/warn/error)
Automatic fix: None
The complete installed router must be reachable from the application's createApp(...).use(router). Unknown/dynamic route tables do not prove unknown-name findings. Missing params are warnings because navigation may inherit a value from the current route.
import { defineConfig } from "@vizejs/vite-plugin/vite-plus";
export default defineConfig({
lint: {
vize: { preset: "incremental", crossFile: true,
rules: { "ecosystem/vue-router-param-type": "warn" },
},
},
});
vp run lint
Shared project files
Use these unchanged files in both Bad and Good. Install the imported packages in the project: Vue, plus vue-router or Pinia where shown. Follow any version-specific support note. The entry root makes the component relationship explicit.
index.html
<div id="app"></div>
<script type="module" src="/src/main.ts"></script>
src/main.ts
import { createApp } from "vue";
import App from "./App.vue";
import { router } from "./router";
createApp(App).use(router).mount("#app");
src/App.vue
<script setup lang="ts">
import { RouterView } from "vue-router";
</script>
<template><RouterView /></template>
src/router.ts
import { createRouter, createWebHistory } from "vue-router";
import UserPost from "./UserPost.vue";
export const router = createRouter({
history: createWebHistory(),
routes: [{ path: "/users/:userId/posts/:postId", name: "user-post", component: UserPost }],
});
Bad
postId is a scalar path parameter, but the navigation gives it the array ["2"].
src/UserPost.vue
<script setup lang="ts">
import { useRouter } from "vue-router";
const router = useRouter();
router.push({ name: "user-post", params: { userId: "1", postId: ["2"] } });
</script>
<template><p>Post</p></template>
Good
Pass the scalar "2" for the non-repeatable postId segment.
src/UserPost.vue
<script setup lang="ts">
import { useRouter } from "vue-router";
const router = useRouter();
router.push({ name: "user-post", params: { userId: "1", postId: "2" } });
</script>
<template><p>Post</p></template>
The Good files demonstrate the change described above; other diagnostics can still apply to the complete project.
ecosystem/vue-router-missing-param
Required postId is missing; relying on the current route is fragile.
Default severity: warning
Applies to: Reachable project declarations and imported components
Options: crossFile; rule severity (off/warn/error)
Automatic fix: None
The complete installed router must be reachable from the application's createApp(...).use(router). Unknown/dynamic route tables do not prove unknown-name findings. Missing params are warnings because navigation may inherit a value from the current route.
import { defineConfig } from "@vizejs/vite-plugin/vite-plus";
export default defineConfig({
lint: {
vize: { preset: "incremental", crossFile: true,
rules: { "ecosystem/vue-router-missing-param": "warn" },
},
},
});
vp run lint
Shared project files
Use these unchanged files in both Bad and Good. Install the imported packages in the project: Vue, plus vue-router or Pinia where shown. Follow any version-specific support note. The entry root makes the component relationship explicit.
index.html
<div id="app"></div>
<script type="module" src="/src/main.ts"></script>
src/main.ts
import { createApp } from "vue";
import App from "./App.vue";
import { router } from "./router";
createApp(App).use(router).mount("#app");
src/App.vue
<script setup lang="ts">
import { RouterView } from "vue-router";
</script>
<template><RouterView /></template>
src/router.ts
import { createRouter, createWebHistory } from "vue-router";
import UserPost from "./UserPost.vue";
export const router = createRouter({
history: createWebHistory(),
routes: [{ path: "/users/:userId/posts/:postId", name: "user-post", component: UserPost }],
});
Bad
The navigation omits required postId from the user-post path. This is a warning because Vue Router may inherit a value from the current route.
src/UserPost.vue
<script setup lang="ts">
import { useRouter } from "vue-router";
const router = useRouter();
router.push({ name: "user-post", params: { userId: "1" } });
</script>
<template><p>Post</p></template>
Good
Pass both userId and postId explicitly so navigation does not depend on the current route's parameter state.
src/UserPost.vue
<script setup lang="ts">
import { useRouter } from "vue-router";
const router = useRouter();
router.push({ name: "user-post", params: { userId: "1", postId: "2" } });
</script>
<template><p>Post</p></template>
The Good files demonstrate the change described above; other diagnostics can still apply to the complete project.
html/cross-component-nesting
Check actual HTML nesting after imported components are composed.
Default severity: warning
Applies to: Reachable project declarations and imported components
Options: crossFile; rule severity (off/warn/error)
Automatic fix: None
import { defineConfig } from "@vizejs/vite-plugin/vite-plus";
export default defineConfig({
lint: {
vize: { preset: "incremental", crossFile: true,
rules: { "html/cross-component-nesting": "warn" },
},
},
});
vp run lint
Shared project files
Use these unchanged files in both Bad and Good. Install the imported packages in the project: Vue, plus vue-router or Pinia where shown. Follow any version-specific support note. The entry root makes the component relationship explicit.
main.ts
import { createApp } from "vue";
import Root from "./App.vue";
createApp(Root).mount("#app");
index.html
<div id="app"></div>
<script type="module" src="/main.ts"></script>
Bad
The parent's <p> contains a resolved child whose root is <div>, producing invalid paragraph/block nesting after composition.
App.vue
<script setup lang="ts">
import Child from "./Child.vue";
</script>
<template><p><Child /></p></template>
Child.vue
<template><div>Block content</div></template>
Good
Use a <section> container that can contain the child's block element; the child stays unchanged.
App.vue
<script setup lang="ts">
import Child from "./Child.vue";
</script>
<template><section><Child /></section></template>
Child.vue
<template><div>Block content</div></template>
The Good files demonstrate the change described above; other diagnostics can still apply to the complete project.
vue/cross-file-attrs-fallthrough
A parent passes attributes to a resolved child whose root cannot inherit them and does not explicitly use $attrs.
Default severity: warning
Applies to: Reachable project declarations and imported components
Options: crossFile; rule severity (off/warn/error)
Automatic fix: None
import { defineConfig } from "@vizejs/vite-plugin/vite-plus";
export default defineConfig({
lint: {
vize: { preset: "incremental", crossFile: true,
rules: { "vue/cross-file-attrs-fallthrough": "warn" },
},
},
});
vp run lint
Shared project files
Use these unchanged files in both Bad and Good. Install the imported packages in the project: Vue, plus vue-router or Pinia where shown. Follow any version-specific support note. The entry root makes the component relationship explicit.
main.ts
import { createApp } from "vue";
import Root from "./App.vue";
createApp(Root).mount("#app");
index.html
<div id="app"></div>
<script type="module" src="/main.ts"></script>
Bad
The parent passes class="notice" to a resolved fragment child that has no automatic attribute target and never reads $attrs.
App.vue
<script setup lang="ts">
import Child from "./Child.vue";
</script>
<template><Child class="notice" /></template>
Child.vue
<template><main>Content</main><aside>Help</aside></template>
Good
The child chooses <main> as its target by binding $attrs there; its sibling <aside> remains separate.
App.vue
<script setup lang="ts">
import Child from "./Child.vue";
</script>
<template><Child class="notice" /></template>
Child.vue
<template><main v-bind="$attrs">Content</main><aside>Help</aside></template>
The Good files demonstrate the change described above; other diagnostics can still apply to the complete project.
vize:croquis/cf/array-mutation
An array is mutated by index, which a reactive array does not track.
Default severity: Not emitted
Applies to: Analyzed component graph and the supported facts described below
Automatic fix: None; review related files and apply the repair
Options: No per-code options; supported CLI findings accept severity overrides
This is a published diagnostic contract without a current producer. The Bad/Good scenario below explains the risk and repair; no flag currently makes this code trigger.
Historical Vue 2.7 only: use matching Vue 2.7 and SFC compiler dependencies for this scenario. Vue 3 proxies track array index assignment, so items[0] = next is reactive in Vue 3 and is not a Vue 3 defect. This published code has no current producer.
Shared project files
Use these unchanged files in both Bad and Good. Install the imported packages in the project: Vue, plus vue-router or Pinia where shown. Follow any version-specific support note. The entry root makes the component relationship explicit.
main.ts
import Vue from 'vue';
import App from './App.vue';
new Vue({ render: h => h(App) }).$mount('#app');
index.html
<!doctype html>
<html lang="en"><head><meta charset="UTF-8"><title>Contract scenario</title></head>
<body><div id="app"></div><script type="module" src="/main.ts"></script></body></html>
App.vue
<script lang="ts">
import Vue from 'vue';
import { replaceFirst } from './replace-first';
export default Vue.extend({
data() { return { items: ['Before'] }; },
methods: { replace() { replaceFirst(this.items, 'After'); } },
});
</script>
<template><section><p>{{ items[0] }}</p><button @click="replace">Replace</button></section></template>
Bad
In this historical Vue 2.7 project, items[0] = next changes the array without notifying Vue 2’s array observer, so the displayed first item need not update.
replace-first.ts
export function replaceFirst(items: string[], next: string): void {
items[0] = next;
}
Good
splice(0, 1, next) uses the array mutation method observed by Vue 2, allowing the same replacement to update the view.
replace-first.ts
export function replaceFirst(items: string[], next: string): void {
items.splice(0, 1, next);
}
The Good files demonstrate the change described above; other diagnostics can still apply to the complete project.
vize:croquis/cf/async-boundary
Reactive state crosses an async boundary and can be observed stale.
Default severity: error
Applies to: Analyzed component graph and the supported facts described below
Automatic fix: None; review related files and apply the repair
Options: No per-code options; supported CLI findings accept severity overrides
import { defineConfig } from "@vizejs/vite-plugin/vite-plus";
export default defineConfig({
lint: {
vize: { preset: "incremental", crossFile: true,
rules: { "croquis/cf/async-boundary": "warn" },
},
},
});
vp run lint
Shared project files
Use these unchanged files in both Bad and Good. Install the imported packages in the project: Vue, plus vue-router or Pinia where shown. Follow any version-specific support note. The entry root makes the component relationship explicit.
main.ts
import { createApp } from "vue";
import Root from "./SearchPage.vue";
createApp(Root).mount("#app");
index.html
<div id="app"></div>
<script type="module" src="/main.ts"></script>
api.ts
export interface Result { items: string[]; }
export async function load(query: string, options?: { signal?: AbortSignal }): Promise<Result> {
const response = await fetch(`/search?q=${encodeURIComponent(query)}`, options);
return response.json();
}
Bad
A slower old query can finish after a newer query and overwrite result, because the watcher has no invalidation cleanup.
SearchPage.vue
<script setup lang="ts">
import { ref } from "vue";
import SearchResults from "./SearchResults.vue";
const query = ref("");
</script>
<template>
<SearchResults :query="query" />
</template>
SearchResults.vue
<script setup lang="ts">
import { load, type Result } from "./api";
import { ref, watch } from "vue";
const props = defineProps<{ query: string }>();
const result = ref<Result | null>(null);
watch(
() => props.query,
async (value) => {
result.value = await load(value);
},
);
</script>
Good
Register cleanup before awaiting: abort the old request and invalidate its active flag, then assign only a still-active response.
SearchPage.vue
<script setup lang="ts">
import { ref } from "vue";
import SearchResults from "./SearchResults.vue";
const query = ref("");
</script>
<template>
<SearchResults :query="query" />
</template>
SearchResults.vue
<script setup lang="ts">
import { load, type Result } from "./api";
import { ref, watch } from "vue";
const props = defineProps<{ query: string }>();
const result = ref<Result | null>(null);
watch(
() => props.query,
async (value, _oldValue, onCleanup) => {
const controller = new AbortController();
let active = true;
onCleanup(() => {
active = false;
controller.abort();
});
const next = await load(value, { signal: controller.signal });
if (active) result.value = next;
},
);
</script>
The Good files demonstrate the change described above; other diagnostics can still apply to the complete project.
vize:croquis/cf/async-no-suspense
An async component is rendered without a Suspense boundary.
Default severity: warning
Applies to: Analyzed component graph and the supported facts described below
Automatic fix: None; review related files and apply the repair
Options: No per-code options; supported CLI findings accept severity overrides
The experimental Rust CrossFileAnalyzer has a producer for this code. The CLI pass does not emit this individual code; configuring its ID does not enable that Rust pass. These scenarios describe the analyzer's supported graph/facts, not a Vite+ promise.
Current support: no-source-async-fact
The boundary producer reads macros.is_async(), but source parsing currently records top-level await on the script-setup scope instead. The complete Bad/Good source pair below therefore produces no async-no-suspense finding through the current CLI. It explains the Suspense convention; supplying the missing macro fact is implementation follow-up work.
Shared project files
Use these unchanged files in both Bad and Good. Install the imported packages in the project: Vue, plus vue-router or Pinia where shown. Follow any version-specific support note. The entry root makes the component relationship explicit.
main.ts
import { createApp } from "vue";
import Root from "./App.vue";
createApp(Root).mount("#app");
index.html
<div id="app"></div>
<script type="module" src="/main.ts"></script>
Bad
The child has top-level await but its parent supplies no <Suspense> boundary. Current source parsing does not supply the macro fact required to emit this code.
App.vue
<script setup lang="ts">
import Child from "./Child.vue";
</script>
<template><Child /></template>
Child.vue
<script setup lang="ts">
const greeting = await Promise.resolve("Hello");
</script>
<template><p>{{ greeting }}</p></template>
Good
The parent wraps the same async child in <Suspense> with a loading fallback. This demonstrates the convention; both source alternatives remain non-emitted by the current pass.
App.vue
<script setup lang="ts">
import Child from "./Child.vue";
</script>
<template><Suspense><Child /><template #fallback><p>Loading</p></template></Suspense></template>
Child.vue
<script setup lang="ts">
const greeting = await Promise.resolve("Hello");
</script>
<template><p>{{ greeting }}</p></template>
The Good files demonstrate the change described above; other diagnostics can still apply to the complete project.
vize:croquis/cf/browser-api-ssr
A browser-only API is used where the component can render on the server.
Default severity: warning
Applies to: Analyzed component graph and the supported facts described below
Automatic fix: None; review related files and apply the repair
Options: No per-code options; supported CLI findings accept severity overrides
import { defineConfig } from "@vizejs/vite-plugin/vite-plus";
export default defineConfig({
lint: {
vize: { preset: "incremental", crossFile: true,
rules: { "croquis/cf/browser-api-ssr": "warn" },
},
},
});
vp run lint
Shared project files
Use these unchanged files in both Bad and Good. Install the imported packages in the project: Vue, plus vue-router or Pinia where shown. Follow any version-specific support note. The entry root makes the component relationship explicit.
main.ts
import { createApp } from "vue";
import Root from "./App.vue";
createApp(Root).mount("#app");
index.html
<div id="app"></div>
<script type="module" src="/main.ts"></script>
Bad
window.innerWidth runs during setup, where an SSR environment has no browser window.
App.vue
<script setup lang="ts">
const width = window.innerWidth;
</script>
<template><p>Content</p></template>
Good
Initialize a ref to a server-safe value and read window inside onMounted, which runs after client mounting.
App.vue
<script setup lang="ts">
import { onMounted, ref } from "vue";
const width = ref(0);
onMounted(() => { width.value = window.innerWidth; });
</script>
<template><p>Content</p></template>
The Good files demonstrate the change described above; other diagnostics can still apply to the complete project.
vize:croquis/cf/circular-dep
Components import each other in a cycle.
Default severity: Not emitted
Applies to: Analyzed component graph and the supported facts described below
Automatic fix: None; review related files and apply the repair
Options: No per-code options; supported CLI findings accept severity overrides
This is a published diagnostic contract without a current producer. The Bad/Good scenario below explains the risk and repair; no flag currently makes this code trigger.
This illustrates a concrete eager-initialization cycle. A recursive Vue component or every circular import is not automatically erroneous. No current producer emits this contract code.
Shared project files
Use these unchanged files in both Bad and Good. Install the imported packages in the project: Vue, plus vue-router or Pinia where shown. Follow any version-specific support note. The entry root makes the component relationship explicit.
main.ts
import { createApp } from 'vue';
import App from './App.vue';
createApp(App).mount('#app');
index.html
<!doctype html>
<html lang="en"><head><meta charset="UTF-8"><title>Contract scenario</title></head>
<body><div id="app"></div><script type="module" src="/main.ts"></script></body></html>
App.vue
<script setup lang="ts">
import { aLabel } from './a';
</script>
<template>
<p>{{ aLabel }}</p>
</template>
labels.ts
export const aPrefix = 'A';
export const bPrefix = 'B';
Bad
a.ts imports b.ts, which imports a.ts back. Both eagerly initialize a constant from the other module’s still-uninitialized constant, creating a temporal-dead-zone failure.
a.ts
import { bLabel } from './b';
export const aLabel = 'A' + bLabel;
b.ts
import { aLabel } from './a';
export const bLabel = 'B' + aLabel;
Good
Both modules read initialized prefixes from the independent labels.ts module, removing the cycle and the eager cross-read.
a.ts
import { bPrefix } from './labels';
export const aLabel = 'A' + bPrefix;
b.ts
import { aPrefix } from './labels';
export const bLabel = 'B' + aPrefix;
The Good files demonstrate the change described above; other diagnostics can still apply to the complete project.
vize:croquis/cf/circular-reactive-dependency
Reactive computations depend on each other in a cycle.
Default severity: context-dependent
Applies to: Analyzed component graph and the supported facts described below
Automatic fix: None; review related files and apply the repair
Options: No per-code options; supported CLI findings accept severity overrides
import { defineConfig } from "@vizejs/vite-plugin/vite-plus";
export default defineConfig({
lint: {
vize: { preset: "incremental", crossFile: true,
rules: { "croquis/cf/circular-reactive-dependency": "warn" },
},
},
});
vp run lint
Example qualification: illustrative-source-pair
The complete Vue project below illustrates update feedback and its repair. It is not a qualified CLI finding witness: the diagnostic producer requires retained reactive-flow reference identities and edges, as shown by the accompanying graph. These sources do not establish that the current source path will emit this exact code. Dedicated tracked-ID graph finding controls remain separate from source grammar checks.
Shared project files
Use these unchanged files in both Bad and Good. Install the imported packages in the project: Vue, plus vue-router or Pinia where shown. Follow any version-specific support note. The entry root makes the component relationship explicit.
main.ts
import { createApp } from 'vue';
import App from './App.vue';
createApp(App).mount('#app');
index.html
<div id="app"></div>
<script type="module" src="/main.ts"></script>
count-key.ts
import type { InjectionKey, Ref } from 'vue';
export const countKey: InjectionKey<Ref<number>> = Symbol('count');
App.vue
<script setup lang="ts">
import { provide, ref } from 'vue';
import { countKey } from './count-key';
import CycleView from './CycleView.vue';
const count = ref(1); // A: the provider-owned source.
provide(countKey, count);
</script>
<template>
<button @click="count++">Increment</button>
<CycleView />
</template>
Bad
App owns and provides count (A). CycleView derives nextCount (B), then immediately writes each derived value back into the same injected count. Every write changes the input to the computation again, creating update feedback A → B → A. The identities in the retained graph below represent these two references, not unrelated bindings with matching names.
CycleView.vue
<script setup lang="ts">
import { computed, inject, watch } from 'vue';
import { countKey } from './count-key';
const count = inject(countKey)!; // App provides this same A reference.
const nextCount = computed(() => count.value + 1); // B: the derived consumer.
watch(nextCount, value => { count.value = value; }, { immediate: true });
</script>
<template><p>{{ nextCount }}</p></template>
Tracked references: A = provider source; B = consumer reference
Tracked flows: A -> B; B -> A
Good
Remove the watcher that writes B back into A. App keeps ownership of count and changes it only through its explicit Increment action; CycleView reads the derived nextCount without feeding the result back. The same references retain only the A → B dependency.
CycleView.vue
<script setup lang="ts">
import { computed, inject } from 'vue';
import { countKey } from './count-key';
const count = inject(countKey)!; // App provides this same A reference.
const nextCount = computed(() => count.value + 1); // B: the derived consumer.
</script>
<template><p>{{ nextCount }}</p></template>
Tracked references: A = provider source; B = consumer reference
Tracked flows: A -> B
The Good files demonstrate the change described above; other diagnostics can still apply to the complete project.
vize:croquis/cf/closure-captures-reactive
A closure captures a reactive value and will not see later updates.
Default severity: Not emitted
Applies to: Analyzed component graph and the supported facts described below
Automatic fix: None; review related files and apply the repair
Options: No per-code options; supported CLI findings accept severity overrides
This is a published diagnostic contract without a current producer. The Bad/Good scenario below explains the risk and repair; no flag currently makes this code trigger.
Shared project files
Use these unchanged files in both Bad and Good. Install the imported packages in the project: Vue, plus vue-router or Pinia where shown. Follow any version-specific support note. The entry root makes the component relationship explicit.
main.ts
import { createApp } from 'vue';
import App from './App.vue';
createApp(App).mount('#app');
index.html
<!doctype html>
<html lang="en"><head><meta charset="UTF-8"><title>Contract scenario</title></head>
<body><div id="app"></div><script type="module" src="/main.ts"></script></body></html>
App.vue
<script setup lang="ts">
import { computed, ref } from 'vue';
import { makeReader } from './reader';
const count = ref(0);
const read = makeReader(count);
const shown = computed(read);
</script>
<template>
<button @click="count++">Increment {{ count }}</button><p>{{ shown }}</p>
</template>
Bad
makeReader copies count.value before creating the closure. The computed reader then returns that initial number without reading a reactive dependency.
reader.ts
import type { Ref } from 'vue';
export function makeReader(count: Ref<number>): () => number {
const captured = count.value;
return () => captured;
}
Good
The closure reads count.value when invoked, so the computed getter can track the ref and update shown after increments.
reader.ts
import type { Ref } from 'vue';
export function makeReader(count: Ref<number>): () => number {
return () => count.value;
}
The Good files demonstrate the change described above; other diagnostics can still apply to the complete project.
vize:croquis/cf/composable-outside-setup
A composable is called outside setup.
Default severity: Not emitted
Applies to: Analyzed component graph and the supported facts described below
Automatic fix: None; review related files and apply the repair
Options: No per-code options; supported CLI findings accept severity overrides
This is a published diagnostic contract without a current producer. The Bad/Good scenario below explains the risk and repair; no flag currently makes this code trigger.
The concern is this lifecycle-dependent composable, not a blanket ban on ordinary utility functions or all Composition API calls outside setup. This contract has no current producer.
Shared project files
Use these unchanged files in both Bad and Good. Install the imported packages in the project: Vue, plus vue-router or Pinia where shown. Follow any version-specific support note. The entry root makes the component relationship explicit.
main.ts
import { createApp } from 'vue';
import App from './App.vue';
createApp(App).mount('#app');
index.html
<!doctype html>
<html lang="en"><head><meta charset="UTF-8"><title>Contract scenario</title></head>
<body><div id="app"></div><script type="module" src="/main.ts"></script></body></html>
App.vue
<script setup lang="ts">
import { useTitle } from './use-title';
const title = useTitle();
</script>
<template>
<h1>{{ title }}</h1>
</template>
Bad
Importing use-title.ts registers onMounted before a component setup is active. Calling its exported function later only returns that module-level ref; it cannot repair the missed lifecycle ownership.
use-title.ts
import { onMounted, ref } from 'vue';
const title = ref('Before mount');
onMounted(() => { title.value = 'Mounted'; });
export function useTitle() { return title; }
Good
Both state creation and hook registration move into useTitle, which App calls synchronously inside setup. The mounted hook now belongs to that App instance.
use-title.ts
import { onMounted, ref } from 'vue';
export function useTitle() {
const title = ref('Before mount');
onMounted(() => { title.value = 'Mounted'; });
return title;
}
The Good files demonstrate the change described above; other diagnostics can still apply to the complete project.
vize:croquis/cf/computed-side-effects
A computed getter writes state or performs another side effect.
Default severity: Not emitted
Applies to: Analyzed component graph and the supported facts described below
Automatic fix: None; review related files and apply the repair
Options: No per-code options; supported CLI findings accept severity overrides
This is a published diagnostic contract without a current producer. The Bad/Good scenario below explains the risk and repair; no flag currently makes this code trigger.
Shared project files
Use these unchanged files in both Bad and Good. Install the imported packages in the project: Vue, plus vue-router or Pinia where shown. Follow any version-specific support note. The entry root makes the component relationship explicit.
main.ts
import { createApp } from 'vue';
import App from './App.vue';
createApp(App).mount('#app');
index.html
<!doctype html>
<html lang="en"><head><meta charset="UTF-8"><title>Contract scenario</title></head>
<body><div id="app"></div><script type="module" src="/main.ts"></script></body></html>
App.vue
<script setup lang="ts">
import { useDouble } from './use-double';
const { count, doubled, lastCalculated } = useDouble();
</script>
<template>
<button @click="count++">Increment {{ count }}</button><p>{{ doubled }} / {{ lastCalculated }}</p>
</template>
Bad
Evaluating doubled writes lastCalculated, so reading a computed value also mutates separate state. That couples the side effect to when the lazy getter is read.
use-double.ts
import { computed, ref } from 'vue';
export function useDouble() {
const count = ref(0);
const lastCalculated = ref(0);
const doubled = computed(() => {
const next = count.value * 2;
lastCalculated.value = next;
return next;
});
return { count, doubled, lastCalculated };
}
Good
The getter only returns the derived number. A separate watcher owns the write to lastCalculated when count changes, including its initial value.
use-double.ts
import { computed, ref, watch } from 'vue';
export function useDouble() {
const count = ref(0);
const lastCalculated = ref(0);
const doubled = computed(() => count.value * 2);
watch(count, next => { lastCalculated.value = next * 2; }, { immediate: true });
return { count, doubled, lastCalculated };
}
The Good files demonstrate the change described above; other diagnostics can still apply to the complete project.
vize:croquis/cf/deep-import
An import chain is deeper than the project allows.
Default severity: Not emitted
Applies to: Analyzed component graph and the supported facts described below
Automatic fix: None; review related files and apply the repair
Options: No per-code options; supported CLI findings accept severity overrides
This is a published diagnostic contract without a current producer. The Bad/Good scenario below explains the risk and repair; no flag currently makes this code trigger.
This is an explicitly chosen project layout policy; it does not invent a supported depth threshold or option. There is no current diagnostic producer for this contract.
Shared project files
Use these unchanged files in both Bad and Good. Install the imported packages in the project: Vue, plus vue-router or Pinia where shown. Follow any version-specific support note. The entry root makes the component relationship explicit.
main.ts
import { createApp } from 'vue';
import App from './App.vue';
createApp(App).mount('#app');
index.html
<!doctype html>
<html lang="en"><head><meta charset="UTF-8"><title>Contract scenario</title></head>
<body><div id="app"></div><script type="module" src="/main.ts"></script></body></html>
App.vue
<script setup lang="ts">
import { label } from './entry';
</script>
<template>
<p>{{ label }}</p>
</template>
value.ts
export const label = 'Notice';
level-one.ts
export { label } from './level-two';
level-two.ts
export { label } from './level-three';
level-three.ts
export { label } from './value';
public-api.ts
export { label } from './value';
Bad
The entry routes a simple value through level-one, level-two, and level-three, creating an unnecessarily deep import chain for a project that wants a shallow public boundary.
entry.ts
export { label } from './level-one';
Good
The entry uses public-api.ts, which re-exports the value directly. The consumer keeps the same imported name while the chain becomes shorter.
entry.ts
export { label } from './public-api';
The Good files demonstrate the change described above; other diagnostics can still apply to the complete project.
vize:croquis/cf/destructuring-breaks-reactivity
Destructuring a reactive object copies the fields and drops tracking.
Default severity: error
Applies to: Analyzed component graph and the supported facts described below
Automatic fix: None; review related files and apply the repair
Options: No per-code options; supported CLI findings accept severity overrides
import { defineConfig } from "@vizejs/vite-plugin/vite-plus";
export default defineConfig({
lint: {
vize: { preset: "incremental", crossFile: true,
rules: { "croquis/cf/destructuring-breaks-reactivity": "warn" },
},
},
});
vp run lint
Shared project files
Use these unchanged files in both Bad and Good. Install the imported packages in the project: Vue, plus vue-router or Pinia where shown. Follow any version-specific support note. The entry root makes the component relationship explicit.
main.ts
import { createApp } from "vue";
import Root from "./UserPage.vue";
createApp(Root).mount("#app");
index.html
<div id="app"></div>
<script type="module" src="/main.ts"></script>
Bad
Ordinary destructuring of the props object copies its current item value; this is separate from direct Vue 3.5 defineProps() destructuring.
UserPage.vue
<script setup lang="ts">
import { reactive } from "vue";
import UserSummary from "./UserSummary.vue";
const user = reactive({ name: "Ada" });
</script>
<template>
<UserSummary :item="user" />
</template>
UserSummary.vue
<script setup lang="ts">
const props = defineProps<{ item: { name: string } }>();
const { item } = props;
</script>
Good
toRef(props, "item") retains the connection to the property on props.
UserPage.vue
<script setup lang="ts">
import { reactive } from "vue";
import UserSummary from "./UserSummary.vue";
const user = reactive({ name: "Ada" });
</script>
<template>
<UserSummary :item="user" />
</template>
UserSummary.vue
<script setup lang="ts">
import { toRef } from "vue";
const props = defineProps<{ item: { name: string } }>();
const item = toRef(props, "item");
</script>
The Good files demonstrate the change described above; other diagnostics can still apply to the complete project.
vize:croquis/cf/di-outside-setup
provide or inject is called outside setup.
Default severity: Not emitted
Applies to: Analyzed component graph and the supported facts described below
Automatic fix: None; review related files and apply the repair
Options: No per-code options; supported CLI findings accept severity overrides
This is a published diagnostic contract without a current producer. The Bad/Good scenario below explains the risk and repair; no flag currently makes this code trigger.
This example uses component provide/inject. app.provide and supported app.runWithContext injection are different valid ownership surfaces, not prohibited by this scenario. No current producer emits this contract code.
Shared project files
Use these unchanged files in both Bad and Good. Install the imported packages in the project: Vue, plus vue-router or Pinia where shown. Follow any version-specific support note. The entry root makes the component relationship explicit.
main.ts
import { createApp } from 'vue';
import App from './App.vue';
createApp(App).mount('#app');
index.html
<!doctype html>
<html lang="en"><head><meta charset="UTF-8"><title>Contract scenario</title></head>
<body><div id="app"></div><script type="module" src="/main.ts"></script></body></html>
theme.ts
import { inject, provide } from 'vue';
import type { InjectionKey } from 'vue';
export const ThemeKey: InjectionKey<string> = Symbol('theme');
export function provideTheme() { provide(ThemeKey, 'dark'); }
export function useTheme() { return inject(ThemeKey, 'light'); }
ThemedText.vue
<script setup lang="ts">
import { useTheme } from './theme';
const theme = useTheme();
</script>
<template>
<p>{{ theme }}</p>
</template>
Bad
main.ts calls component provide with no active component instance. The child’s inject therefore cannot receive this intended ancestor value and uses light.
main.ts
import { createApp } from 'vue';
import App from './App.vue';
import { provideTheme } from './theme';
provideTheme();
createApp(App).mount('#app');
App.vue
<script setup lang="ts">
import ThemedText from './ThemedText.vue';
</script>
<template>
<ThemedText />
</template>
Good
App calls the provider from its setup before rendering the child. The child now inherits the dark value from its component ancestor.
App.vue
<script setup lang="ts">
import ThemedText from './ThemedText.vue';
import { provideTheme } from './theme';
provideTheme();
</script>
<template>
<ThemedText />
</template>
The Good files demonstrate the change described above; other diagnostics can still apply to the complete project.
vize:croquis/cf/dom-access-without-next-tick
The DOM is read before Vue has flushed the update.
Default severity: Not emitted
Applies to: Analyzed component graph and the supported facts described below
Automatic fix: None; review related files and apply the repair
Options: No per-code options; supported CLI findings accept severity overrides
This is a published diagnostic contract without a current producer. The Bad/Good scenario below explains the risk and repair; no flag currently makes this code trigger.
Shared project files
Use these unchanged files in both Bad and Good. Install the imported packages in the project: Vue, plus vue-router or Pinia where shown. Follow any version-specific support note. The entry root makes the component relationship explicit.
main.ts
import { createApp } from 'vue';
import App from './App.vue';
createApp(App).mount('#app');
index.html
<!doctype html>
<html lang="en"><head><meta charset="UTF-8"><title>Contract scenario</title></head>
<body><div id="app"></div><script type="module" src="/main.ts"></script></body></html>
read-label.ts
export function readLabel(node: HTMLElement | null): string {
return node?.textContent ?? '';
}
Bad
The click handler increments count and immediately reads the rendered paragraph, before Vue flushes the scheduled DOM update. sampled can contain the previous count.
App.vue
<script setup lang="ts">
import { ref } from 'vue';
import { readLabel } from './read-label';
const count = ref(0);
const label = ref<HTMLElement | null>(null);
const sampled = ref('');
function increment() {
count.value++;
sampled.value = readLabel(label.value);
}
</script>
<template>
<button @click="increment">Increment</button><p ref="label">{{ count }}</p><p>DOM sample: {{ sampled }}</p>
</template>
Good
Awaiting nextTick() after the state write lets Vue update the paragraph before readLabel samples its text.
App.vue
<script setup lang="ts">
import { nextTick, ref } from 'vue';
import { readLabel } from './read-label';
const count = ref(0);
const label = ref<HTMLElement | null>(null);
const sampled = ref('');
async function increment() {
count.value++;
await nextTick();
sampled.value = readLabel(label.value);
}
</script>
<template>
<button @click="increment">Increment</button><p ref="label">{{ count }}</p><p>DOM sample: {{ sampled }}</p>
</template>
The Good files demonstrate the change described above; other diagnostics can still apply to the complete project.
vize:croquis/cf/duplicate-id
The same element id is used in more than one component.
Default severity: warning
Applies to: Analyzed component graph and the supported facts described below
Automatic fix: None; review related files and apply the repair
Options: No per-code options; supported CLI findings accept severity overrides
import { defineConfig } from "@vizejs/vite-plugin/vite-plus";
export default defineConfig({
lint: {
vize: { preset: "incremental", crossFile: true,
rules: { "croquis/cf/duplicate-id": "warn" },
},
},
});
vp run lint
Shared project files
Use these unchanged files in both Bad and Good. Install the imported packages in the project: Vue, plus vue-router or Pinia where shown. Follow any version-specific support note. The entry root makes the component relationship explicit.
main.ts
import { createApp } from "vue";
import Root from "./CheckoutForm.vue";
createApp(Root).mount("#app");
index.html
<div id="app"></div>
<script type="module" src="/main.ts"></script>
Bad
The reachable shipping and billing components both render id="postal-code", so their labels share an ambiguous document target.
CheckoutForm.vue
<script setup lang="ts">
import BillingAddress from "./BillingAddress.vue";
import ShippingAddress from "./ShippingAddress.vue";
</script>
<template>
<ShippingAddress />
<BillingAddress />
</template>
ShippingAddress.vue
<template>
<label for="postal-code">Shipping postal code</label>
<input id="postal-code" />
</template>
BillingAddress.vue
<template>
<label for="postal-code">Billing postal code</label>
<input id="postal-code" />
</template>
Good
Each component calls useId() and binds its own value to both label and input, preserving the association without a repeated literal ID.
CheckoutForm.vue
<script setup lang="ts">
import BillingAddress from "./BillingAddress.vue";
import ShippingAddress from "./ShippingAddress.vue";
</script>
<template>
<ShippingAddress />
<BillingAddress />
</template>
ShippingAddress.vue
<script setup lang="ts">
import { useId } from "vue";
const postalCodeId = useId();
</script>
<template>
<label :for="postalCodeId">Shipping postal code</label>
<input :id="postalCodeId" />
</template>
BillingAddress.vue
<script setup lang="ts">
import { useId } from "vue";
const postalCodeId = useId();
</script>
<template>
<label :for="postalCodeId">Billing postal code</label>
<input :id="postalCodeId" />
</template>
The Good files demonstrate the change described above; other diagnostics can still apply to the complete project.
vize:croquis/cf/event-listener-leak
An event listener is registered and never removed.
Default severity: Not emitted
Applies to: Analyzed component graph and the supported facts described below
Automatic fix: None; review related files and apply the repair
Options: No per-code options; supported CLI findings accept severity overrides
This is a published diagnostic contract without a current producer. The Bad/Good scenario below explains the risk and repair; no flag currently makes this code trigger.
Shared project files
Use these unchanged files in both Bad and Good. Install the imported packages in the project: Vue, plus vue-router or Pinia where shown. Follow any version-specific support note. The entry root makes the component relationship explicit.
main.ts
import { createApp } from 'vue';
import App from './App.vue';
createApp(App).mount('#app');
index.html
<!doctype html>
<html lang="en"><head><meta charset="UTF-8"><title>Contract scenario</title></head>
<body><div id="app"></div><script type="module" src="/main.ts"></script></body></html>
App.vue
<script setup lang="ts">
import { useWidth } from './use-width';
const width = useWidth();
</script>
<template>
<p>{{ width }}</p>
</template>
Bad
Mounting adds a window resize listener that captures the component’s width ref, but unmounting never removes it. Repeated mounts can retain unused listeners and state.
use-width.ts
import { onMounted, ref } from 'vue';
export function useWidth() {
const width = ref(0);
const resize = () => { width.value = window.innerWidth; };
onMounted(() => { resize(); window.addEventListener('resize', resize); });
return width;
}
Good
onUnmounted removes the exact same resize function registered at mount, ending that instance’s external listener lifetime.
use-width.ts
import { onMounted, onUnmounted, ref } from 'vue';
export function useWidth() {
const width = ref(0);
const resize = () => { width.value = window.innerWidth; };
onMounted(() => { resize(); window.addEventListener('resize', resize); });
onUnmounted(() => { window.removeEventListener('resize', resize); });
return width;
}
The Good files demonstrate the change described above; other diagnostics can still apply to the complete project.
vize:croquis/cf/event-modifier
An event listener uses a modifier the emit does not support.
Default severity: info
Applies to: Analyzed component graph and the supported facts described below
Automatic fix: None; review related files and apply the repair
Options: No per-code options; supported CLI findings accept severity overrides
The experimental Rust CrossFileAnalyzer has a producer for this code. The CLI pass does not emit this individual code; configuring its ID does not enable that Rust pass. These scenarios describe the analyzer's supported graph/facts, not a Vite+ promise.
Shared project files
Use these unchanged files in both Bad and Good. Install the imported packages in the project: Vue, plus vue-router or Pinia where shown. Follow any version-specific support note. The entry root makes the component relationship explicit.
main.ts
import { createApp } from "vue";
import Root from "./App.vue";
createApp(Root).mount("#app");
index.html
<div id="app"></div>
<script type="module" src="/main.ts"></script>
Bad
.stop assumes a native event's propagation method on the child's custom save event, whose payload need not be a DOM Event.
App.vue
<script setup lang="ts">
import Child from "./Child.vue";
</script>
<template><Child @save.stop="() => {}" /></template>
Child.vue
<script setup lang="ts">
const emit = defineEmits<{ save: [] }>();
emit("save");
</script>
<template><p>Content</p></template>
Good
Remove .stop from the custom-event listener; handle native propagation at the actual DOM listener when needed.
App.vue
<script setup lang="ts">
import Child from "./Child.vue";
</script>
<template><Child @save="() => {}" /></template>
Child.vue
<script setup lang="ts">
const emit = defineEmits<{ save: [] }>();
emit("save");
</script>
<template><p>Content</p></template>
The Good files demonstrate the change described above; other diagnostics can still apply to the complete project.
vize:croquis/cf/hydration-risk
This code groups several reactivity findings, including a prop copied into a ref. It does not imply that every Date.now() expression is detected by the cross-file pass.
Default severity: error
Applies to: Analyzed component graph and the supported facts described below
Automatic fix: None; review related files and apply the repair
Options: No per-code options; supported CLI findings accept severity overrides
import { defineConfig } from "@vizejs/vite-plugin/vite-plus";
export default defineConfig({
lint: {
vize: { preset: "incremental", crossFile: true,
rules: { "croquis/cf/hydration-risk": "warn" },
},
},
});
vp run lint
Shared project files
Use these unchanged files in both Bad and Good. Install the imported packages in the project: Vue, plus vue-router or Pinia where shown. Follow any version-specific support note. The entry root makes the component relationship explicit.
main.ts
import { createApp } from "vue";
import Root from "./App.vue";
createApp(Root).mount("#app");
index.html
<div id="app"></div>
<script type="module" src="/main.ts"></script>
Bad
The child initializes ref(props.count) once, so its local count no longer follows later parent prop changes. This is the current prop-to-ref producer, not a general nondeterministic-SSR example.
App.vue
<script setup lang="ts">
import Child from "./Child.vue";
</script>
<template><Child :count="0" /></template>
Child.vue
<script setup lang="ts">
import { ref } from "vue";
const props = defineProps<{ count: number }>();
const count = ref(props.count);
</script>
<template><p>{{ count }}</p></template>
Good
toRef(props, "count") points to the prop instead of copying its initial value into independent state.
App.vue
<script setup lang="ts">
import Child from "./Child.vue";
</script>
<template><Child :count="0" /></template>
Child.vue
<script setup lang="ts">
import { toRef } from "vue";
const props = defineProps<{ count: number }>();
const count = toRef(props, "count");
</script>
<template><p>{{ count }}</p></template>
The Good files demonstrate the change described above; other diagnostics can still apply to the complete project.
vize:croquis/cf/inherit-attrs-unused
inheritAttrs: false is set and the component never reads the attributes.
Default severity: warning
Applies to: Analyzed component graph and the supported facts described below
Automatic fix: None; review related files and apply the repair
Options: No per-code options; supported CLI findings accept severity overrides
The experimental Rust CrossFileAnalyzer has a producer for this code. The CLI pass does not emit this individual code; configuring its ID does not enable that Rust pass. These scenarios describe the analyzer's supported graph/facts, not a Vite+ promise.
Shared project files
Use these unchanged files in both Bad and Good. Install the imported packages in the project: Vue, plus vue-router or Pinia where shown. Follow any version-specific support note. The entry root makes the component relationship explicit.
main.ts
import { createApp } from "vue";
import Root from "./App.vue";
createApp(Root).mount("#app");
index.html
<div id="app"></div>
<script type="module" src="/main.ts"></script>
Bad
The child sets inheritAttrs: false but never forwards the parent's class="notice" attribute.
App.vue
<script setup lang="ts">
import Child from "./Child.vue";
</script>
<template><Child class="notice" /></template>
Child.vue
<script setup lang="ts">
defineOptions({ inheritAttrs: false });
</script>
<template><main>Content</main></template>
Good
Keep explicit inheritance control and bind $attrs to the intended <main> target.
App.vue
<script setup lang="ts">
import Child from "./Child.vue";
</script>
<template><Child class="notice" /></template>
Child.vue
<script setup lang="ts">
defineOptions({ inheritAttrs: false });
</script>
<template><main v-bind="$attrs">Content</main></template>
The Good files demonstrate the change described above; other diagnostics can still apply to the complete project.
vize:croquis/cf/inject-without-symbol
inject uses a plain key instead of an InjectionKey symbol.
Default severity: warning
Applies to: Analyzed component graph and the supported facts described below
Automatic fix: None; review related files and apply the repair
Options: No per-code options; supported CLI findings accept severity overrides
import { defineConfig } from "@vizejs/vite-plugin/vite-plus";
export default defineConfig({
lint: {
vize: { preset: "incremental", crossFile: true,
rules: { "croquis/cf/inject-without-symbol": "warn" },
},
},
});
vp run lint
Shared project files
Use these unchanged files in both Bad and Good. Install the imported packages in the project: Vue, plus vue-router or Pinia where shown. Follow any version-specific support note. The entry root makes the component relationship explicit.
main.ts
import { createApp } from "vue";
import Root from "./ThemeProvider.vue";
createApp(Root).mount("#app");
index.html
<div id="app"></div>
<script type="module" src="/main.ts"></script>
keys/theme.ts
import type { InjectionKey, Ref } from "vue";
export interface Theme { color: string; }
export const ThemeKey: InjectionKey<Ref<Theme>> = Symbol("theme");
Bad
The consumer injects the untyped string key "theme", which offers no symbol identity shared with the provider.
ThemeProvider.vue
<script setup lang="ts">
import { provide, ref } from "vue";
import ThemeLabel from "./ThemeLabel.vue";
const theme = ref({ color: "blue" });
provide("theme", theme);
</script>
<template>
<ThemeLabel />
</template>
ThemeLabel.vue
<script setup lang="ts">
import { inject } from "vue";
const theme = inject("theme");
</script>
Good
The consumer and provider import the same ThemeKey instead of duplicating string names.
ThemeProvider.vue
<script setup lang="ts">
import { provide, ref } from "vue";
import ThemeLabel from "./ThemeLabel.vue";
import { ThemeKey } from "./keys/theme";
const theme = ref({ color: "blue" });
provide(ThemeKey, theme);
</script>
<template>
<ThemeLabel />
</template>
ThemeLabel.vue
<script setup lang="ts">
import { inject } from "vue";
import { ThemeKey } from "./keys/theme";
const theme = inject(ThemeKey);
</script>
The Good files demonstrate the change described above; other diagnostics can still apply to the complete project.
vize:croquis/cf/injected-async-mutation-race
An injected value is mutated from an async task that can race.
Default severity: error
Applies to: Analyzed component graph and the supported facts described below
Automatic fix: None; review related files and apply the repair
Options: No per-code options; supported CLI findings accept severity overrides
import { defineConfig } from "@vizejs/vite-plugin/vite-plus";
export default defineConfig({
lint: {
vize: { preset: "incremental", crossFile: true,
rules: { "croquis/cf/injected-async-mutation-race": "warn" },
},
},
});
vp run lint
Shared project files
Use these unchanged files in both Bad and Good. Install the imported packages in the project: Vue, plus vue-router or Pinia where shown. Follow any version-specific support note. The entry root makes the component relationship explicit.
main.ts
import { createApp } from "vue";
import Root from "./StoreProvider.vue";
createApp(Root).mount("#app");
index.html
<div id="app"></div>
<script type="module" src="/main.ts"></script>
api.ts
export async function loadCount(query: string, options?: { signal?: AbortSignal }): Promise<number> {
const response = await fetch(`/count?q=${encodeURIComponent(query)}`, options);
return Number(await response.text());
}
CountSummary.vue
<script setup lang="ts">
import { inject } from "vue";
import { StoreKey } from "./keys/store";
const store = inject(StoreKey)!;
</script>
<template><p>{{ store.count }}</p></template>
Bad
CountLoader.vue writes an awaited result directly into the injected store shared with CountSummary.vue, letting stale work affect both consumers.
keys/store.ts
import type { InjectionKey } from "vue";
export interface Store {
count: number;
}
export const StoreKey: InjectionKey<Store> = Symbol("store");
StoreProvider.vue
<script setup lang="ts">
import { provide, reactive } from "vue";
import CountLoader from "./CountLoader.vue";
import CountSummary from "./CountSummary.vue";
import { StoreKey, type Store } from "./keys/store";
const store = reactive<Store>({ count: 0 });
provide(StoreKey, store);
</script>
<template>
<CountLoader />
<CountSummary />
</template>
CountLoader.vue
<script setup lang="ts">
import { loadCount } from "./api";
import { inject, ref, watch } from "vue";
import { StoreKey } from "./keys/store";
const store = inject(StoreKey)!;
const query = ref("");
watch(query, async (value) => {
store.count = await loadCount(value);
});
</script>
Good
The loader cancels invalidated work and emits only an active result. The provider owns the store mutation through applyLoadedCount.
keys/store.ts
import type { InjectionKey } from "vue";
export interface Store {
count: number;
}
export const StoreKey: InjectionKey<Store> = Symbol("store");
StoreProvider.vue
<script setup lang="ts">
import { provide, reactive } from "vue";
import CountLoader from "./CountLoader.vue";
import CountSummary from "./CountSummary.vue";
import { StoreKey, type Store } from "./keys/store";
const store = reactive<Store>({ count: 0 });
provide(StoreKey, store);
function applyLoadedCount(count: number) {
store.count = count;
}
</script>
<template>
<CountLoader @loaded="applyLoadedCount" />
<CountSummary />
</template>
CountLoader.vue
<script setup lang="ts">
import { loadCount } from "./api";
import { ref, watch } from "vue";
const emit = defineEmits<{ loaded: [count: number] }>();
const query = ref("");
watch(query, async (value, _oldValue, onCleanup) => {
const controller = new AbortController();
let active = true;
onCleanup(() => {
active = false;
controller.abort();
});
const count = await loadCount(value, { signal: controller.signal });
if (active) emit("loaded", count);
});
</script>
The Good files demonstrate the change described above; other diagnostics can still apply to the complete project.
vize:croquis/cf/lifecycle-outside-setup
A lifecycle hook is registered outside setup.
Default severity: Not emitted
Applies to: Analyzed component graph and the supported facts described below
Automatic fix: None; review related files and apply the repair
Options: No per-code options; supported CLI findings accept severity overrides
This is a published diagnostic contract without a current producer. The Bad/Good scenario below explains the risk and repair; no flag currently makes this code trigger.
Shared project files
Use these unchanged files in both Bad and Good. Install the imported packages in the project: Vue, plus vue-router or Pinia where shown. Follow any version-specific support note. The entry root makes the component relationship explicit.
main.ts
import { createApp } from 'vue';
import App from './App.vue';
createApp(App).mount('#app');
index.html
<!doctype html>
<html lang="en"><head><meta charset="UTF-8"><title>Contract scenario</title></head>
<body><div id="app"></div><script type="module" src="/main.ts"></script></body></html>
install-title.ts
import { onMounted } from 'vue';
export function installTitle() {
onMounted(() => { document.title = 'Mounted application'; });
}
Bad
The entry calls installTitle() before mounting an app, so onMounted is registered without an active component setup context.
main.ts
import { createApp } from 'vue';
import App from './App.vue';
import { installTitle } from './install-title';
installTitle();
createApp(App).mount('#app');
App.vue
<script setup lang="ts">
</script>
<template>
<p>Application</p>
</template>
Good
Calling the same helper synchronously from App’s setup attaches the lifecycle callback to that instance’s mount.
App.vue
<script setup lang="ts">
import { installTitle } from './install-title';
installTitle();
</script>
<template>
<p>Application</p>
</template>
The Good files demonstrate the change described above; other diagnostics can still apply to the complete project.
vize:croquis/cf/lifecycle-without-cleanup
A lifecycle hook starts work and never cleans it up.
Default severity: warning
Applies to: Analyzed component graph and the supported facts described below
Automatic fix: None; review related files and apply the repair
Options: No per-code options; supported CLI findings accept severity overrides
The experimental Rust CrossFileAnalyzer has a producer for this code. The CLI pass does not emit this individual code; configuring its ID does not enable that Rust pass. These scenarios describe the analyzer's supported graph/facts, not a Vite+ promise.
Shared project files
Use these unchanged files in both Bad and Good. Install the imported packages in the project: Vue, plus vue-router or Pinia where shown. Follow any version-specific support note. The entry root makes the component relationship explicit.
main.ts
import { createApp } from "vue";
import Root from "./App.vue";
createApp(Root).mount("#app");
index.html
<div id="app"></div>
<script type="module" src="/main.ts"></script>
Bad
Mounting registers a window resize listener, but unmounting never removes the same callback.
App.vue
<script setup lang="ts">
import { onMounted } from "vue";
const resize = () => {};
onMounted(() => { window.addEventListener("resize", resize); });
</script>
<template><p>Content</p></template>
Good
onUnmounted removes the listener with the same event name and function identity used by addEventListener.
App.vue
<script setup lang="ts">
import { onMounted, onUnmounted } from "vue";
const resize = () => {};
onMounted(() => { window.addEventListener("resize", resize); });
onUnmounted(() => { window.removeEventListener("resize", resize); });
</script>
<template><p>Content</p></template>
The Good files demonstrate the change described above; other diagnostics can still apply to the complete project.
vize:croquis/cf/missing-required-prop
A required prop is not passed.
Default severity: error
Applies to: Analyzed component graph and the supported facts described below
Automatic fix: None; review related files and apply the repair
Options: No per-code options; supported CLI findings accept severity overrides
The experimental Rust CrossFileAnalyzer has a producer for this code. The CLI pass does not emit this individual code; configuring its ID does not enable that Rust pass. These scenarios describe the analyzer's supported graph/facts, not a Vite+ promise.
Shared project files
Use these unchanged files in both Bad and Good. Install the imported packages in the project: Vue, plus vue-router or Pinia where shown. Follow any version-specific support note. The entry root makes the component relationship explicit.
main.ts
import { createApp } from "vue";
import Root from "./App.vue";
createApp(Root).mount("#app");
index.html
<div id="app"></div>
<script type="module" src="/main.ts"></script>
Bad
The parent renders <Child /> without the child's required title: string prop.
App.vue
<script setup lang="ts">
import Child from "./Child.vue";
</script>
<template><Child /></template>
Child.vue
<script setup lang="ts">
const props = defineProps<{ title: string }>();
</script>
<template><p>{{ props.title }}</p></template>
Good
title="Hello" supplies the required prop declared by the resolved child.
App.vue
<script setup lang="ts">
import Child from "./Child.vue";
</script>
<template><Child title="Hello" /></template>
Child.vue
<script setup lang="ts">
const props = defineProps<{ title: string }>();
</script>
<template><p>{{ props.title }}</p></template>
The Good files demonstrate the change described above; other diagnostics can still apply to the complete project.
vize:croquis/cf/missing-suspense
An async dependency is used outside a Suspense boundary.
Default severity: Not emitted
Applies to: Analyzed component graph and the supported facts described below
Automatic fix: None; review related files and apply the repair
Options: No per-code options; supported CLI findings accept severity overrides
This is a published diagnostic contract without a current producer. The Bad/Good scenario below explains the risk and repair; no flag currently makes this code trigger.
Shared project files
Use these unchanged files in both Bad and Good. Install the imported packages in the project: Vue, plus vue-router or Pinia where shown. Follow any version-specific support note. The entry root makes the component relationship explicit.
main.ts
import { createApp } from 'vue';
import App from './App.vue';
createApp(App).mount('#app');
index.html
<!doctype html>
<html lang="en"><head><meta charset="UTF-8"><title>Contract scenario</title></head>
<body><div id="app"></div><script type="module" src="/main.ts"></script></body></html>
AsyncCard.vue
<script setup lang="ts">
const message = await Promise.resolve('Ready');
</script>
<template>
<p>{{ message }}</p>
</template>
Bad
AsyncCard has top-level await, making its setup asynchronous, but App renders it without a Suspense boundary to coordinate that dependency.
App.vue
<script setup lang="ts">
import AsyncCard from './AsyncCard.vue';
</script>
<template>
<AsyncCard />
</template>
Good
App wraps the async child in Suspense and supplies a loading fallback until the child setup resolves.
App.vue
<script setup lang="ts">
import { Suspense } from 'vue';
import AsyncCard from './AsyncCard.vue';
</script>
<template>
<Suspense><AsyncCard /><template #fallback><p>Loading…</p></template></Suspense>
</template>
The Good files demonstrate the change described above; other diagnostics can still apply to the complete project.
vize:croquis/cf/module-scope-reactive
Reactive state is created at module scope and shared by every caller.
Default severity: Not emitted
Applies to: Analyzed component graph and the supported facts described below
Automatic fix: None; review related files and apply the repair
Options: No per-code options; supported CLI findings accept severity overrides
This is a published diagnostic contract without a current producer. The Bad/Good scenario below explains the risk and repair; no flag currently makes this code trigger.
Module-scope reactive state is legal for intentional application stores. This example assumes component/request isolation; the published contract currently has no producer.
Shared project files
Use these unchanged files in both Bad and Good. Install the imported packages in the project: Vue, plus vue-router or Pinia where shown. Follow any version-specific support note. The entry root makes the component relationship explicit.
main.ts
import { createApp } from 'vue';
import App from './App.vue';
createApp(App).mount('#app');
index.html
<!doctype html>
<html lang="en"><head><meta charset="UTF-8"><title>Contract scenario</title></head>
<body><div id="app"></div><script type="module" src="/main.ts"></script></body></html>
App.vue
<script setup lang="ts">
import Counter from './Counter.vue';
</script>
<template>
<Counter /><Counter />
</template>
Counter.vue
<script setup lang="ts">
import { createCounter } from './counter';
const { count } = createCounter();
</script>
<template>
<button @click="count++">{{ count }}</button>
</template>
Bad
The module initializes count once, and both Counter instances receive the same ref. Clicking one changes both counters even though this example intends independent instance state.
counter.ts
import { ref } from 'vue';
const count = ref(0);
export function createCounter() { return { count }; }
Good
Creating the ref inside createCounter gives each synchronous setup call a separate state object, so each button owns its counter.
counter.ts
import { ref } from 'vue';
export function createCounter() {
const count = ref(0);
return { count };
}
The Good files demonstrate the change described above; other diagnostics can still apply to the complete project.
vize:croquis/cf/multi-root-attrs
A multi-root component receives attributes and has nowhere to put them.
Default severity: warning
Applies to: Analyzed component graph and the supported facts described below
Automatic fix: None; review related files and apply the repair
Options: No per-code options; supported CLI findings accept severity overrides
The experimental Rust CrossFileAnalyzer has a producer for this code. The CLI pass does not emit this individual code; configuring its ID does not enable that Rust pass. These scenarios describe the analyzer's supported graph/facts, not a Vite+ promise.
Shared project files
Use these unchanged files in both Bad and Good. Install the imported packages in the project: Vue, plus vue-router or Pinia where shown. Follow any version-specific support note. The entry root makes the component relationship explicit.
main.ts
import { createApp } from "vue";
import Root from "./App.vue";
createApp(Root).mount("#app");
index.html
<div id="app"></div>
<script type="module" src="/main.ts"></script>
Bad
The child has <main> and <aside> roots, so Vue has no single root that can automatically receive the parent's class.
App.vue
<script setup lang="ts">
import Child from "./Child.vue";
</script>
<template><Child class="notice" /></template>
Child.vue
<template><main>Content</main><aside>Help</aside></template>
Good
Forward $attrs explicitly to <main> while keeping the second root.
App.vue
<script setup lang="ts">
import Child from "./Child.vue";
</script>
<template><Child class="notice" /></template>
Child.vue
<template><main v-bind="$attrs">Content</main><aside>Help</aside></template>
The Good files demonstrate the change described above; other diagnostics can still apply to the complete project.
vize:croquis/cf/mutated-after-escape
A reactive object is mutated after it has escaped its owner.
Default severity: Not emitted
Applies to: Analyzed component graph and the supported facts described below
Automatic fix: None; review related files and apply the repair
Options: No per-code options; supported CLI findings accept severity overrides
This is a published diagnostic contract without a current producer. The Bad/Good scenario below explains the risk and repair; no flag currently makes this code trigger.
This is an explicit immutable-history ownership policy, not a general prohibition on passing or later mutating reactive objects. No current producer emits this contract.
Shared project files
Use these unchanged files in both Bad and Good. Install the imported packages in the project: Vue, plus vue-router or Pinia where shown. Follow any version-specific support note. The entry root makes the component relationship explicit.
main.ts
import { createApp } from 'vue';
import App from './App.vue';
createApp(App).mount('#app');
index.html
<!doctype html>
<html lang="en"><head><meta charset="UTF-8"><title>Contract scenario</title></head>
<body><div id="app"></div><script type="module" src="/main.ts"></script></body></html>
archive.ts
export interface Profile { name: string }
const records: Readonly<Profile>[] = [];
export function publish(profile: Readonly<Profile>): void { records.push(profile); }
export function latestName(): string { return records.at(-1)?.name ?? ''; }
App.vue
<script setup lang="ts">
import { publishProfile } from './profile';
import { latestName } from './archive';
publishProfile();
const archivedName = latestName();
</script>
<template>
<p>Archived name: {{ archivedName }}</p>
</template>
Bad
The archive retains the same object passed to publish. The owner then changes its name, retroactively changing the supposedly historical record to Grace. TypeScript’s Readonly parameter does not copy the object.
profile.ts
import { reactive } from 'vue';
import { publish } from './archive';
export function publishProfile(): void {
const profile = reactive({ name: 'Ada' });
publish(profile);
profile.name = 'Grace';
}
Good
Publishing a plain copy separates the archived Ada record from later edits of the reactive profile. The archive’s snapshot policy is now maintained.
profile.ts
import { reactive } from 'vue';
import { publish } from './archive';
export function publishProfile(): void {
const profile = reactive({ name: 'Ada' });
publish({ ...profile });
profile.name = 'Grace';
}
The Good files demonstrate the change described above; other diagnostics can still apply to the complete project.
vize:croquis/cf/non-reactive-provide
A provided value is not reactive, so descendants will not see updates.
Default severity: context-dependent
Applies to: Analyzed component graph and the supported facts described below
Automatic fix: None; review related files and apply the repair
Options: No per-code options; supported CLI findings accept severity overrides
import { defineConfig } from "@vizejs/vite-plugin/vite-plus";
export default defineConfig({
lint: {
vize: { preset: "incremental", crossFile: true,
rules: { "croquis/cf/non-reactive-provide": "warn" },
},
},
});
vp run lint
Shared project files
Use these unchanged files in both Bad and Good. Install the imported packages in the project: Vue, plus vue-router or Pinia where shown. Follow any version-specific support note. The entry root makes the component relationship explicit.
main.ts
import { createApp } from "vue";
import Root from "./ThemeProvider.vue";
createApp(Root).mount("#app");
index.html
<div id="app"></div>
<script type="module" src="/main.ts"></script>
Bad
ThemeProvider.vue provides a plain object. Mutating that object's fields does not give the injected consumer a Vue reactive dependency.
keys/theme.ts
export const ThemeKey = Symbol("theme");
ThemeProvider.vue
<script setup lang="ts">
import { provide } from "vue";
import ThemeLabel from "./ThemeLabel.vue";
import { ThemeKey } from "./keys/theme";
const theme = { color: "blue" };
provide(ThemeKey, theme);
</script>
<template>
<ThemeLabel />
</template>
ThemeLabel.vue
<script setup lang="ts">
import { inject } from "vue";
import { ThemeKey } from "./keys/theme";
const theme = inject(ThemeKey);
</script>
Good
The provider wraps the theme in ref; the same injected reference can track later changes.
keys/theme.ts
export const ThemeKey = Symbol("theme");
ThemeProvider.vue
<script setup lang="ts">
import { provide, ref } from "vue";
import ThemeLabel from "./ThemeLabel.vue";
import { ThemeKey } from "./keys/theme";
const theme = ref({ color: "blue" });
provide(ThemeKey, theme);
</script>
<template>
<ThemeLabel />
</template>
ThemeLabel.vue
<script setup lang="ts">
import { inject } from "vue";
import { ThemeKey } from "./keys/theme";
const theme = inject(ThemeKey);
</script>
The Good files demonstrate the change described above; other diagnostics can still apply to the complete project.
vize:croquis/cf/non-unique-id
An element id inside a loop is not unique per item.
Default severity: error
Applies to: Analyzed component graph and the supported facts described below
Automatic fix: None; review related files and apply the repair
Options: No per-code options; supported CLI findings accept severity overrides
import { defineConfig } from "@vizejs/vite-plugin/vite-plus";
export default defineConfig({
lint: {
vize: { preset: "incremental", crossFile: true,
rules: { "croquis/cf/non-unique-id": "warn" },
},
},
});
vp run lint
Shared project files
Use these unchanged files in both Bad and Good. Install the imported packages in the project: Vue, plus vue-router or Pinia where shown. Follow any version-specific support note. The entry root makes the component relationship explicit.
main.ts
import { createApp } from "vue";
import Root from "./ResultsList.vue";
createApp(Root).mount("#app");
index.html
<div id="app"></div>
<script type="module" src="/main.ts"></script>
Bad
Every v-for iteration renders the same literal result-title ID; the loop's key does not make DOM IDs unique.
ResultsList.vue
<script setup lang="ts">
const results = [{ id: "first", title: "First result" }, { id: "second", title: "Second result" }];
</script>
<template>
<article v-for="result in results" :key="result.id">
<h2 id="result-title">{{ result.title }}</h2>
</article>
</template>
Good
The heading ID includes the result's stable ID, producing a distinct document identifier for each item.
ResultsList.vue
<script setup lang="ts">
const results = [{ id: "first", title: "First result" }, { id: "second", title: "Second result" }];
</script>
<template>
<article v-for="result in results" :key="result.id">
<h2 :id="`result-${result.id}-title`">{{ result.title }}</h2>
</article>
</template>
The Good files demonstrate the change described above; other diagnostics can still apply to the complete project.
vize:croquis/cf/object-identity-comparison
A reactive object is compared by identity, which changes across unwraps.
Default severity: Not emitted
Applies to: Analyzed component graph and the supported facts described below
Automatic fix: None; review related files and apply the repair
Options: No per-code options; supported CLI findings accept severity overrides
This is a published diagnostic contract without a current producer. The Bad/Good scenario below explains the risk and repair; no flag currently makes this code trigger.
The example assumes IDs uniquely identify records. Comparing two references to the same reactive proxy remains valid; this contract has no current producer.
Shared project files
Use these unchanged files in both Bad and Good. Install the imported packages in the project: Vue, plus vue-router or Pinia where shown. Follow any version-specific support note. The entry root makes the component relationship explicit.
main.ts
import { createApp } from 'vue';
import App from './App.vue';
createApp(App).mount('#app');
index.html
<!doctype html>
<html lang="en"><head><meta charset="UTF-8"><title>Contract scenario</title></head>
<body><div id="app"></div><script type="module" src="/main.ts"></script></body></html>
user.ts
import { reactive } from 'vue';
export function makeUser() {
const raw = { id: 7, name: 'Ada' };
return { raw, proxy: reactive(raw) };
}
Bad
proxy === raw compares wrapper identity, so it is false even though both represent the same user record. The application intended record identity, not object-wrapper identity.
App.vue
<script setup lang="ts">
import { makeUser } from './user';
const { raw, proxy } = makeUser();
const sameRecord = proxy === raw;
</script>
<template>
<p>Same record: {{ sameRecord }}</p>
</template>
Good
Comparing the stable record id answers the intended question without depending on whether the object is raw or proxied.
App.vue
<script setup lang="ts">
import { makeUser } from './user';
const { raw, proxy } = makeUser();
const sameRecord = proxy.id === raw.id;
</script>
<template>
<p>Same record: {{ sameRecord }}</p>
</template>
The Good files demonstrate the change described above; other diagnostics can still apply to the complete project.
vize:croquis/cf/pinia-getter
A Pinia getter is read without storeToRefs, so it will not stay reactive.
Default severity: Not emitted
Applies to: Analyzed component graph and the supported facts described below
Automatic fix: None; review related files and apply the repair
Options: No per-code options; supported CLI findings accept severity overrides
This is a published diagnostic contract without a current producer. The Bad/Good scenario below explains the risk and repair; no flag currently makes this code trigger.
Pinia must be installed, and main.ts installs its plugin before mounting. Reading store.doubled directly inside a tracked computation or template is valid; the defect here is taking a plain snapshot. This contract currently has no producer.
Shared project files
Use these unchanged files in both Bad and Good. Install the imported packages in the project: Vue, plus vue-router or Pinia where shown. Follow any version-specific support note. The entry root makes the component relationship explicit.
main.ts
import { createApp } from 'vue';
import { createPinia } from 'pinia';
import App from './App.vue';
createApp(App).use(createPinia()).mount('#app');
index.html
<!doctype html>
<html lang="en"><head><meta charset="UTF-8"><title>Contract scenario</title></head>
<body><div id="app"></div><script type="module" src="/main.ts"></script></body></html>
counter-store.ts
import { defineStore } from 'pinia';
export const useCounterStore = defineStore('counter', {
state: () => ({ count: 0 }),
getters: { doubled: state => state.count * 2 },
});
Bad
const doubled = store.doubled copies the getter’s current number during setup. The copied number does not follow later store.count updates.
App.vue
<script setup lang="ts">
import { useCounterStore } from './counter-store';
const store = useCounterStore();
const doubled = store.doubled;
</script>
<template>
<button @click="store.count++">{{ store.count }}</button><p>{{ doubled }}</p>
</template>
Good
storeToRefs(store) supplies a reactive getter ref that can be destructured and unwrapped by the template while staying connected to the store.
App.vue
<script setup lang="ts">
import { storeToRefs } from 'pinia';
import { useCounterStore } from './counter-store';
const store = useCounterStore();
const { doubled } = storeToRefs(store);
</script>
<template>
<button @click="store.count++">{{ store.count }}</button><p>{{ doubled }}</p>
</template>
The Good files demonstrate the change described above; other diagnostics can still apply to the complete project.
vize:croquis/cf/prop-type-mismatch
A passed prop value does not match the declared type.
Default severity: error
Applies to: Analyzed component graph and the supported facts described below
Automatic fix: None; review related files and apply the repair
Options: No per-code options; supported CLI findings accept severity overrides
The experimental Rust CrossFileAnalyzer has a producer for this code. The CLI pass does not emit this individual code; configuring its ID does not enable that Rust pass. These scenarios describe the analyzer's supported graph/facts, not a Vite+ promise.
Shared project files
Use these unchanged files in both Bad and Good. Install the imported packages in the project: Vue, plus vue-router or Pinia where shown. Follow any version-specific support note. The entry root makes the component relationship explicit.
main.ts
import { createApp } from "vue";
import Root from "./App.vue";
createApp(Root).mount("#app");
index.html
<div id="app"></div>
<script type="module" src="/main.ts"></script>
Bad
The parent passes the numeric expression 42 to the resolved child's title: string prop.
App.vue
<script setup lang="ts">
import Child from "./Child.vue";
</script>
<template><Child :title="42" /></template>
Child.vue
<script setup lang="ts">
const props = defineProps<{ title: string }>();
</script>
<template><p>{{ props.title }}</p></template>
Good
The literal title="Hello" supplies a string matching the child's declaration.
App.vue
<script setup lang="ts">
import Child from "./Child.vue";
</script>
<template><Child title="Hello" /></template>
Child.vue
<script setup lang="ts">
const props = defineProps<{ title: string }>();
</script>
<template><p>{{ props.title }}</p></template>
The Good files demonstrate the change described above; other diagnostics can still apply to the complete project.
vize:croquis/cf/provide-inject-type
A provided value and its inject do not have the same type.
Default severity: warning
Applies to: Analyzed component graph and the supported facts described below
Automatic fix: None; review related files and apply the repair
Options: No per-code options; supported CLI findings accept severity overrides
import { defineConfig } from "@vizejs/vite-plugin/vite-plus";
export default defineConfig({
lint: {
vize: { preset: "incremental", crossFile: true,
rules: { "croquis/cf/provide-inject-type": "warn" },
},
},
});
vp run lint
This check compares explicit provider/consumer type annotations, not inferred literal value types. Keep the provider's as string annotation in this example.
Shared project files
Use these unchanged files in both Bad and Good. Install the imported packages in the project: Vue, plus vue-router or Pinia where shown. Follow any version-specific support note. The entry root makes the component relationship explicit.
main.ts
import { createApp } from "vue";
import Root from "./App.vue";
createApp(Root).mount("#app");
index.html
<div id="app"></div>
<script type="module" src="/main.ts"></script>
Bad
The provider explicitly annotates title as string, while the descendant requests inject<number> for the same key.
App.vue
<script setup lang="ts">
import { provide } from "vue";
import Child from "./Child.vue";
provide("title", "Hello" as string);
</script>
<template><Child /></template>
Child.vue
<script setup lang="ts">
import { inject } from "vue";
const title = inject<number>("title");
</script>
<template><p>Content</p></template>
Good
The consumer's explicit inject<string> agrees with the provider annotation. Keep as string: this producer compares explicit annotations, not inferred literal types.
App.vue
<script setup lang="ts">
import { provide } from "vue";
import Child from "./Child.vue";
provide("title", "Hello" as string);
</script>
<template><Child /></template>
Child.vue
<script setup lang="ts">
import { inject } from "vue";
const title = inject<string>("title");
</script>
<template><p>Content</p></template>
The Good files demonstrate the change described above; other diagnostics can still apply to the complete project.
vize:croquis/cf/provide-without-symbol
provide uses a plain key instead of an InjectionKey symbol.
Default severity: warning
Applies to: Analyzed component graph and the supported facts described below
Automatic fix: None; review related files and apply the repair
Options: No per-code options; supported CLI findings accept severity overrides
import { defineConfig } from "@vizejs/vite-plugin/vite-plus";
export default defineConfig({
lint: {
vize: { preset: "incremental", crossFile: true,
rules: { "croquis/cf/provide-without-symbol": "warn" },
},
},
});
vp run lint
Shared project files
Use these unchanged files in both Bad and Good. Install the imported packages in the project: Vue, plus vue-router or Pinia where shown. Follow any version-specific support note. The entry root makes the component relationship explicit.
main.ts
import { createApp } from "vue";
import Root from "./ThemeProvider.vue";
createApp(Root).mount("#app");
index.html
<div id="app"></div>
<script type="module" src="/main.ts"></script>
Bad
Both components use the string "theme"; unrelated features can accidentally reuse that key.
ThemeProvider.vue
<script setup lang="ts">
import { provide, ref } from "vue";
import ThemeLabel from "./ThemeLabel.vue";
const theme = ref({ color: "blue" });
provide("theme", theme);
</script>
<template>
<ThemeLabel />
</template>
ThemeLabel.vue
<script setup lang="ts">
import { inject } from "vue";
const theme = inject("theme");
</script>
Good
Export one typed ThemeKey symbol and import that same value at both provide and inject sites. Creating separate symbols with the same description would not connect them.
ThemeProvider.vue
<script setup lang="ts">
import { provide, ref } from "vue";
import ThemeLabel from "./ThemeLabel.vue";
import { ThemeKey, type Theme } from "./keys/theme";
const theme = ref<Theme>({ color: "blue" });
provide(ThemeKey, theme);
</script>
<template>
<ThemeLabel />
</template>
ThemeLabel.vue
<script setup lang="ts">
import { inject } from "vue";
import { ThemeKey } from "./keys/theme";
const theme = inject(ThemeKey);
</script>
keys/theme.ts
import type { InjectionKey, Ref } from "vue";
export interface Theme {
color: string;
}
export const ThemeKey: InjectionKey<Ref<Theme>> = Symbol("theme");
The Good files demonstrate the change described above; other diagnostics can still apply to the complete project.
vize:croquis/cf/reactive-export
Reactive state is exported from the module.
Default severity: Not emitted
Applies to: Analyzed component graph and the supported facts described below
Automatic fix: None; review related files and apply the repair
Options: No per-code options; supported CLI findings accept severity overrides
This is a published diagnostic contract without a current producer. The Bad/Good scenario below explains the risk and repair; no flag currently makes this code trigger.
Intentional shared application stores may export reactive state. This scenario requires isolated state and does not claim every reactive export is invalid. No current producer emits this contract.
Shared project files
Use these unchanged files in both Bad and Good. Install the imported packages in the project: Vue, plus vue-router or Pinia where shown. Follow any version-specific support note. The entry root makes the component relationship explicit.
main.ts
import { createApp } from 'vue';
import App from './App.vue';
createApp(App).mount('#app');
index.html
<!doctype html>
<html lang="en"><head><meta charset="UTF-8"><title>Contract scenario</title></head>
<body><div id="app"></div><script type="module" src="/main.ts"></script></body></html>
Bad
The module exports one initialized reactive object, so every importer receives the same count. In an SSR module shared between requests, this defeats the example’s per-instance/request state isolation.
state.ts
import { reactive } from 'vue';
export const state = reactive({ count: 0 });
App.vue
<script setup lang="ts">
import { state } from './state';
</script>
<template>
<button @click="state.count++">{{ state.count }}</button>
</template>
Good
The module exports a factory, and App invokes it inside setup. Each instance obtains a fresh reactive count rather than the exported singleton.
state.ts
import { reactive } from 'vue';
export function createState() { return reactive({ count: 0 }); }
App.vue
<script setup lang="ts">
import { createState } from './state';
const state = createState();
</script>
<template>
<button @click="state.count++">{{ state.count }}</button>
</template>
The Good files demonstrate the change described above; other diagnostics can still apply to the complete project.
vize:croquis/cf/reactivity-outside-setup
A reactive API is called outside setup.
Default severity: Not emitted
Applies to: Analyzed component graph and the supported facts described below
Automatic fix: None; review related files and apply the repair
Options: No per-code options; supported CLI findings accept severity overrides
This is a published diagnostic contract without a current producer. The Bad/Good scenario below explains the risk and repair; no flag currently makes this code trigger.
Vue permits ref/reactive/computed outside component setup. The risk here is unwanted ownership/sharing under an explicit instance-isolation policy, not API illegality. This contract has no current producer.
Shared project files
Use these unchanged files in both Bad and Good. Install the imported packages in the project: Vue, plus vue-router or Pinia where shown. Follow any version-specific support note. The entry root makes the component relationship explicit.
main.ts
import { createApp } from 'vue';
import App from './App.vue';
createApp(App).mount('#app');
index.html
<!doctype html>
<html lang="en"><head><meta charset="UTF-8"><title>Contract scenario</title></head>
<body><div id="app"></div><script type="module" src="/main.ts"></script></body></html>
App.vue
<script setup lang="ts">
import Counter from './Counter.vue';
</script>
<template>
<Counter /><Counter />
</template>
Counter.vue
<script setup lang="ts">
import { useCounter } from './use-counter';
const { count, doubled } = useCounter();
</script>
<template>
<button @click="count++">{{ count }}</button><p>{{ doubled }}</p>
</template>
Bad
Both reactive APIs run while the module loads. The two Counter instances therefore share one ref and computed value despite the intended independent counters.
use-counter.ts
import { computed, ref } from 'vue';
const count = ref(0);
const doubled = computed(() => count.value * 2);
export function useCounter() { return { count, doubled }; }
Good
useCounter creates the ref and computed synchronously inside each component setup call, giving each widget its own state and tracked derivation.
use-counter.ts
import { computed, ref } from 'vue';
export function useCounter() {
const count = ref(0);
const doubled = computed(() => count.value * 2);
return { count, doubled };
}
The Good files demonstrate the change described above; other diagnostics can still apply to the complete project.
vize:croquis/cf/reassignment-breaks-reactivity
Reassigning a reactive binding replaces it with a plain value.
Default severity: error
Applies to: Analyzed component graph and the supported facts described below
Automatic fix: None; review related files and apply the repair
Options: No per-code options; supported CLI findings accept severity overrides
import { defineConfig } from "@vizejs/vite-plugin/vite-plus";
export default defineConfig({
lint: {
vize: { preset: "incremental", crossFile: true,
rules: { "croquis/cf/reassignment-breaks-reactivity": "warn" },
},
},
});
vp run lint
Shared project files
Use these unchanged files in both Bad and Good. Install the imported packages in the project: Vue, plus vue-router or Pinia where shown. Follow any version-specific support note. The entry root makes the component relationship explicit.
main.ts
import { createApp } from "vue";
import Root from "./UserPage.vue";
createApp(Root).mount("#app");
index.html
<div id="app"></div>
<script type="module" src="/main.ts"></script>
Bad
The child creates a prop ref and then overwrites the variable with props.user, discarding that ref connection.
UserPage.vue
<script setup lang="ts">
import { reactive } from "vue";
import UserSummary from "./UserSummary.vue";
const user = reactive({ name: "Ada" });
</script>
<template>
<UserSummary :user="user" />
</template>
UserSummary.vue
<script setup lang="ts">
import { toRef } from "vue";
const props = defineProps<{ user: { name: string } }>();
let user = toRef(props, "user");
user = props.user;
</script>
Good
Keep the toRef in a const binding and remove the reassignment that replaces it.
UserPage.vue
<script setup lang="ts">
import { reactive } from "vue";
import UserSummary from "./UserSummary.vue";
const user = reactive({ name: "Ada" });
</script>
<template>
<UserSummary :user="user" />
</template>
UserSummary.vue
<script setup lang="ts">
import { toRef } from "vue";
const props = defineProps<{ user: { name: string } }>();
const user = toRef(props, "user");
</script>
The Good files demonstrate the change described above; other diagnostics can still apply to the complete project.
vize:croquis/cf/reference-escapes-scope
A reactive reference escapes the scope that owns its lifetime.
Default severity: Not emitted
Applies to: Analyzed component graph and the supported facts described below
Automatic fix: None; review related files and apply the repair
Options: No per-code options; supported CLI findings accept severity overrides
This is a published diagnostic contract without a current producer. The Bad/Good scenario below explains the risk and repair; no flag currently makes this code trigger.
Refs may legitimately be returned from composables or shared across scopes. This example explicitly requires a snapshot cache; it does not claim that unmount invalidates a ref. No current producer emits this contract.
Shared project files
Use these unchanged files in both Bad and Good. Install the imported packages in the project: Vue, plus vue-router or Pinia where shown. Follow any version-specific support note. The entry root makes the component relationship explicit.
main.ts
import { createApp } from 'vue';
import App from './App.vue';
createApp(App).mount('#app');
index.html
<!doctype html>
<html lang="en"><head><meta charset="UTF-8"><title>Contract scenario</title></head>
<body><div id="app"></div><script type="module" src="/main.ts"></script></body></html>
saved.ts
import type { Ref } from 'vue';
let saved: Ref<number> | number | undefined;
export function remember(value: Ref<number> | number): void { saved = value; }
export function remembered(): Ref<number> | number | undefined { return saved; }
Bad
The process-level cache retains the component’s live count ref. It can keep that instance state reachable after unmount and can observe later edits, although this cache is intended to store a snapshot.
App.vue
<script setup lang="ts">
import { ref } from 'vue';
import { remember } from './saved';
const count = ref(0);
remember(count);
</script>
<template>
<button @click="count++">{{ count }}</button>
</template>
Good
The cache receives the current plain number, so it keeps a snapshot without retaining the component-owned ref.
App.vue
<script setup lang="ts">
import { ref } from 'vue';
import { remember } from './saved';
const count = ref(0);
remember(count.value);
</script>
<template>
<button @click="count++">{{ count }}</button>
</template>
The Good files demonstrate the change described above; other diagnostics can still apply to the complete project.
vize:croquis/cf/setup-context-violation
Setup context is used in a way Vue does not allow.
Default severity: context-dependent
Applies to: Analyzed component graph and the supported facts described below
Automatic fix: None; review related files and apply the repair
Options: No per-code options; supported CLI findings accept severity overrides
The experimental Rust CrossFileAnalyzer has a producer for this code. The CLI pass does not emit this individual code; configuring its ID does not enable that Rust pass. These scenarios describe the analyzer's supported graph/facts, not a Vite+ promise.
Shared project files
Use these unchanged files in both Bad and Good. Install the imported packages in the project: Vue, plus vue-router or Pinia where shown. Follow any version-specific support note. The entry root makes the component relationship explicit.
main.ts
import { createApp } from "vue";
import Root from "./App.vue";
createApp(Root).mount("#app");
index.html
<div id="app"></div>
<script type="module" src="/main.ts"></script>
Bad
ref(0) is created at normal script module scope, outside the per-instance setup context represented by this analyzer scenario.
App.vue
<script lang="ts">
import { ref } from "vue";
const count = ref(0);
export default {};
</script>
<template><p>Count</p></template>
Good
Move the binding into script setup, where each component instance owns its count and the template can read it.
App.vue
<script setup lang="ts">
import { ref } from "vue";
const count = ref(0);
</script>
<template><p>{{ count }}</p></template>
The Good files demonstrate the change described above; other diagnostics can still apply to the complete project.
vize:croquis/cf/shallow-deep-access
A deep property of a shallowReactive or shallowRef value is read as if it were tracked.
Default severity: Not emitted
Applies to: Analyzed component graph and the supported facts described below
Automatic fix: None; review related files and apply the repair
Options: No per-code options; supported CLI findings accept severity overrides
This is a published diagnostic contract without a current producer. The Bad/Good scenario below explains the risk and repair; no flag currently makes this code trigger.
Shared project files
Use these unchanged files in both Bad and Good. Install the imported packages in the project: Vue, plus vue-router or Pinia where shown. Follow any version-specific support note. The entry root makes the component relationship explicit.
main.ts
import { createApp } from 'vue';
import App from './App.vue';
createApp(App).mount('#app');
index.html
<!doctype html>
<html lang="en"><head><meta charset="UTF-8"><title>Contract scenario</title></head>
<body><div id="app"></div><script type="module" src="/main.ts"></script></body></html>
App.vue
<script setup lang="ts">
import { makeProfile } from './profile';
const profile = makeProfile();
</script>
<template>
<p>{{ profile.user.name }}</p><button @click="profile.user.name = 'Grace'">Rename</button>
</template>
Bad
shallowReactive tracks the root user property but leaves the nested object raw. Changing profile.user.name does not notify the template as a tracked deep mutation.
profile.ts
import { shallowReactive } from 'vue';
export function makeProfile() { return shallowReactive({ user: { name: 'Ada' } }); }
Good
Deep reactive wraps the nested user object, so the same name assignment can trigger the displayed name’s update.
profile.ts
import { reactive } from 'vue';
export function makeProfile() { return reactive({ user: { name: 'Ada' } }); }
The Good files demonstrate the change described above; other diagnostics can still apply to the complete project.
vize:croquis/cf/spread-breaks-reactivity
Spreading a reactive object copies its values and drops the tracking.
Default severity: error
Applies to: Analyzed component graph and the supported facts described below
Automatic fix: None; review related files and apply the repair
Options: No per-code options; supported CLI findings accept severity overrides
import { defineConfig } from "@vizejs/vite-plugin/vite-plus";
export default defineConfig({
lint: {
vize: { preset: "incremental", crossFile: true,
rules: { "croquis/cf/spread-breaks-reactivity": "warn" },
},
},
});
vp run lint
Shared project files
Use these unchanged files in both Bad and Good. Install the imported packages in the project: Vue, plus vue-router or Pinia where shown. Follow any version-specific support note. The entry root makes the component relationship explicit.
main.ts
import { createApp } from "vue";
import Root from "./UserPage.vue";
createApp(Root).mount("#app");
index.html
<div id="app"></div>
<script type="module" src="/main.ts"></script>
Bad
UserSummary.vue spreads props.user into a new object, taking a snapshot of the incoming reactive data.
UserPage.vue
<script setup lang="ts">
import { reactive } from "vue";
import UserSummary from "./UserSummary.vue";
const user = reactive({ name: "Ada", role: "admin" });
</script>
<template>
<UserSummary :user="user" />
</template>
UserSummary.vue
<script setup lang="ts">
const props = defineProps<{ user: { name: string; role: string } }>();
const copiedUser = { ...props.user };
</script>
Good
toRef(props, "user") keeps a reference to the incoming prop instead of copying its fields.
UserPage.vue
<script setup lang="ts">
import { reactive } from "vue";
import UserSummary from "./UserSummary.vue";
const user = reactive({ name: "Ada", role: "admin" });
</script>
<template>
<UserSummary :user="user" />
</template>
UserSummary.vue
<script setup lang="ts">
import { toRef } from "vue";
const props = defineProps<{ user: { name: string; role: string } }>();
const user = toRef(props, "user");
</script>
The Good files demonstrate the change described above; other diagnostics can still apply to the complete project.
vize:croquis/cf/suspense-no-fallback
<Suspense> has no fallback content.
Default severity: Not emitted
Applies to: Analyzed component graph and the supported facts described below
Automatic fix: None; review related files and apply the repair
Options: No per-code options; supported CLI findings accept severity overrides
This is a published diagnostic contract without a current producer. The Bad/Good scenario below explains the risk and repair; no flag currently makes this code trigger.
Suspense without a fallback is valid Vue syntax. This is a chosen loading-UI convention, not a compiler error; the published contract has no current producer.
Shared project files
Use these unchanged files in both Bad and Good. Install the imported packages in the project: Vue, plus vue-router or Pinia where shown. Follow any version-specific support note. The entry root makes the component relationship explicit.
main.ts
import { createApp } from 'vue';
import App from './App.vue';
createApp(App).mount('#app');
index.html
<!doctype html>
<html lang="en"><head><meta charset="UTF-8"><title>Contract scenario</title></head>
<body><div id="app"></div><script type="module" src="/main.ts"></script></body></html>
AsyncCard.vue
<script setup lang="ts">
const message = await Promise.resolve('Ready');
</script>
<template>
<p>{{ message }}</p>
</template>
Bad
The Suspense boundary has an async child but no fallback content, leaving no loading content for this example’s pending state.
App.vue
<script setup lang="ts">
import { Suspense } from 'vue';
import AsyncCard from './AsyncCard.vue';
</script>
<template>
<Suspense><AsyncCard /></Suspense>
</template>
Good
The #fallback slot supplies an explicit loading paragraph until the async child resolves.
App.vue
<script setup lang="ts">
import { Suspense } from 'vue';
import AsyncCard from './AsyncCard.vue';
</script>
<template>
<Suspense><AsyncCard /><template #fallback><p>Loading…</p></template></Suspense>
</template>
The Good files demonstrate the change described above; other diagnostics can still apply to the complete project.
vize:croquis/cf/template-ref-timing
A template ref is read before the component is mounted.
Default severity: Not emitted
Applies to: Analyzed component graph and the supported facts described below
Automatic fix: None; review related files and apply the repair
Options: No per-code options; supported CLI findings accept severity overrides
This is a published diagnostic contract without a current producer. The Bad/Good scenario below explains the risk and repair; no flag currently makes this code trigger.
Shared project files
Use these unchanged files in both Bad and Good. Install the imported packages in the project: Vue, plus vue-router or Pinia where shown. Follow any version-specific support note. The entry root makes the component relationship explicit.
main.ts
import { createApp } from 'vue';
import App from './App.vue';
createApp(App).mount('#app');
index.html
<!doctype html>
<html lang="en"><head><meta charset="UTF-8"><title>Contract scenario</title></head>
<body><div id="app"></div><script type="module" src="/main.ts"></script></body></html>
focus-input.ts
export function focusInput(input: HTMLInputElement | null): void { input?.focus(); }
Bad
Setup reads the template ref before mounting, when its value is still null. The optional focus call therefore performs no focus action.
App.vue
<script setup lang="ts">
import { ref } from 'vue';
import { focusInput } from './focus-input';
const input = ref<HTMLInputElement | null>(null);
focusInput(input.value);
</script>
<template>
<input ref="input" aria-label="Name" />
</template>
Good
onMounted defers the read until Vue has assigned the input element to the template ref, allowing the focus helper to act on it.
App.vue
<script setup lang="ts">
import { onMounted, ref } from 'vue';
import { focusInput } from './focus-input';
const input = ref<HTMLInputElement | null>(null);
onMounted(() => { focusInput(input.value); });
</script>
<template>
<input ref="input" aria-label="Name" />
</template>
The Good files demonstrate the change described above; other diagnostics can still apply to the complete project.
vize:croquis/cf/toraw-mutation
toRaw is used and the raw object is then mutated.
Default severity: Not emitted
Applies to: Analyzed component graph and the supported facts described below
Automatic fix: None; review related files and apply the repair
Options: No per-code options; supported CLI findings accept severity overrides
This is a published diagnostic contract without a current producer. The Bad/Good scenario below explains the risk and repair; no flag currently makes this code trigger.
Shared project files
Use these unchanged files in both Bad and Good. Install the imported packages in the project: Vue, plus vue-router or Pinia where shown. Follow any version-specific support note. The entry root makes the component relationship explicit.
main.ts
import { createApp } from 'vue';
import App from './App.vue';
createApp(App).mount('#app');
index.html
<!doctype html>
<html lang="en"><head><meta charset="UTF-8"><title>Contract scenario</title></head>
<body><div id="app"></div><script type="module" src="/main.ts"></script></body></html>
App.vue
<script setup lang="ts">
import { makeProfile, rename } from './profile';
const profile = makeProfile();
</script>
<template>
<p>{{ profile.name }}</p><button @click="rename(profile)">Rename</button>
</template>
Bad
rename obtains the raw target and writes raw.name, bypassing the proxy setter that would notify the displayed reactive name.
profile.ts
import { reactive, toRaw } from 'vue';
export function makeProfile() { return reactive({ name: 'Ada' }); }
export function rename(profile: { name: string }): void {
const raw = toRaw(profile);
raw.name = 'Grace';
}
Good
Writing profile.name through the passed reactive proxy preserves the same rename while notifying its dependents.
profile.ts
import { reactive } from 'vue';
export function makeProfile() { return reactive({ name: 'Ada' }); }
export function rename(profile: { name: string }): void {
profile.name = 'Grace';
}
The Good files demonstrate the change described above; other diagnostics can still apply to the complete project.
vize:croquis/cf/uncaught-error
A component can throw and no error boundary catches it.
Default severity: info
Applies to: Analyzed component graph and the supported facts described below
Automatic fix: None; review related files and apply the repair
Options: No per-code options; supported CLI findings accept severity overrides
import { defineConfig } from "@vizejs/vite-plugin/vite-plus";
export default defineConfig({
lint: {
vize: { preset: "incremental", crossFile: true,
rules: { "croquis/cf/uncaught-error": "warn" },
},
},
});
vp run lint
The current producer scans template expressions such as JSON.parse(input). It does not report a throw statement that exists only in the script block.
Shared project files
Use these unchanged files in both Bad and Good. Install the imported packages in the project: Vue, plus vue-router or Pinia where shown. Follow any version-specific support note. The entry root makes the component relationship explicit.
main.ts
import { createApp } from "vue";
import Root from "./App.vue";
createApp(Root).mount("#app");
index.html
<div id="app"></div>
<script type="module" src="/main.ts"></script>
Bad
The child's template calls JSON.parse on malformed input, and the reachable parent has no error-capture boundary.
App.vue
<script setup lang="ts">
import Child from "./Child.vue";
</script>
<template><Child /></template>
Child.vue
<script setup lang="ts">
const input = "{";
</script>
<template><button @click="JSON.parse(input)">Parse</button></template>
Good
The parent registers onErrorCaptured around that child. Returning false stops propagation; a production boundary should also present useful recovery UI.
App.vue
<script setup lang="ts">
import { onErrorCaptured } from "vue";
import Child from "./Child.vue";
onErrorCaptured(() => false);
</script>
<template><Child /></template>
Child.vue
<script setup lang="ts">
const input = "{";
</script>
<template><button @click="JSON.parse(input)">Parse</button></template>
The Good files demonstrate the change described above; other diagnostics can still apply to the complete project.
vize:croquis/cf/undeclared-emit
The component emits an event that is not declared.
Default severity: error
Applies to: Analyzed component graph and the supported facts described below
Automatic fix: None; review related files and apply the repair
Options: No per-code options; supported CLI findings accept severity overrides
The experimental Rust CrossFileAnalyzer has a producer for this code. The CLI pass does not emit this individual code; configuring its ID does not enable that Rust pass. These scenarios describe the analyzer's supported graph/facts, not a Vite+ promise.
Shared project files
Use these unchanged files in both Bad and Good. Install the imported packages in the project: Vue, plus vue-router or Pinia where shown. Follow any version-specific support note. The entry root makes the component relationship explicit.
main.ts
import { createApp } from "vue";
import Root from "./App.vue";
createApp(Root).mount("#app");
index.html
<div id="app"></div>
<script type="module" src="/main.ts"></script>
Bad
The child calls emit("save") but its defineEmits contract declares only cancel.
App.vue
<script setup lang="ts">
import Child from "./Child.vue";
</script>
<template><Child /></template>
Child.vue
<script setup lang="ts">
const emit = defineEmits<{ cancel: [] }>();
emit("save");
</script>
<template><p>Content</p></template>
Good
Declare save with its empty argument tuple so the emitted event agrees with the component contract.
App.vue
<script setup lang="ts">
import Child from "./Child.vue";
</script>
<template><Child /></template>
Child.vue
<script setup lang="ts">
const emit = defineEmits<{ save: [] }>();
emit("save");
</script>
<template><p>Content</p></template>
The Good files demonstrate the change described above; other diagnostics can still apply to the complete project.
vize:croquis/cf/undeclared-prop
A parent passes a prop the child does not declare.
Default severity: warning
Applies to: Analyzed component graph and the supported facts described below
Automatic fix: None; review related files and apply the repair
Options: No per-code options; supported CLI findings accept severity overrides
The experimental Rust CrossFileAnalyzer has a producer for this code. The CLI pass does not emit this individual code; configuring its ID does not enable that Rust pass. These scenarios describe the analyzer's supported graph/facts, not a Vite+ promise.
Shared project files
Use these unchanged files in both Bad and Good. Install the imported packages in the project: Vue, plus vue-router or Pinia where shown. Follow any version-specific support note. The entry root makes the component relationship explicit.
main.ts
import { createApp } from "vue";
import Root from "./App.vue";
createApp(Root).mount("#app");
index.html
<div id="app"></div>
<script type="module" src="/main.ts"></script>
Bad
The parent passes typo even though the resolved child declares only title. This analyzer convention is separate from Vue's general fallthrough-attribute behavior.
App.vue
<script setup lang="ts">
import Child from "./Child.vue";
</script>
<template><Child title="Hello" :typo="true" /></template>
Child.vue
<script setup lang="ts">
const props = defineProps<{ title: string }>();
</script>
<template><p>{{ props.title }}</p></template>
Good
Remove the unintended typo binding and retain the declared title prop.
App.vue
<script setup lang="ts">
import Child from "./Child.vue";
</script>
<template><Child title="Hello" /></template>
Child.vue
<script setup lang="ts">
const props = defineProps<{ title: string }>();
</script>
<template><p>{{ props.title }}</p></template>
The Good files demonstrate the change described above; other diagnostics can still apply to the complete project.
vize:croquis/cf/undefined-slot
A parent fills a slot the child does not expose.
Default severity: Not emitted
Applies to: Analyzed component graph and the supported facts described below
Automatic fix: None; review related files and apply the repair
Options: No per-code options; supported CLI findings accept severity overrides
This is a published diagnostic contract without a current producer. The Bad/Good scenario below explains the risk and repair; no flag currently makes this code trigger.
Shared project files
Use these unchanged files in both Bad and Good. Install the imported packages in the project: Vue, plus vue-router or Pinia where shown. Follow any version-specific support note. The entry root makes the component relationship explicit.
main.ts
import { createApp } from 'vue';
import App from './App.vue';
createApp(App).mount('#app');
index.html
<!doctype html>
<html lang="en"><head><meta charset="UTF-8"><title>Contract scenario</title></head>
<body><div id="app"></div><script type="module" src="/main.ts"></script></body></html>
Card.vue
<script setup lang="ts">
defineSlots<{ header(): unknown }>();
</script>
<template>
<article><header><slot name="header" /></header></article>
</template>
Bad
App supplies a footer slot, but Card declares and renders only header. The supplied Notice content has no matching slot outlet in this child.
App.vue
<script setup lang="ts">
import Card from './Card.vue';
</script>
<template>
<Card><template #footer>Notice</template></Card>
</template>
Good
App supplies header, matching both the child’s typed slot declaration and its rendered outlet, so Notice appears there.
App.vue
<script setup lang="ts">
import Card from './Card.vue';
</script>
<template>
<Card><template #header>Notice</template></Card>
</template>
The Good files demonstrate the change described above; other diagnostics can still apply to the complete project.
vize:croquis/cf/unhandled-event
A child emits an event that no parent handles.
Default severity: info
Applies to: Analyzed component graph and the supported facts described below
Automatic fix: None; review related files and apply the repair
Options: No per-code options; supported CLI findings accept severity overrides
The experimental Rust CrossFileAnalyzer has a producer for this code. The CLI pass does not emit this individual code; configuring its ID does not enable that Rust pass. These scenarios describe the analyzer's supported graph/facts, not a Vite+ promise.
Shared project files
Use these unchanged files in both Bad and Good. Install the imported packages in the project: Vue, plus vue-router or Pinia where shown. Follow any version-specific support note. The entry root makes the component relationship explicit.
main.ts
import { createApp } from "vue";
import Root from "./App.vue";
createApp(Root).mount("#app");
index.html
<div id="app"></div>
<script type="module" src="/main.ts"></script>
Bad
Child.vue emits save, but its immediate wrapper does not listen for it; component events do not automatically bubble through wrappers.
App.vue
<script setup lang="ts">
import Wrapper from "./Wrapper.vue";
</script>
<template><Wrapper /></template>
Wrapper.vue
<script setup lang="ts">
import Child from "./Child.vue";
</script>
<template><Child /></template>
Child.vue
<script setup lang="ts">
const emit = defineEmits<{ save: [] }>();
emit("save");
</script>
<template><p>Content</p></template>
Good
Wrapper.vue attaches a save listener to its direct child. The empty callback demonstrates handling for this rule, not a complete save implementation.
App.vue
<script setup lang="ts">
import Wrapper from "./Wrapper.vue";
</script>
<template><Wrapper /></template>
Wrapper.vue
<script setup lang="ts">
import Child from "./Child.vue";
</script>
<template><Child @save="() => {}" /></template>
Child.vue
<script setup lang="ts">
const emit = defineEmits<{ save: [] }>();
emit("save");
</script>
<template><p>Content</p></template>
The Good files demonstrate the change described above; other diagnostics can still apply to the complete project.
vize:croquis/cf/unmatched-inject
inject names a key that no ancestor provides.
Default severity: error / warning (with default)
Applies to: Analyzed component graph and the supported facts described below
Automatic fix: None; review related files and apply the repair
Options: No per-code options; supported CLI findings accept severity overrides
import { defineConfig } from "@vizejs/vite-plugin/vite-plus";
export default defineConfig({
lint: {
vize: { preset: "incremental", crossFile: true,
rules: { "croquis/cf/unmatched-inject": "warn" },
},
},
});
vp run lint
Shared project files
Use these unchanged files in both Bad and Good. Install the imported packages in the project: Vue, plus vue-router or Pinia where shown. Follow any version-specific support note. The entry root makes the component relationship explicit.
main.ts
import { createApp } from "vue";
import Root from "./App.vue";
createApp(Root).mount("#app");
index.html
<div id="app"></div>
<script type="module" src="/main.ts"></script>
Bad
ThemeLabel.vue injects ThemeKey, but its reachable App.vue ancestor never provides that key.
keys/theme.ts
import type { InjectionKey, Ref } from "vue";
export interface Theme {
color: string;
}
export const ThemeKey: InjectionKey<Ref<Theme>> = Symbol("theme");
App.vue
<script setup lang="ts">
import ThemeLabel from "./ThemeLabel.vue";
</script>
<template>
<ThemeLabel />
</template>
ThemeLabel.vue
<script setup lang="ts">
import { inject } from "vue";
import { ThemeKey } from "./keys/theme";
const theme = inject(ThemeKey);
</script>
Good
App.vue provides a reactive theme using the same exported ThemeKey, before rendering the descendant that injects it.
keys/theme.ts
import type { InjectionKey, Ref } from "vue";
export interface Theme {
color: string;
}
export const ThemeKey: InjectionKey<Ref<Theme>> = Symbol("theme");
App.vue
<script setup lang="ts">
import { provide, ref } from "vue";
import ThemeLabel from "./ThemeLabel.vue";
import { ThemeKey, type Theme } from "./keys/theme";
const theme = ref<Theme>({ color: "blue" });
provide(ThemeKey, theme);
</script>
<template>
<ThemeLabel />
</template>
ThemeLabel.vue
<script setup lang="ts">
import { inject } from "vue";
import { ThemeKey } from "./keys/theme";
const theme = inject(ThemeKey);
</script>
The Good files demonstrate the change described above; other diagnostics can still apply to the complete project.
vize:croquis/cf/unmatched-listener
A parent listens for an event the child does not emit.
Default severity: warning
Applies to: Analyzed component graph and the supported facts described below
Automatic fix: None; review related files and apply the repair
Options: No per-code options; supported CLI findings accept severity overrides
The experimental Rust CrossFileAnalyzer has a producer for this code. The CLI pass does not emit this individual code; configuring its ID does not enable that Rust pass. These scenarios describe the analyzer's supported graph/facts, not a Vite+ promise.
Shared project files
Use these unchanged files in both Bad and Good. Install the imported packages in the project: Vue, plus vue-router or Pinia where shown. Follow any version-specific support note. The entry root makes the component relationship explicit.
main.ts
import { createApp } from "vue";
import Root from "./App.vue";
createApp(Root).mount("#app");
index.html
<div id="app"></div>
<script type="module" src="/main.ts"></script>
Bad
The parent listens for save, while the resolved child declares only cancel.
App.vue
<script setup lang="ts">
import Child from "./Child.vue";
</script>
<template><Child @save="() => {}" /></template>
Child.vue
<script setup lang="ts">
const emit = defineEmits<{ cancel: [] }>();
</script>
<template><p>Content</p></template>
Good
The child declares and emits save, matching the parent's listener name.
App.vue
<script setup lang="ts">
import Child from "./Child.vue";
</script>
<template><Child @save="() => {}" /></template>
Child.vue
<script setup lang="ts">
const emit = defineEmits<{ save: [] }>();
emit("save");
</script>
<template><p>Content</p></template>
The Good files demonstrate the change described above; other diagnostics can still apply to the complete project.
vize:croquis/cf/unregistered-component
A template uses a component that is not registered or imported.
Default severity: error
Applies to: Analyzed component graph and the supported facts described below
Automatic fix: None; review related files and apply the repair
Options: No per-code options; supported CLI findings accept severity overrides
The experimental Rust CrossFileAnalyzer has a producer for this code. The CLI pass does not emit this individual code; configuring its ID does not enable that Rust pass. These scenarios describe the analyzer's supported graph/facts, not a Vite+ promise.
Shared project files
Use these unchanged files in both Bad and Good. Install the imported packages in the project: Vue, plus vue-router or Pinia where shown. Follow any version-specific support note. The entry root makes the component relationship explicit.
main.ts
import { createApp } from "vue";
import Root from "./App.vue";
createApp(Root).mount("#app");
index.html
<div id="app"></div>
<script type="module" src="/main.ts"></script>
Bad
A Child.vue file exists, but the parent neither imports nor otherwise registers Child for its template.
App.vue
<template><Child /></template>
Child.vue
<template><p>Child</p></template>
Good
Import Child in the parent's script setup so the template resolves the component binding.
App.vue
<script setup lang="ts">
import Child from "./Child.vue";
</script>
<template><Child /></template>
Child.vue
<template><p>Child</p></template>
The Good files demonstrate the change described above; other diagnostics can still apply to the complete project.
vize:croquis/cf/unresolved-import
An import does not resolve to a module.
Default severity: error
Applies to: Analyzed component graph and the supported facts described below
Automatic fix: None; review related files and apply the repair
Options: No per-code options; supported CLI findings accept severity overrides
The experimental Rust CrossFileAnalyzer has a producer for this code. The CLI pass does not emit this individual code; configuring its ID does not enable that Rust pass. These scenarios describe the analyzer's supported graph/facts, not a Vite+ promise.
Shared project files
Use these unchanged files in both Bad and Good. Install the imported packages in the project: Vue, plus vue-router or Pinia where shown. Follow any version-specific support note. The entry root makes the component relationship explicit.
main.ts
import { createApp } from "vue";
import Root from "./App.vue";
createApp(Root).mount("#app");
index.html
<div id="app"></div>
<script type="module" src="/main.ts"></script>
Bad
The parent imports ./Missing.vue, but the project contains Child.vue rather than that path.
App.vue
<script setup lang="ts">
import Child from "./Missing.vue";
</script>
<template><Child /></template>
Child.vue
<template><p>Child</p></template>
Good
Point the import at the existing ./Child.vue file, retaining the same template binding.
App.vue
<script setup lang="ts">
import Child from "./Child.vue";
</script>
<template><Child /></template>
Child.vue
<template><p>Child</p></template>
The Good files demonstrate the change described above; other diagnostics can still apply to the complete project.
vize:croquis/cf/unused-attrs
Fallthrough attributes are passed to a multi-root component that does not use them.
Default severity: info
Applies to: Analyzed component graph and the supported facts described below
Automatic fix: None; review related files and apply the repair
Options: No per-code options; supported CLI findings accept severity overrides
The experimental Rust CrossFileAnalyzer has a producer for this code. The CLI pass does not emit this individual code; configuring its ID does not enable that Rust pass. These scenarios describe the analyzer's supported graph/facts, not a Vite+ promise.
Shared project files
Use these unchanged files in both Bad and Good. Install the imported packages in the project: Vue, plus vue-router or Pinia where shown. Follow any version-specific support note. The entry root makes the component relationship explicit.
main.ts
import { createApp } from "vue";
import Root from "./App.vue";
createApp(Root).mount("#app");
index.html
<div id="app"></div>
<script type="module" src="/main.ts"></script>
Bad
The parent's tracking-code is neither consumed as a prop nor forwarded by the multi-root child.
App.vue
<script setup lang="ts">
import Child from "./Child.vue";
</script>
<template><Child tracking-code="notice" /></template>
Child.vue
<template><main>Content</main><aside>Help</aside></template>
Good
Binding $attrs on <main> gives that fallthrough attribute an explicit destination.
App.vue
<script setup lang="ts">
import Child from "./Child.vue";
</script>
<template><Child tracking-code="notice" /></template>
Child.vue
<template><main v-bind="$attrs">Content</main><aside>Help</aside></template>
The Good files demonstrate the change described above; other diagnostics can still apply to the complete project.
vize:croquis/cf/unused-emit
A declared emit is never used.
Default severity: warning
Applies to: Analyzed component graph and the supported facts described below
Automatic fix: None; review related files and apply the repair
Options: No per-code options; supported CLI findings accept severity overrides
The experimental Rust CrossFileAnalyzer has a producer for this code. The CLI pass does not emit this individual code; configuring its ID does not enable that Rust pass. These scenarios describe the analyzer's supported graph/facts, not a Vite+ promise.
Shared project files
Use these unchanged files in both Bad and Good. Install the imported packages in the project: Vue, plus vue-router or Pinia where shown. Follow any version-specific support note. The entry root makes the component relationship explicit.
main.ts
import { createApp } from "vue";
import Root from "./App.vue";
createApp(Root).mount("#app");
index.html
<div id="app"></div>
<script type="module" src="/main.ts"></script>
Bad
The child declares save but never calls the emitted-event function with that name.
App.vue
<script setup lang="ts">
import Child from "./Child.vue";
</script>
<template><Child /></template>
Child.vue
<script setup lang="ts">
const emit = defineEmits<{ save: [] }>();
</script>
<template><p>Content</p></template>
Good
The example calls emit("save"), making the declared event used. Real interactions should emit it when the corresponding action occurs.
App.vue
<script setup lang="ts">
import Child from "./Child.vue";
</script>
<template><Child /></template>
Child.vue
<script setup lang="ts">
const emit = defineEmits<{ save: [] }>();
emit("save");
</script>
<template><p>Content</p></template>
The Good files demonstrate the change described above; other diagnostics can still apply to the complete project.
vize:croquis/cf/unused-provide
A provided key is never injected.
Default severity: warning
Applies to: Analyzed component graph and the supported facts described below
Automatic fix: None; review related files and apply the repair
Options: No per-code options; supported CLI findings accept severity overrides
import { defineConfig } from "@vizejs/vite-plugin/vite-plus";
export default defineConfig({
lint: {
vize: { preset: "incremental", crossFile: true,
rules: { "croquis/cf/unused-provide": "warn" },
},
},
});
vp run lint
Shared project files
Use these unchanged files in both Bad and Good. Install the imported packages in the project: Vue, plus vue-router or Pinia where shown. Follow any version-specific support note. The entry root makes the component relationship explicit.
main.ts
import { createApp } from "vue";
import Root from "./App.vue";
createApp(Root).mount("#app");
index.html
<div id="app"></div>
<script type="module" src="/main.ts"></script>
keys/theme.ts
import type { InjectionKey, Ref } from "vue";
export interface Theme { color: string; }
export const ThemeKey: InjectionKey<Ref<Theme>> = Symbol("theme");
Bad
App.vue provides ThemeKey, but its rendered Dashboard.vue subtree has no consumer of that key.
App.vue
<script setup lang="ts">
import { provide, ref } from "vue";
import Dashboard from "./Dashboard.vue";
import { ThemeKey, type Theme } from "./keys/theme";
const theme = ref<Theme>({ color: "blue" });
provide(ThemeKey, theme);
</script>
<template>
<Dashboard />
</template>
Dashboard.vue
<template>
<h1>Dashboard</h1>
</template>
Good
The dashboard now renders ThemeLabel.vue, which injects the ancestor's exact ThemeKey identity.
App.vue
<script setup lang="ts">
import { provide, ref } from "vue";
import Dashboard from "./Dashboard.vue";
import { ThemeKey, type Theme } from "./keys/theme";
const theme = ref<Theme>({ color: "blue" });
provide(ThemeKey, theme);
</script>
<template>
<Dashboard />
</template>
Dashboard.vue
<script setup lang="ts">
import ThemeLabel from "./ThemeLabel.vue";
</script>
<template>
<ThemeLabel />
</template>
ThemeLabel.vue
<script setup lang="ts">
import { inject } from "vue";
import { ThemeKey } from "./keys/theme";
const theme = inject(ThemeKey);
</script>
The Good files demonstrate the change described above; other diagnostics can still apply to the complete project.
vize:croquis/cf/value-extraction-breaks-reactivity
Reading a reactive value out into a local drops later updates.
Default severity: error
Applies to: Analyzed component graph and the supported facts described below
Automatic fix: None; review related files and apply the repair
Options: No per-code options; supported CLI findings accept severity overrides
import { defineConfig } from "@vizejs/vite-plugin/vite-plus";
export default defineConfig({
lint: {
vize: { preset: "incremental", crossFile: true,
rules: { "croquis/cf/value-extraction-breaks-reactivity": "warn" },
},
},
});
vp run lint
Shared project files
Use these unchanged files in both Bad and Good. Install the imported packages in the project: Vue, plus vue-router or Pinia where shown. Follow any version-specific support note. The entry root makes the component relationship explicit.
main.ts
import { createApp } from "vue";
import Root from "./UserPage.vue";
createApp(Root).mount("#app");
index.html
<div id="app"></div>
<script type="module" src="/main.ts"></script>
Bad
Vue 3.5's reactive destructured item is read into itemSnapshot once; later prop replacement does not update that snapshot.
UserPage.vue
<script setup lang="ts">
import { reactive } from "vue";
import UserSummary from "./UserSummary.vue";
const user = reactive({ name: "Ada" });
</script>
<template>
<UserSummary :item="user" />
</template>
UserSummary.vue
<script setup lang="ts">
const { item } = defineProps<{ item: { name: string } }>();
const itemSnapshot = item;
</script>
Good
Read item inside computed, so Vue's reactive props-destructuring transform can track each evaluation.
UserPage.vue
<script setup lang="ts">
import { reactive } from "vue";
import UserSummary from "./UserSummary.vue";
const user = reactive({ name: "Ada" });
</script>
<template>
<UserSummary :item="user" />
</template>
UserSummary.vue
<script setup lang="ts">
import { computed } from "vue";
const { item } = defineProps<{ item: { name: string } }>();
const itemView = computed(() => item);
</script>
The Good files demonstrate the change described above; other diagnostics can still apply to the complete project.
vize:croquis/cf/watch-can-be-computed
A watcher only copies a value into state and can be a computed.
Default severity: Not emitted
Applies to: Analyzed component graph and the supported facts described below
Automatic fix: None; review related files and apply the repair
Options: No per-code options; supported CLI findings accept severity overrides
This is a published diagnostic contract without a current producer. The Bad/Good scenario below explains the risk and repair; no flag currently makes this code trigger.
This illustrates the published preference for purely derived state. Watchers remain appropriate for external effects or independently writable state; no current producer emits this contract.
Shared project files
Use these unchanged files in both Bad and Good. Install the imported packages in the project: Vue, plus vue-router or Pinia where shown. Follow any version-specific support note. The entry root makes the component relationship explicit.
main.ts
import { createApp } from 'vue';
import App from './App.vue';
createApp(App).mount('#app');
index.html
<!doctype html>
<html lang="en"><head><meta charset="UTF-8"><title>Contract scenario</title></head>
<body><div id="app"></div><script type="module" src="/main.ts"></script></body></html>
App.vue
<script setup lang="ts">
import { useDouble } from './use-double';
const { count, doubled } = useDouble();
</script>
<template>
<button @click="count++">{{ count }}</button><p>{{ doubled }}</p>
</template>
Bad
The watcher performs no external effect; it only keeps a second writable ref synchronized with twice count. This example has no independent writes to that derived value.
use-double.ts
import { ref, watch } from 'vue';
export function useDouble() {
const count = ref(0);
const doubled = ref(0);
watch(count, next => { doubled.value = next * 2; }, { immediate: true });
return { count, doubled };
}
Good
A computed getter expresses the same derivation directly and removes the manual synchronization and extra writable state.
use-double.ts
import { computed, ref } from 'vue';
export function useDouble() {
const count = ref(0);
const doubled = computed(() => count.value * 2);
return { count, doubled };
}
The Good files demonstrate the change described above; other diagnostics can still apply to the complete project.
vize:croquis/cf/watcheffect-async
watchEffect starts an async task and cannot clean up the previous run.
Default severity: error
Applies to: Analyzed component graph and the supported facts described below
Automatic fix: None; review related files and apply the repair
Options: No per-code options; supported CLI findings accept severity overrides
import { defineConfig } from "@vizejs/vite-plugin/vite-plus";
export default defineConfig({
lint: {
vize: { preset: "incremental", crossFile: true,
rules: { "croquis/cf/watcheffect-async": "warn" },
},
},
});
vp run lint
Shared project files
Use these unchanged files in both Bad and Good. Install the imported packages in the project: Vue, plus vue-router or Pinia where shown. Follow any version-specific support note. The entry root makes the component relationship explicit.
main.ts
import { createApp } from "vue";
import Root from "./SearchPage.vue";
createApp(Root).mount("#app");
index.html
<div id="app"></div>
<script type="module" src="/main.ts"></script>
api.ts
export interface Result { items: string[]; }
export async function load(query: string, options?: { signal?: AbortSignal }): Promise<Result> {
const response = await fetch(`/search?q=${encodeURIComponent(query)}`, options);
return response.json();
}
Bad
The async watchEffect mixes implicit dependency collection with an awaited request and no invalidation guard.
SearchPage.vue
<script setup lang="ts">
import { ref } from "vue";
import SearchResults from "./SearchResults.vue";
const query = ref("");
</script>
<template>
<SearchResults :query="query" />
</template>
SearchResults.vue
<script setup lang="ts">
import { load, type Result } from "./api";
import { ref, watchEffect } from "vue";
const props = defineProps<{ query: string }>();
const result = ref<Result | null>(null);
watchEffect(async () => {
result.value = await load(props.query);
});
</script>
Good
An explicit watch(() => props.query, ...) declares the source, registers request cleanup, and refuses a stale response after invalidation.
SearchPage.vue
<script setup lang="ts">
import { ref } from "vue";
import SearchResults from "./SearchResults.vue";
const query = ref("");
</script>
<template>
<SearchResults :query="query" />
</template>
SearchResults.vue
<script setup lang="ts">
import { load, type Result } from "./api";
import { ref, watch } from "vue";
const props = defineProps<{ query: string }>();
const result = ref<Result | null>(null);
watch(
() => props.query,
async (value, _oldValue, onCleanup) => {
const controller = new AbortController();
let active = true;
onCleanup(() => {
active = false;
controller.abort();
});
const next = await load(value, { signal: controller.signal });
if (active) result.value = next;
},
);
</script>
The Good files demonstrate the change described above; other diagnostics can still apply to the complete project.
vize:croquis/cf/watcher-outside-setup
watch or watchEffect is called outside setup.
Default severity: Not emitted
Applies to: Analyzed component graph and the supported facts described below
Automatic fix: None; review related files and apply the repair
Options: No per-code options; supported CLI findings accept severity overrides
This is a published diagnostic contract without a current producer. The Bad/Good scenario below explains the risk and repair; no flag currently makes this code trigger.
Module-scope watchers are valid when their owner keeps and calls a stop handle or intentionally gives them application lifetime. This example requires component-owned lifetimes; the contract has no current producer.
Shared project files
Use these unchanged files in both Bad and Good. Install the imported packages in the project: Vue, plus vue-router or Pinia where shown. Follow any version-specific support note. The entry root makes the component relationship explicit.
main.ts
import { createApp } from 'vue';
import App from './App.vue';
createApp(App).mount('#app');
index.html
<!doctype html>
<html lang="en"><head><meta charset="UTF-8"><title>Contract scenario</title></head>
<body><div id="app"></div><script type="module" src="/main.ts"></script></body></html>
App.vue
<script setup lang="ts">
import Observer from './Observer.vue';
</script>
<template>
<Observer /><Observer />
</template>
Observer.vue
<script setup lang="ts">
import { useObserver } from './use-observer';
const { count, observed } = useObserver();
</script>
<template>
<button @click="count++">{{ count }}</button><p>{{ observed }}</p>
</template>
Bad
The watcher is created at module load, outside either Observer’s setup, and both instances share its refs. It is not automatically stopped when a particular Observer unmounts.
use-observer.ts
import { ref, watch } from 'vue';
const count = ref(0);
const observed = ref(0);
watch(count, next => { observed.value = next; });
export function useObserver() { return { count, observed }; }
Good
Each synchronous setup call creates its own refs and watcher inside useObserver. Vue associates that watcher with the calling component’s lifetime.
use-observer.ts
import { ref, watch } from 'vue';
export function useObserver() {
const count = ref(0);
const observed = ref(0);
watch(count, next => { observed.value = next; });
return { count, observed };
}
The Good files demonstrate the change described above; other diagnostics can still apply to the complete project.