Skip to content
Draft
Show file tree
Hide file tree
Changes from 8 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion docs/rules/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -274,7 +274,7 @@ For example:
| [vue/prefer-prop-type-boolean-first] | enforce `Boolean` comes first in component prop types | :bulb: | :warning: |
| [vue/prefer-separate-static-class] | require static class names in template to be in a separate `class` attribute | :wrench: | :hammer: |
| [vue/prefer-true-attribute-shorthand] | require shorthand form attribute when `v-bind` value is `true` | :bulb: | :hammer: |
| [vue/prefer-use-template-ref] | require using `useTemplateRef` instead of `ref`/`shallowRef` for template refs | | :hammer: |
| [vue/prefer-use-template-ref] | require using `useTemplateRef` instead of `ref`/`shallowRef` for template refs | :wrench: | :hammer: |
| [vue/require-default-export] | require components to be the default export | | :warning: |
| [vue/require-direct-export] | require the component to be directly exported | | :hammer: |
| [vue/require-emit-validator] | require type definitions in emits | :bulb: | :hammer: |
Expand Down
6 changes: 4 additions & 2 deletions docs/rules/prefer-use-template-ref.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,16 @@ since: v9.31.0

> require using `useTemplateRef` instead of `ref`/`shallowRef` for template refs

- :wrench: The `--fix` option on the [command line](https://eslint.org/docs/user-guide/command-line-interface#fix-problems) can automatically fix some of the problems reported by this rule.

## :book: Rule Details

Vue 3.5 introduced a new way of obtaining template refs via
the [`useTemplateRef()`](https://vuejs.org/guide/essentials/template-refs.html#accessing-the-refs) API.

This rule enforces using the new `useTemplateRef` function instead of `ref`/`shallowRef` for template refs.

<eslint-code-block :rules="{'vue/prefer-use-template-ref': ['error']}">
<eslint-code-block fix :rules="{'vue/prefer-use-template-ref': ['error']}">

```vue
<template>
Expand Down Expand Up @@ -45,7 +47,7 @@ This rule enforces using the new `useTemplateRef` function instead of `ref`/`sha
This rule skips `ref` template function refs as these should be used to allow custom implementation of storing `ref`. If you prefer
`useTemplateRef`, you have to change the value of the template `ref` to a string.

<eslint-code-block :rules="{'vue/prefer-use-template-ref': ['error']}">
<eslint-code-block fix :rules="{'vue/prefer-use-template-ref': ['error']}">

```vue
<template>
Expand Down
11 changes: 11 additions & 0 deletions lib/rules/prefer-use-template-ref.js
Original file line number Diff line number Diff line change
Expand Up @@ -43,13 +43,15 @@ module.exports = {
categories: undefined,
url: 'https://eslint.vuejs.org/rules/prefer-use-template-ref.html'
},
fixable: 'code',
schema: [],
messages: {
preferUseTemplateRef: "Replace '{{name}}' with 'useTemplateRef'."
}
},
/** @param {RuleContext} context */
create(context) {
const sourceCode = context.sourceCode
/** @type Set<string> */
const templateRefs = new Set()

Expand Down Expand Up @@ -97,6 +99,15 @@ module.exports = {
messageId: 'preferUseTemplateRef',
data: {
name: /** @type {Identifier} */ (scriptRef.callee).name
},
fix(fixer) {
const typeArgs =
scriptRef.typeArguments ?? scriptRef.typeParameters

return fixer.replaceText(
scriptRef,
`useTemplateRef${typeArgs ? sourceCode.getText(typeArgs) : ''}('${templateRef}')`
)
}
})
}
Expand Down
169 changes: 160 additions & 9 deletions tests/lib/rules/prefer-use-template-ref.js
Original file line number Diff line number Diff line change
Expand Up @@ -311,14 +311,25 @@ tester.run('prefer-use-template-ref', rule, {
const root = ref();
</script>
`,
output: `
<template>
<div ref="root"/>
</template>
<script setup>
import { ref } from 'vue';
const root = useTemplateRef('root');
Copy link
Member

@ota-meshi ota-meshi Dec 5, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think autofixes shouldn't break source code.
I think adding import { useTemplateRef } from 'vue' will inconvenience users who use unplugin-auto-import, but I think not adding import { useTemplateRef } from 'vue' will break it for users who don't use unplugin-auto-import.
Could you change it to add import { useTemplateRef } from 'vue'?
Or could you change it to a suggestion instead of autofix?

Copy link
Contributor Author

@9romise 9romise Dec 5, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That makes sense. I'll have a try.

</script>
`,
errors: [
{
messageId: 'preferUseTemplateRef',
data: {
name: 'ref'
},
line: 7,
column: 22
column: 22,
endLine: 7,
endColumn: 27
}
]
},
Expand All @@ -335,14 +346,27 @@ tester.run('prefer-use-template-ref', rule, {
const link = ref();
</script>
`,
output: `
<template>
<button ref="button">Content</button>
<a href="" ref="link">Link</a>
</template>
<script setup>
import { ref } from 'vue';
const buttonRef = ref();
const link = useTemplateRef('link');
</script>
`,
errors: [
{
messageId: 'preferUseTemplateRef',
data: {
name: 'ref'
},
line: 9,
column: 22
column: 22,
endLine: 9,
endColumn: 27
}
]
},
Expand All @@ -359,22 +383,37 @@ tester.run('prefer-use-template-ref', rule, {
const link = ref();
</script>
`,
output: `
<template>
<h1 ref="heading">Heading</h1>
<a href="" ref="link">Link</a>
</template>
<script setup>
import { ref } from 'vue';
const heading = useTemplateRef('heading');
const link = useTemplateRef('link');
</script>
`,
errors: [
{
messageId: 'preferUseTemplateRef',
data: {
name: 'ref'
},
line: 8,
column: 25
column: 25,
endLine: 8,
endColumn: 30
},
{
messageId: 'preferUseTemplateRef',
data: {
name: 'ref'
},
line: 9,
column: 22
column: 22,
endLine: 9,
endColumn: 27
}
]
},
Expand All @@ -396,14 +435,32 @@ tester.run('prefer-use-template-ref', rule, {
}
</script>
`,
output: `
<template>
<p>Button clicked {{counter}} times.</p>
<button ref="button">Click</button>
</template>
<script>
import { ref } from 'vue';
export default {
name: 'Counter',
setup() {
const counter = ref(0);
const button = useTemplateRef('button');
}
}
</script>
`,
errors: [
{
messageId: 'preferUseTemplateRef',
data: {
name: 'ref'
},
line: 12,
column: 28
column: 28,
endLine: 12,
endColumn: 33
}
]
},
Expand All @@ -418,14 +475,25 @@ tester.run('prefer-use-template-ref', rule, {
const root = shallowRef();
</script>
`,
output: `
<template>
<div ref="root"/>
</template>
<script setup>
import { shallowRef } from 'vue';
const root = useTemplateRef('root');
</script>
`,
errors: [
{
messageId: 'preferUseTemplateRef',
data: {
name: 'shallowRef'
},
line: 7,
column: 22
column: 22,
endLine: 7,
endColumn: 34
}
]
},
Expand All @@ -444,14 +512,29 @@ tester.run('prefer-use-template-ref', rule, {
}
</script>
`,
output: `
<template>
<button ref="button">Click</button>
</template>
<script>
import { ref } from 'vue';
export default {
setup: () => {
const button = useTemplateRef('button');
}
}
</script>
`,
errors: [
{
messageId: 'preferUseTemplateRef',
data: {
name: 'ref'
},
line: 9,
column: 28
column: 28,
endLine: 9,
endColumn: 33
}
]
},
Expand All @@ -467,6 +550,20 @@ tester.run('prefer-use-template-ref', rule, {
const root = ref()
</script>

<script>
const A = 'foo'
</script>
`,
output: `
<template>
<div ref="root" :data-a="A" />
</template>

<script setup>
import { ref } from 'vue'
const root = useTemplateRef('root')
</script>

<script>
const A = 'foo'
</script>
Expand All @@ -478,7 +575,9 @@ tester.run('prefer-use-template-ref', rule, {
name: 'ref'
},
line: 8,
column: 20
column: 20,
endLine: 8,
endColumn: 25
}
]
},
Expand All @@ -498,14 +597,66 @@ tester.run('prefer-use-template-ref', rule, {
const root = ref()
</script>
`,
output: `
<template>
<div ref="root" :data-a="A" />
</template>

<script>
const A = 'foo'
</script>

<script setup>
import { ref } from 'vue'
const root = useTemplateRef('root')
</script>
`,
errors: [
{
messageId: 'preferUseTemplateRef',
data: {
name: 'ref'
},
line: 12,
column: 20
column: 20,
endLine: 12,
endColumn: 25
}
]
},
{
filename: 'ref-with-typeArguments.vue',
code: `
<template>
<div ref="root" />
</template>
<script setup lang="ts">
const root = ref<HTMLElement | null>()
</script>
`,
output: `
<template>
<div ref="root" />
</template>
<script setup lang="ts">
const root = useTemplateRef<HTMLElement | null>('root')
</script>
`,
languageOptions: {
parserOptions: {
parser: require('@typescript-eslint/parser')
}
},
errors: [
{
messageId: 'preferUseTemplateRef',
data: {
name: 'ref'
},
line: 6,
column: 22,
endLine: 6,
endColumn: 47
}
]
}
Expand Down
Loading