From 562a732e1af7b0a679bb3654dc2cfab3a537478f Mon Sep 17 00:00:00 2001
From: NullVoxPopuli <199018+NullVoxPopuli@users.noreply.github.com>
Date: Mon, 23 Feb 2026 15:54:17 -0500
Subject: [PATCH] Extract rule: template-no-input-placeholder
---
README.md | 1 +
docs/rules/template-no-input-placeholder.md | 47 ++++++++++++
lib/rules/template-no-input-placeholder.js | 36 +++++++++
.../rules/template-no-input-placeholder.js | 74 +++++++++++++++++++
4 files changed, 158 insertions(+)
create mode 100644 docs/rules/template-no-input-placeholder.md
create mode 100644 lib/rules/template-no-input-placeholder.js
create mode 100644 tests/lib/rules/template-no-input-placeholder.js
diff --git a/README.md b/README.md
index 26fd0ffeea..f87c310bb8 100644
--- a/README.md
+++ b/README.md
@@ -203,6 +203,7 @@ rules in templates can be disabled with eslint directives with mustache or html
| [template-no-chained-this](docs/rules/template-no-chained-this.md) | disallow redundant `this.this` in templates | | 🔧 | |
| [template-no-debugger](docs/rules/template-no-debugger.md) | disallow {{debugger}} in templates | | | |
| [template-no-element-event-actions](docs/rules/template-no-element-event-actions.md) | disallow element event actions (use {{on}} modifier instead) | | | |
+| [template-no-input-placeholder](docs/rules/template-no-input-placeholder.md) | disallow placeholder attribute on input elements | | | |
| [template-no-input-tagname](docs/rules/template-no-input-tagname.md) | disallow tagName attribute on {{input}} helper | | | |
| [template-no-log](docs/rules/template-no-log.md) | disallow {{log}} in templates | | | |
diff --git a/docs/rules/template-no-input-placeholder.md b/docs/rules/template-no-input-placeholder.md
new file mode 100644
index 0000000000..638b52af25
--- /dev/null
+++ b/docs/rules/template-no-input-placeholder.md
@@ -0,0 +1,47 @@
+# ember/template-no-input-placeholder
+
+
+
+Disallows `placeholder` attribute on input elements.
+
+## Rule Details
+
+The `placeholder` attribute should not be used as it has accessibility issues. Use proper labels instead.
+
+## Examples
+
+Examples of **incorrect** code for this rule:
+
+```gjs
+
+
+
+```
+
+```gjs
+
+
+
+```
+
+Examples of **correct** code for this rule:
+
+```gjs
+
+
+
+```
+
+```gjs
+
+
+
+```
+
+## References
+
+- [eslint-plugin-ember template-no-input-placeholder](https://github.com/ember-cli/eslint-plugin-ember/blob/master/docs/rules/template-no-input-placeholder.md)
+- [Placeholders in Form Fields Are Harmful](https://www.nngroup.com/articles/form-design-placeholders/)
diff --git a/lib/rules/template-no-input-placeholder.js b/lib/rules/template-no-input-placeholder.js
new file mode 100644
index 0000000000..553b9caf3a
--- /dev/null
+++ b/lib/rules/template-no-input-placeholder.js
@@ -0,0 +1,36 @@
+/** @type {import('eslint').Rule.RuleModule} */
+module.exports = {
+ meta: {
+ type: 'suggestion',
+ docs: {
+ description: 'disallow placeholder attribute on input elements',
+ category: 'Best Practices',
+ strictGjs: true,
+ strictGts: true,
+ url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/template-no-input-placeholder.md',
+ },
+ fixable: null,
+ schema: [],
+ messages: {
+ noPlaceholder:
+ 'Do not use placeholder attribute. Use a label instead for better accessibility.',
+ },
+ },
+
+ create(context) {
+ return {
+ GlimmerElementNode(node) {
+ if (node.tag === 'input' && node.attributes) {
+ for (const attr of node.attributes) {
+ if (attr.type === 'GlimmerAttrNode' && attr.name === 'placeholder') {
+ context.report({
+ node: attr,
+ messageId: 'noPlaceholder',
+ });
+ }
+ }
+ }
+ },
+ };
+ },
+};
diff --git a/tests/lib/rules/template-no-input-placeholder.js b/tests/lib/rules/template-no-input-placeholder.js
new file mode 100644
index 0000000000..1d9168f362
--- /dev/null
+++ b/tests/lib/rules/template-no-input-placeholder.js
@@ -0,0 +1,74 @@
+//------------------------------------------------------------------------------
+// Requirements
+//------------------------------------------------------------------------------
+
+const rule = require('../../../lib/rules/template-no-input-placeholder');
+const RuleTester = require('eslint').RuleTester;
+
+//------------------------------------------------------------------------------
+// Tests
+//------------------------------------------------------------------------------
+
+const ruleTester = new RuleTester({
+ parser: require.resolve('ember-eslint-parser'),
+ parserOptions: { ecmaVersion: 2022, sourceType: 'module' },
+});
+
+ruleTester.run('template-no-input-placeholder', rule, {
+ valid: [
+ `
+
+ `,
+ `
+
+ `,
+ `
+
+ `,
+ ],
+
+ invalid: [
+ {
+ code: `
+
+ `,
+ output: null,
+ errors: [
+ {
+ message:
+ 'Do not use placeholder attribute. Use a label instead for better accessibility.',
+ type: 'GlimmerAttrNode',
+ },
+ ],
+ },
+ {
+ code: `
+
+ `,
+ output: null,
+ errors: [
+ {
+ message:
+ 'Do not use placeholder attribute. Use a label instead for better accessibility.',
+ type: 'GlimmerAttrNode',
+ },
+ ],
+ },
+ {
+ code: `
+
+ `,
+ output: null,
+ errors: [
+ {
+ message:
+ 'Do not use placeholder attribute. Use a label instead for better accessibility.',
+ type: 'GlimmerAttrNode',
+ },
+ ],
+ },
+ ],
+});