Skip to content
Draft
Show file tree
Hide file tree
Changes from all 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