Vize

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 &lt;template&gt; Essential
vue/no-textarea-mustache Bad · Good error essential, happy-path, nuxt, ecosystem, opinionated No No source Disallow mustache interpolation in &lt;textarea&gt; 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 &lt;template&gt; 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 &lt;template v-for&gt; 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 &lt;component&gt; 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 &lt;transition&gt; 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 &lt;template&gt; 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 &lt;template&gt; 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 &lt;KeepAlive&gt; 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.

Rule / code Examples Current support
ecosystem/vue-router-unknown-route Bad · Good CLI project pass
ecosystem/vue-router-extra-param Bad · Good CLI project pass
ecosystem/vue-router-param-type Bad · Good CLI project pass
ecosystem/vue-router-missing-param Bad · Good CLI project pass
html/cross-component-nesting Bad · Good CLI project pass
vue/cross-file-attrs-fallthrough Bad · Good CLI project pass
vize:croquis/cf/array-mutation Bad · Good Contract only; no producer
vize:croquis/cf/async-boundary Bad · Good CLI project pass
vize:croquis/cf/async-no-suspense Bad · Good Library producer; required source fact missing
vize:croquis/cf/browser-api-ssr Bad · Good CLI project pass
vize:croquis/cf/circular-dep Bad · Good Contract only; no producer
vize:croquis/cf/circular-reactive-dependency Bad · Good CLI: tracked-graph scenario
vize:croquis/cf/closure-captures-reactive Bad · Good Contract only; no producer
vize:croquis/cf/composable-outside-setup Bad · Good Contract only; no producer
vize:croquis/cf/computed-side-effects Bad · Good Contract only; no producer
vize:croquis/cf/deep-import Bad · Good Contract only; no producer
vize:croquis/cf/destructuring-breaks-reactivity Bad · Good CLI project pass
vize:croquis/cf/di-outside-setup Bad · Good Contract only; no producer
vize:croquis/cf/dom-access-without-next-tick Bad · Good Contract only; no producer
vize:croquis/cf/duplicate-id Bad · Good CLI project pass
vize:croquis/cf/event-listener-leak Bad · Good Contract only; no producer
vize:croquis/cf/event-modifier Bad · Good Experimental library producer; not this CLI code
vize:croquis/cf/hydration-risk Bad · Good CLI project pass
vize:croquis/cf/inherit-attrs-unused Bad · Good Experimental library producer; not this CLI code
vize:croquis/cf/inject-without-symbol Bad · Good CLI project pass
vize:croquis/cf/injected-async-mutation-race Bad · Good CLI project pass
vize:croquis/cf/lifecycle-outside-setup Bad · Good Contract only; no producer
vize:croquis/cf/lifecycle-without-cleanup Bad · Good Experimental library producer; not this CLI code
vize:croquis/cf/missing-required-prop Bad · Good Experimental library producer; not this CLI code
vize:croquis/cf/missing-suspense Bad · Good Contract only; no producer
vize:croquis/cf/module-scope-reactive Bad · Good Contract only; no producer
vize:croquis/cf/multi-root-attrs Bad · Good Experimental library producer; not this CLI code
vize:croquis/cf/mutated-after-escape Bad · Good Contract only; no producer
vize:croquis/cf/non-reactive-provide Bad · Good CLI project pass
vize:croquis/cf/non-unique-id Bad · Good CLI project pass
vize:croquis/cf/object-identity-comparison Bad · Good Contract only; no producer
vize:croquis/cf/pinia-getter Bad · Good Contract only; no producer
vize:croquis/cf/prop-type-mismatch Bad · Good Experimental library producer; not this CLI code
vize:croquis/cf/provide-inject-type Bad · Good CLI project pass
vize:croquis/cf/provide-without-symbol Bad · Good CLI project pass
vize:croquis/cf/reactive-export Bad · Good Contract only; no producer
vize:croquis/cf/reactivity-outside-setup Bad · Good Contract only; no producer
vize:croquis/cf/reassignment-breaks-reactivity Bad · Good CLI project pass
vize:croquis/cf/reference-escapes-scope Bad · Good Contract only; no producer
vize:croquis/cf/setup-context-violation Bad · Good Experimental library producer; not this CLI code
vize:croquis/cf/shallow-deep-access Bad · Good Contract only; no producer
vize:croquis/cf/spread-breaks-reactivity Bad · Good CLI project pass
vize:croquis/cf/suspense-no-fallback Bad · Good Contract only; no producer
vize:croquis/cf/template-ref-timing Bad · Good Contract only; no producer
vize:croquis/cf/toraw-mutation Bad · Good Contract only; no producer
vize:croquis/cf/uncaught-error Bad · Good CLI project pass
vize:croquis/cf/undeclared-emit Bad · Good Experimental library producer; not this CLI code
vize:croquis/cf/undeclared-prop Bad · Good Experimental library producer; not this CLI code
vize:croquis/cf/undefined-slot Bad · Good Contract only; no producer
vize:croquis/cf/unhandled-event Bad · Good Experimental library producer; not this CLI code
vize:croquis/cf/unmatched-inject Bad · Good CLI project pass
vize:croquis/cf/unmatched-listener Bad · Good Experimental library producer; not this CLI code
vize:croquis/cf/unregistered-component Bad · Good Experimental library producer; not this CLI code
vize:croquis/cf/unresolved-import Bad · Good Experimental library producer; not this CLI code
vize:croquis/cf/unused-attrs Bad · Good Experimental library producer; not this CLI code
vize:croquis/cf/unused-emit Bad · Good Experimental library producer; not this CLI code
vize:croquis/cf/unused-provide Bad · Good CLI project pass
vize:croquis/cf/value-extraction-breaks-reactivity Bad · Good CLI project pass
vize:croquis/cf/watch-can-be-computed Bad · Good Contract only; no producer
vize:croquis/cf/watcheffect-async Bad · Good CLI project pass
vize:croquis/cf/watcher-outside-setup Bad · Good Contract only; no producer

Single-file examples

petite-vue/no-unsupported-directive

Disallow directives that petite-vue does not support

Bad · Good

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.

Implementation · All rules

petite-vue/valid-v-effect

Require v-effect to have a non-empty expression

Bad · Good

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.

Implementation · All rules

petite-vue/valid-v-scope

Require v-scope to bind an object literal

Bad · Good

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.

Implementation · All rules

vue/multi-word-component-names

Require component names to be multi-word

Bad · Good

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.

Implementation · All rules

vue/no-child-content

Disallow child content when using v-html or v-text

Bad · Good

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.

Implementation · All rules

vue/no-deprecated-filter

Disallow deprecated Vue 2 filter syntax using the pipe operator

Bad · Good

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.

Implementation · All rules

vue/no-deprecated-functional-template

Disallow the functional attribute on the SFC <template>

Bad · Good

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.

Implementation · All rules

vue/no-deprecated-html-element-is

Disallow the is attribute on native HTML elements

Bad · Good

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.

Implementation · All rules

vue/no-deprecated-inline-template

Disallow the deprecated inline-template attribute

Bad · Good

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.

Implementation · All rules

Disallow the tag prop on <router-link>

Bad · Good

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.

Implementation · All rules

vue/no-deprecated-scope-attribute

Disallow the deprecated scope attribute on <template>

Bad · Good

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.

Implementation · All rules

vue/no-deprecated-slot-attribute

Disallow the deprecated slot attribute

Bad · Good

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.

Implementation · All rules

vue/no-deprecated-slot-scope-attribute

Disallow the deprecated slot-scope attribute

Bad · Good

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.

Implementation · All rules

vue/no-deprecated-v-bind-sync

Disallow the deprecated .sync modifier on v-bind

Bad · Good

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.

Implementation · All rules

vue/no-deprecated-v-on-native-modifier

Disallow the deprecated .native modifier on v-on

Bad · Good

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.

Implementation · All rules

vue/no-deprecated-v-on-number-modifiers

Disallow deprecated numeric keyCode modifiers on v-on

Bad · Good

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.

Implementation · All rules

vue/no-dupe-v-else-if

Disallow duplicate conditions in v-if / v-else-if chains

Bad · Good

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.

Implementation · All rules

vue/no-duplicate-attributes

Disallow duplicate attributes on the same element

Bad · Good

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.

Implementation · All rules

vue/no-multiple-template-root

Disallow multiple root nodes in a template

Bad · Good

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.

Implementation · All rules

vue/no-mutating-props

Disallow mutating component props

Bad · Good

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.

Implementation · All rules

vue/no-reserved-component-names

Disallow the use of reserved names as component names

Bad · Good

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.

Implementation · All rules

vue/no-template-key

Disallow key attribute on <template>

Bad · Good

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.

Implementation · All rules

vue/no-textarea-mustache

Disallow mustache interpolation in <textarea>

Bad · Good

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.

Implementation · All rules

vue/no-unused-components

Disallow registering components that are not used inside templates

Bad · Good

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.

Implementation · All rules

vue/no-unused-vars

Disallow unused variable definitions in v-for and v-slot directives

Bad · Good

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.

Implementation · All rules

vue/no-use-v-if-with-v-for

Disallow using v-if on the same element as v-for

Bad · Good

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.

Implementation · All rules

vue/no-useless-template-attributes

Disallow useless attributes on <template> elements

Bad · Good

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.

Implementation · All rules

vue/no-v-for-template-key-on-child

Disallow key on the child of a <template v-for>

Bad · Good

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.

Implementation · All rules

vue/no-v-html

Warn against v-html to prevent XSS vulnerabilities

Bad · Good

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.

Implementation · All rules

vue/no-v-text-v-html-on-component

Disallow v-text / v-html on component elements

Bad · Good

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.

Implementation · All rules

vue/permitted-contents

Enforce HTML content model rules

Bad · Good

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.

Implementation · All rules

vue/require-component-is

Require v-bind:is on <component> elements

Bad · Good

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.

Implementation · All rules

vue/require-toggle-inside-transition

Require a toggle on the element wrapped by <transition>

Bad · Good

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.

Implementation · All rules

vue/require-v-for-key

Require v-bind:key with v-for directives

Bad · Good

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.

Implementation · All rules

vue/use-v-on-exact

Enforce .exact modifier on v-on when there are modifier-based handlers

Bad · Good

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.

Implementation · All rules

vue/valid-attribute-name

Require valid attribute names

Bad · Good

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.

Implementation · All rules

vue/valid-template-root

Enforce a valid <template> root for Vue 3 fragment semantics

Bad · Good

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.

Implementation · All rules

vue/valid-v-bind

Enforce valid v-bind directives

Bad · Good

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.

Implementation · All rules

vue/valid-v-cloak

Enforce valid v-cloak directives

Bad · Good

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.

Implementation · All rules

vue/valid-v-else

Enforce valid v-else directives

Bad · Good

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.

Implementation · All rules

vue/valid-v-for

Enforce valid v-for directives

Bad · Good

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.

Implementation · All rules

vue/valid-v-html

Enforce valid v-html directives

Bad · Good

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.

Implementation · All rules

vue/valid-v-if

Enforce valid v-if directives

Bad · Good

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.

Implementation · All rules

vue/valid-v-memo

Enforce valid v-memo directives

Bad · Good

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.

Implementation · All rules

vue/valid-v-model

Enforce valid v-model directives

Bad · Good

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.

Implementation · All rules

vue/valid-v-on

Enforce valid v-on directives

Bad · Good

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.

Implementation · All rules

vue/valid-v-once

Enforce valid v-once directives

Bad · Good

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.

Implementation · All rules

vue/valid-v-show

Enforce valid v-show directives

Bad · Good

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.

Implementation · All rules

vue/valid-v-slot

Enforce valid v-slot directives

Bad · Good

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.

Implementation · All rules

vue/valid-v-text

Enforce valid v-text directives

Bad · Good

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.

Implementation · All rules

vue/attribute-hyphenation

Enforce attribute naming style on custom components

Bad · Good

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.

Implementation · All rules

vue/component-definition-name-casing

Enforce PascalCase or kebab-case for component definition names

Bad · Good

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.

Implementation · All rules

vue/html-quotes

Enforce quotes style of HTML attributes

Bad · Good

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.

Implementation · All rules

vue/html-self-closing

Enforce self-closing style

Bad · Good

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.

Implementation · All rules

vue/mustache-interpolation-spacing

Enforce consistent spacing inside mustache interpolations

Bad · Good

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.

Implementation · All rules

vue/no-multi-spaces

Disallow multiple consecutive spaces

Bad · Good

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.

Implementation · All rules

vue/no-template-shadow

Disallow variable names that shadow variables in outer scope

Bad · Good

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.

Implementation · All rules

vue/no-unused-properties

Disallow unused properties defined in defineProps

Bad · Good

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.

Implementation · All rules

vue/prop-name-casing

Enforce a casing for declared prop names

Bad · Good

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.

Implementation · All rules

vue/v-bind-style

Enforce v-bind directive style

Bad · Good

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.

Implementation · All rules

vue/v-on-style

Enforce v-on directive style

Bad · Good

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.

Implementation · All rules

vue/v-slot-style

Enforce v-slot directive style

Bad · Good

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.

Implementation · All rules

ssr/no-browser-globals-in-ssr

Disallow browser-only globals in SSR context

Bad · Good

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.

Implementation · All rules

ssr/no-hydration-mismatch

Disallow non-deterministic values that cause hydration mismatch

Bad · Good

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.

Implementation · All rules

vue/a11y-img-alt

Require alt attribute on images for accessibility

Bad · Good

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.

Implementation · All rules

vue/attribute-order

Enforce a consistent order of attributes

Bad · Good

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.

Implementation · All rules

vue/component-name-in-template-casing

Enforce specific casing for component names in templates

Bad · Good

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.

Implementation · All rules

vue/html-button-has-type

Require an explicit valid type on button elements

Bad · Good

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.

Implementation · All rules

vue/max-template-complexity

Limit a component's own template complexity (cyclomatic and cognitive)

Bad · Good

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.

Implementation · All rules

vue/no-array-index-key

Disallow using the v-for index variable directly as the :key

Bad · Good

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.

Implementation · All rules

vue/no-bare-strings-in-template

Disallow raw human-readable text in the template that should be internationalized

Bad · Good

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.

Implementation · All rules

vue/no-boolean-attr-value

Disallow explicit values for boolean HTML attributes

Bad · Good

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.

Implementation · All rules

vue/no-empty-component-block

Disallow empty SFC blocks

Bad · Good

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.

Implementation · All rules

vue/no-inline-style

Discourage use of inline style attributes

Bad · Good

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.

Implementation · All rules

vue/no-invalid-html-attribute

Disallow invalid static values for HTML attributes

Bad · Good

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.

Implementation · All rules

vue/no-lone-template

Disallow unnecessary <template> elements

Bad · Good

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.

Implementation · All rules

vue/no-multiple-objects-in-class

Disallow multiple object literals inside a :class array binding

Bad · Good

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.

Implementation · All rules

vue/no-negated-v-if-condition

Disallow a negated v-if condition when the chain has a v-else

Bad · Good

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.

Implementation · All rules

vue/no-non-component-keep-alive-child

Disallow plain element wrappers directly below <KeepAlive>

Bad · Good

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.

Implementation · All rules

vue/no-preprocessor-lang

Discourage CSS preprocessor usage in favor of modern CSS

Bad · Good

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.

Implementation · All rules

vue/no-root-v-if

Disallow v-if on the single root element of a template

Bad · Good

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.

Implementation · All rules

vue/no-script-non-standard-lang

Discourage non-standard script lang values

Bad · Good

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.

Implementation · All rules

vue/no-src-attribute

Discourage src attribute on SFC blocks

Bad · Good

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.

Implementation · All rules

vue/no-static-inline-styles

Disallow static inline style attributes

Bad · Good

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.

Implementation · All rules

vue/no-template-lang

Discourage lang attribute on template block

Bad · Good

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.

Implementation · All rules

vue/no-template-target-blank

Disallow target="_blank" without rel="noopener noreferrer"

Bad · Good

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.

Implementation · All rules

vue/no-undefined-refs

Disallow undefined variable references in templates

Bad · Good

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.

Implementation · All rules

vue/no-unsafe-url

Warn about potentially unsafe URL bindings

Bad · Good

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.

Implementation · All rules

vue/no-unsandboxed-iframe

Require a sandbox attribute on iframe elements

Bad · Good

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.

Implementation · All rules

vue/no-unused-refs

Report template refs (ref="x") never referenced in <script>

Bad · Good

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.

Implementation · All rules

vue/no-unused-setup-bindings

Disallow unread script setup bindings

Bad · Good

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.

Implementation · All rules

vue/no-use-v-else-with-v-for

Disallow using v-else-if or v-else on the same element as v-for

Bad · Good

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.

Implementation · All rules

vue/no-useless-mustaches

Disallow a mustache interpolation whose expression is a constant string literal

Bad · Good

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.

Implementation · All rules

vue/no-useless-v-bind

Disallow a v-bind whose value is a plain string literal

Bad · Good

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.

Implementation · All rules

vue/no-v-text

Disallow the v-text directive; prefer mustache interpolation

Bad · Good

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.

Implementation · All rules

vue/prefer-props-shorthand

Recommend shorthand syntax for props (Vue 3.4+)

Bad · Good

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.

Implementation · All rules

vue/prefer-true-attribute-shorthand

Prefer the shorthand for a boolean attribute bound to true

Bad · Good

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.

Implementation · All rules

vue/require-component-registration

Require explicit import or registration for components

Bad · Good

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.

Implementation · All rules

vue/require-scoped-style

Require scoped attribute on style tags

Bad · Good

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.

Implementation · All rules

vue/scoped-event-names

Recommend scoped event names using context:event format

Bad · Good

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.

Implementation · All rules

vue/sfc-element-order

Enforce consistent order of SFC top-level elements

Bad · Good

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.

Implementation · All rules

vue/single-style-block

Recommend having a single style block

Bad · Good

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.

Implementation · All rules

vue/slot-name-casing

Enforce kebab-case for named slots used via v-slot

Bad · Good

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.

Implementation · All rules

vue/this-in-template

Disallow this. in template expressions

Bad · Good

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.

Implementation · All rules

vue/v-on-event-hyphenation

Enforce hyphenation of custom event names in v-on on components

Bad · Good

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.

Implementation · All rules

vue/v-on-handler-style

Enforce writing v-on handlers as a method reference or an inline function

Bad · Good

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.

Implementation · All rules

vue/warn-custom-block

Warn about custom blocks in SFC files

Bad · Good

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.

Implementation · All rules

vue/warn-custom-directive

Warn about custom directives that need registration

Bad · Good

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.

Implementation · All rules

a11y/alt-text

Require alternative text for media elements

Bad · Good

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.

Implementation · All rules

a11y/anchor-has-content

Require anchor elements to have accessible content

Bad · Good

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.

Implementation · All rules

a11y/anchor-is-valid

Enforce valid href on anchor elements

Bad · Good

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.

Implementation · All rules

a11y/aria-props

Disallow invalid ARIA attributes

Bad · Good

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.

Implementation · All rules

a11y/aria-role

Elements with ARIA roles must use a valid, non-abstract ARIA role

Bad · Good

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.

Implementation · All rules

a11y/aria-unsupported-elements

Disallow ARIA attributes on elements that do not support them

Bad · Good

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.

Implementation · All rules

a11y/click-events-have-key-events

Require keyboard event handlers with click events

Bad · Good

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.

Implementation · All rules

a11y/form-control-has-label

Require form controls to have associated labels

Bad · Good

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.

Implementation · All rules

a11y/heading-has-content

Require heading elements to have accessible content

Bad · Good

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.

Implementation · All rules

a11y/heading-levels

Disallow skipping heading levels

Bad · Good

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.

Implementation · All rules

a11y/iframe-has-title

Require iframe elements to have a title attribute

Bad · Good

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.

Implementation · All rules

a11y/img-alt

Require alt attribute on images for accessibility

Bad · Good

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.

Implementation · All rules

a11y/interactive-supports-focus

Require interactive role elements to be focusable

Bad · Good

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.

Implementation · All rules

a11y/label-has-for

Require labels to have associated form controls

Bad · Good

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.

Implementation · All rules

a11y/landmark-roles

Validate landmark role placement and uniqueness

Bad · Good

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.

Implementation · All rules

a11y/media-has-caption

Require media elements to have captions

Bad · Good

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.

Implementation · All rules

a11y/mouse-events-have-key-events

Require focus/blur events with mouse events

Bad · Good

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.

Implementation · All rules

a11y/no-access-key

Disallow the use of the accesskey attribute

Bad · Good

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.

Implementation · All rules

a11y/no-aria-hidden-on-focusable

Disallow aria-hidden="true" on focusable elements

Bad · Good

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.

Implementation · All rules

a11y/no-autofocus

Disallow the use of the autofocus attribute

Bad · Good

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.

Implementation · All rules

a11y/no-distracting-elements

Disallow distracting elements like <marquee> and <blink>

Bad · Good

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.

Implementation · All rules

a11y/no-i-for-icon

Disallow using <i> element for icons

Bad · Good

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.

Implementation · All rules

a11y/no-redundant-roles

Disallow redundant ARIA roles

Bad · Good

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.

Implementation · All rules

a11y/no-refer-to-non-existent-id

Disallow references to non-existent IDs

Bad · Good

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.

Implementation · All rules

a11y/no-role-presentation-on-focusable

Disallow role="presentation" or role="none" on focusable elements

Bad · Good

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.

Implementation · All rules

a11y/no-static-element-interactions

Disallow event handlers on static elements

Bad · Good

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.

Implementation · All rules

a11y/placeholder-label-option

Require disabled or hidden on select placeholder option

Bad · Good

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.

Implementation · All rules

a11y/role-has-required-aria-props

Require ARIA roles to have required properties

Bad · Good

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.

Implementation · All rules

a11y/tabindex-no-positive

Disallow positive tabindex values

Bad · Good

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.

Implementation · All rules

a11y/use-list

Suggest using list elements for bullet-like text

Bad · Good

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.

Implementation · All rules

vue/use-unique-element-ids

Enforce unique element IDs using useId() instead of static literals

Bad · Good

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.

Implementation · All rules

html/deprecated-attr

Disallow deprecated HTML attributes

Bad · Good

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.

Implementation · All rules

html/deprecated-element

Disallow deprecated HTML elements

Bad · Good

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.

Implementation · All rules

html/id-duplication

Disallow duplicate element IDs

Bad · Good

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.

Implementation · All rules

html/no-consecutive-br

Disallow consecutive <br> elements

Bad · Good

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.

Implementation · All rules

html/no-dupe-style-properties

Disallow duplicate properties in inline style attributes

Bad · Good

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.

Implementation · All rules

html/no-duplicate-class

Disallow duplicate class names in a static class attribute

Bad · Good

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.

Implementation · All rules

html/no-duplicate-dt

Disallow duplicate <dt> names in <dl>

Bad · Good

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.

Implementation · All rules

html/no-empty-palpable-content

Disallow empty elements that expect visible content

Bad · Good

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.

Implementation · All rules

html/require-datetime

Require datetime attribute on <time> element

Bad · Good

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.

Implementation · All rules

type/no-floating-promises

Disallow floating (unhandled) Promises

Bad · Good

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.

Implementation · All rules

type/no-reactivity-loss

Disallow plain snapshots of reactive values across assignments and calls

Bad · Good

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.

Implementation · All rules

type/no-unsafe-template-binding

Disallow template bindings that resolve to unsafe types

Bad · Good

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.

Implementation · All rules

type/require-typed-emits

Require type definition for defineEmits

Bad · Good

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.

Implementation · All rules

type/require-typed-props

Require type definition for defineProps

Bad · Good

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.

Implementation · All rules

type/strict-boolean-expressions

Require safe boolean expressions in script and template conditions

Bad · Good

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.

Implementation · All rules

script/no-get-current-instance

Disallow getCurrentInstance() in Vapor mode (returns null)

Bad · Good

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.

Implementation · All rules

script/no-next-tick

Disallow nextTick() usage in Vapor-oriented components

Bad · Good

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.

Implementation · All rules

script/no-options-api

Disallow Options API patterns in Vapor mode

Bad · Good

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.

Implementation · All rules

vapor/no-inline-template

Disallow deprecated inline-template attribute

Bad · Good

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.

Implementation · All rules

vapor/no-vue-lifecycle-events

Disallow @vue:xxx per-element lifecycle events (not supported in Vapor)

Bad · Good

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.

Implementation · All rules

vapor/prefer-static-class

Prefer static class over dynamic class binding for string literals

Bad · Good

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.

Implementation · All rules

vapor/require-vapor-attribute

Suggest adding vapor attribute to script setup

Bad · Good

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.

Implementation · All rules

Prefer NuxtLink for internal application links

Bad · Good

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.

Implementation · All rules

ecosystem/pinia-prefer-store-to-refs

Prefer storeToRefs() when destructuring Pinia stores

Bad · Good

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.

Implementation · All rules

Require a to target on RouterLink and NuxtLink components

Bad · Good

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.

Implementation · All rules

Require href on Void Vue Link components

Bad · Good

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.

Implementation · All rules

Validate static Void Vue Link method props

Bad · Good

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.

Implementation · All rules

ecosystem/vue-i18n-no-missing-key

Report static vue-i18n keys that are absent from local SFC messages

Bad · Good

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.

Implementation · All rules

Prefer named route objects over static path strings in RouterLink

Bad · Good

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.

Implementation · All rules

ecosystem/vue-router-prefer-named-push

Prefer named route objects for Vue Router programmatic navigation

Bad · Good

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.

Implementation · All rules

ecosystem/vue-test-utils-no-html-snapshot

Avoid snapshotting wrapper.html() in Vue Test Utils tests

Bad · Good

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.

Implementation · All rules

css/no-display-none

Suggest using v-show instead of display: none

Bad · Good

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.

Implementation · All rules

css/no-hardcoded-values

Suggest using CSS variables instead of hardcoded values

Bad · Good

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.

Implementation · All rules

css/no-id-selectors

Discourage use of ID selectors in CSS

Bad · Good

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.

Implementation · All rules

css/no-important

Discourage use of !important in CSS

Bad · Good

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.

Implementation · All rules

css/no-utility-classes

Warn against implementing utility classes in component styles

Bad · Good

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.

Implementation · All rules

css/no-v-bind-performance

Warn about performance cost of CSS v-bind()

Bad · Good

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.

Implementation · All rules

css/prefer-logical-properties

Recommend CSS logical properties for better i18n support

Bad · Good

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.

Implementation · All rules

css/prefer-nested-selectors

Recommend using CSS nesting for descendant selectors

Bad · Good

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.

Implementation · All rules

css/prefer-slotted

Recommend ::v-slotted() for styling slot content

Bad · Good

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.

Implementation · All rules

css/require-font-display

Require font-display in @font-face rules

Bad · Good

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.

Implementation · All rules

musea/no-empty-variant

Disallow empty <variant> blocks

Bad · Good

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.

Implementation · All rules

musea/prefer-design-tokens

Prefer design token CSS variables over hardcoded primitive values

Bad · Good

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.

Implementation · All rules

musea/require-component

Require component attribute in <art> block

Bad · Good

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.

Implementation · All rules

musea/require-title

Require title attribute in <art> block

Bad · Good

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.

Implementation · All rules

musea/unique-variant-names

Require unique variant names

Bad · Good

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.

Implementation · All rules

musea/valid-variant

Require name attribute in <variant> blocks

Bad · Good

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.

Implementation · All rules

script/component-options-name-casing

Enforce PascalCase for the component name option

Bad · Good

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.

Implementation · All rules

script/custom-event-name-casing

Enforce camelCase for emitted custom event names

Bad · Good

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.

Implementation · All rules

script/define-emits-declaration

Enforce the type-based defineEmits<{}>() form over the runtime/array form

Bad · Good

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.

Implementation · All rules

script/define-macros-order

Enforce a consistent order of the Vue compiler macros in <script setup>

Bad · Good

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.

Implementation · All rules

script/define-props-declaration

Enforce type-based defineProps<{ ... }>() over the runtime/object form

Bad · Good

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.

Implementation · All rules

script/define-props-destructuring

Enforce consistent style for defineProps destructuring in <script setup>

Bad · Good

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.

Implementation · All rules

script/no-arrow-functions-in-watch

Disallow arrow functions as Options API watch handlers

Bad · Good

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.

Implementation · All rules

script/no-async-in-computed

Disallow async functions in computed properties

Bad · Good

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.

Implementation · All rules

script/no-boolean-default

Disallow a default on a Boolean prop

Bad · Good

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.

Implementation · All rules

script/no-deep-destructure-in-props

Disallow deeply nested destructuring in defineProps

Bad · Good

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.

Implementation · All rules

script/no-deprecated-data-object-declaration

Disallow an object literal as the component data option (Vue 3 requires a function)

Bad · Good

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.

Implementation · All rules

script/no-deprecated-destroyed-lifecycle

Disallow deprecated destroyed and beforeDestroy lifecycle hooks

Bad · Good

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.

Implementation · All rules

script/no-deprecated-dollar-listeners-api

Disallow the $listeners instance property removed in Vue 3 (merged into $attrs)

Bad · Good

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.

Implementation · All rules

script/no-deprecated-dollar-scopedslots-api

Disallow the $scopedSlots instance property removed in Vue 3 (use $slots)

Bad · Good

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.

Implementation · All rules

script/no-deprecated-events-api

Disallow the removed Vue 2 events API ($on / $off / $once)

Bad · Good

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.

Implementation · All rules

script/no-deprecated-props-default-this

Disallow this inside a prop default/validator function (removed in Vue 3)

Bad · Good

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.

Implementation · All rules

script/no-dupe-keys

Disallow duplicate keys across Options API props/data/computed/methods/setup/inject

Bad · Good

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.

Implementation · All rules

script/no-duplicate-attr-inheritance

Flag a component that applies its fallthrough attributes twice

Bad · Good

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.

Implementation · All rules

script/no-export-in-script-setup

Disallow export statements inside <script setup>

Bad · Good

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.

Implementation · All rules

script/no-import-compiler-macros

Disallow importing Vue compiler macros that are auto-imported

Bad · Good

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.

Implementation · All rules

script/no-internal-imports

Disallow importing from Vue internal modules

Bad · Good

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.

Implementation · All rules

script/no-multiple-slot-args

Disallow passing more than one argument to a scoped-slot function call

Bad · Good

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.

Implementation · All rules

script/no-potential-component-option-typo

Flag likely typos in Options API component option names

Bad · Good

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.

Implementation · All rules

script/no-reactive-destructure

Disallow destructuring reactive objects which loses reactivity

Bad · Good

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.

Implementation · All rules

script/no-ref-as-operand

Require ref-bound variables to be accessed via .value when used as an operand

Bad · Good

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.

Implementation · All rules

script/no-required-prop-with-default

Disallow a prop that is both required: true and has a default

Bad · Good

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.

Implementation · All rules

script/no-reserved-identifiers

Disallow using Vue compiler reserved identifiers

Bad · Good

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.

Implementation · All rules

script/no-reserved-keys

Disallow Vue-reserved names as Options API props/data/computed/methods/setup/inject keys

Bad · Good

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.

Implementation · All rules

script/no-reserved-props

Disallow reserved names in a component's props declaration

Bad · Good

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.

Implementation · All rules

script/no-restricted-globals

Disallow references to runtime-environment globals that must go through a typed wrapper

Bad · Good

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.

Implementation · All rules

script/no-restricted-members

Disallow project-configured object.property member accesses

Bad · Good

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.

Implementation · All rules

script/no-side-effects-in-computed-properties

Disallow side effects in Options API computed getters

Bad · Good

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.

Implementation · All rules

script/no-top-level-ref-in-script

Disallow top-level ref/reactive to prevent Cross-Request State Pollution

Bad · Good

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.

Implementation · All rules

script/no-unstable-nested-components

Disallow component definitions inside setup or render functions

Bad · Good

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.

Implementation · All rules

script/no-unused-emit-declarations

Flag declared events that are never emitted

Bad · Good

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.

Implementation · All rules

script/no-use-computed-property-like-method

Disallow calling an Options API computed property like a method

Bad · Good

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.

Implementation · All rules

script/no-with-defaults

Discourage withDefaults in favor of destructuring defaults (Vue 3.5+)

Bad · Good

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.

Implementation · All rules

script/prefer-computed

Prefer computed() for derived reactive state

Bad · Good

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.

Implementation · All rules

script/prefer-define-options

Prefer defineOptions() over a plain <script> that only sets name/inheritAttrs

Bad · Good

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.

Implementation · All rules

script/prefer-import-from-vue

Prefer importing from 'vue' instead of internal packages

Bad · Good

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.

Implementation · All rules

script/prefer-ref-over-reactive

Recommend using ref() over reactive() for state management

Bad · Good

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.

Implementation · All rules

script/prefer-use-attrs

Recommend using useAttrs() over context.attrs

Bad · Good

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.

Implementation · All rules

script/prefer-use-id

Recommend using useId() for generating unique IDs (Vue 3.5+)

Bad · Good

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.

Implementation · All rules

script/prefer-use-slots

Recommend using useSlots() over context.slots

Bad · Good

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.

Implementation · All rules

script/prefer-use-template-ref

Recommend useTemplateRef over ref for template references (Vue 3.5+)

Bad · Good

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.

Implementation · All rules

script/require-default-prop

Require a default value for every optional, non-Boolean prop

Bad · Good

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.

Implementation · All rules

script/require-explicit-emits

Require emitted events to be declared in defineEmits or the emits option

Bad · Good

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.

Implementation · All rules

script/require-explicit-slots

Require slots consumed via useSlots() to be explicitly typed with defineSlots<...>()

Bad · Good

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.

Implementation · All rules

script/require-function-return-type

Require return type annotations on functions

Bad · Good

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.

Implementation · All rules

script/require-prop-type-constructor

Require prop type values to be constructors rather than string literals

Bad · Good

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.

Implementation · All rules

script/require-prop-types

Require every prop to declare a type

Bad · Good

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.

Implementation · All rules

script/require-symbol-provide

Recommend using Symbol as injection key for provide/inject

Bad · Good

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.

Implementation · All rules

script/require-typed-object-prop

Require an explicit type on a prop whose runtime type is Object or Array

Bad · Good

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.

Implementation · All rules

script/require-typed-ref

Require an explicit type argument on a ref() initialized with no value, null, or undefined

Bad · Good

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.

Implementation · All rules

script/require-valid-default-prop

Require a prop's default value to be valid for its declared type

Bad · Good

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.

Implementation · All rules

script/return-in-computed-property

Require a return value in every computed getter

Bad · Good

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.

Implementation · All rules

script/return-in-emits-validator

Require a return value in every Options API emits validator

Bad · Good

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.

Implementation · All rules

script/valid-define-emits

Enforce valid defineEmits() usage (no type+runtime args, no local references, single call)

Bad · Good

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.

Implementation · All rules

script/valid-define-options

Enforce valid defineOptions() usage (single object arg, no props/emits/expose/slots)

Bad · Good

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.

Implementation · All rules

script/valid-define-props

Enforce valid defineProps() usage (single call, not both type and runtime args, no local references)

Bad · Good

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.

Implementation · All rules

script/valid-next-tick

Require the result of a nextTick() call to be awaited, chained, or given a callback

Bad · Good

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.

Implementation · All rules

nuxt/no-nuxt-config-test-key

Disallow setting test key in Nuxt config

Bad · Good

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.

Implementation · All rules

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

Bad · Good

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.

Implementation · All rules

nuxt/nuxt-config-keys-order

Prefer recommended order of Nuxt config properties

Bad · Good

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.

Implementation · All rules

nuxt/prefer-import-meta

Prefer using import.meta.* over process.*

Bad · Good

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.

Implementation · All rules

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.

Cross-file index

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.

Cross-file index

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.

Cross-file index

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.

Cross-file index

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.

Cross-file index

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.

Cross-file index

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.

Public explanation

Cross-file index

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.

Public explanation

Producer

Cross-file index

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.

Public explanation

Producer

Cross-file index

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.

Public explanation

Producer

Cross-file index

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.

Public explanation

Cross-file index

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.

Public explanation

Producer

Cross-file index

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.

Public explanation

Cross-file index

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.

Public explanation

Cross-file index

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.

Public explanation

Cross-file index

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.

Public explanation

Cross-file index

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.

Public explanation

Producer

Producer

Producer

Cross-file index

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.

Public explanation

Cross-file index

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.

Public explanation

Cross-file index

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.

Public explanation

Producer

Cross-file index

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.

Public explanation

Cross-file index

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.

Public explanation

Producer

Cross-file index

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.

Public explanation

Producer

Producer

Cross-file index

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.

Public explanation

Producer

Cross-file index

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.

Public explanation

Producer

Cross-file index

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.

Public explanation

Producer

Cross-file index

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.

Public explanation

Cross-file index

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.

Public explanation

Producer

Cross-file index

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.

Public explanation

Producer

Cross-file index

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.

Public explanation

Cross-file index

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.

Public explanation

Cross-file index

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.

Public explanation

Producer

Cross-file index

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.

Public explanation

Cross-file index

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.

Public explanation

Producer

Cross-file index

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.

Public explanation

Producer

Cross-file index

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.

Public explanation

Cross-file index

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.

Public explanation

Cross-file index

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.

Public explanation

Producer

Cross-file index

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.

Public explanation

Producer

Cross-file index

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.

Public explanation

Producer

Cross-file index

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.

Public explanation

Cross-file index

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.

Public explanation

Cross-file index

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.

Public explanation

Producer

Cross-file index

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.

Public explanation

Cross-file index

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.

Public explanation

Producer

Cross-file index

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.

Public explanation

Cross-file index

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.

Public explanation

Producer

Cross-file index

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.

Public explanation

Cross-file index

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.

Public explanation

Cross-file index

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.

Public explanation

Cross-file index

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.

Public explanation

Producer

Cross-file index

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.

Public explanation

Producer

Cross-file index

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.

Public explanation

Producer

Cross-file index

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.

Public explanation

Cross-file index

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.

Public explanation

Producer

Cross-file index

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.

Public explanation

Producer

Cross-file index

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.

Public explanation

Producer

Cross-file index

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.

Public explanation

Producer

Cross-file index

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.

Public explanation

Producer

Cross-file index

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.

Public explanation

Producer

Cross-file index

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.

Public explanation

Producer

Cross-file index

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.

Public explanation

Producer

Cross-file index

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.

Public explanation

Producer

Cross-file index

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.

Public explanation

Cross-file index

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.

Public explanation

Producer

Cross-file index

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.

Public explanation

Cross-file index