diff --git a/packages/plugins/eslint-plugin-react-core/src/rules/no-mixing-controlled-and-uncontrolled.spec.ts b/packages/plugins/eslint-plugin-react-core/src/rules/no-mixing-controlled-and-uncontrolled.spec.ts
new file mode 100644
index 000000000..022786e6c
--- /dev/null
+++ b/packages/plugins/eslint-plugin-react-core/src/rules/no-mixing-controlled-and-uncontrolled.spec.ts
@@ -0,0 +1,45 @@
+import { allValid, ruleTester } from "../../../../../test";
+import rule, { RULE_NAME } from "./no-mixing-controlled-and-uncontrolled";
+
+ruleTester.run(RULE_NAME, rule, {
+ invalid: [
+ {
+ code: '',
+ errors: [
+ { messageId: "NO_MIXING_CONTROLLED_AND_UNCONTROLLED" },
+ ],
+ },
+ {
+ code: '',
+ errors: [
+ { messageId: "NO_MIXING_CONTROLLED_AND_UNCONTROLLED" },
+ ],
+ },
+ {
+ code: 'React.createElement("input", { checked: true, defaultChecked: true })',
+ errors: [
+ { messageId: "NO_MIXING_CONTROLLED_AND_UNCONTROLLED" },
+ ],
+ },
+ {
+ code: 'React.createElement("input", { value: 1, defaultValue: 1 })',
+ errors: [
+ { messageId: "NO_MIXING_CONTROLLED_AND_UNCONTROLLED" },
+ ],
+ },
+ ],
+ valid: [
+ ...allValid,
+ '',
+ '',
+ '',
+ '',
+ '',
+ '',
+ "React.createElement('input')",
+ "React.createElement('input', { checked: true })",
+ "React.createElement('input', { checked: false })",
+ "React.createElement('input', { defaultChecked: true })",
+ "",
+ ],
+});
diff --git a/packages/plugins/eslint-plugin-react-core/src/rules/no-mixing-controlled-and-uncontrolled.ts b/packages/plugins/eslint-plugin-react-core/src/rules/no-mixing-controlled-and-uncontrolled.ts
new file mode 100644
index 000000000..af751361a
--- /dev/null
+++ b/packages/plugins/eslint-plugin-react-core/src/rules/no-mixing-controlled-and-uncontrolled.ts
@@ -0,0 +1,80 @@
+import { NodeType } from "@eslint-react/ast";
+import { elementType, findPropInProperties, isCreateElementCall } from "@eslint-react/jsx";
+import { hasEveryProp } from "@eslint-react/jsx";
+import type { ESLintUtils } from "@typescript-eslint/utils";
+import { Option as O } from "effect";
+import type { ConstantCase } from "string-ts";
+import { isMatching } from "ts-pattern";
+
+import { createRule } from "../../../eslint-plugin-react-dom/src/utils";
+
+export const RULE_NAME = "no-mixing-controlled-and-uncontrolled";
+
+export type MessageID = ConstantCase;
+
+export default createRule<[], MessageID>({
+ meta: {
+ type: "problem",
+ docs: {
+ description: "disallow mixing controlled and uncontrolled .",
+ recommended: "recommended",
+ requiresTypeChecking: false,
+ },
+ messages: {
+ NO_MIXING_CONTROLLED_AND_UNCONTROLLED: "Disallow controlled prop and uncontrolled prop being used together.",
+ },
+ schema: [],
+ },
+ name: RULE_NAME,
+ create(context) {
+ return {
+ CallExpression(node) {
+ if (!isCreateElementCall(node, context)) return;
+ const [name, props] = node.arguments;
+ if (!isMatching({ type: NodeType.Literal, value: "input" }, name)) return;
+ if (!props || props.type !== NodeType.ObjectExpression) return;
+
+ const initialScope = context.sourceCode.getScope(node);
+
+ if (
+ O.isSome(findPropInProperties(props.properties, context, initialScope)("checked"))
+ && O.isSome(findPropInProperties(props.properties, context, initialScope)("defaultChecked"))
+ ) {
+ return context.report({
+ messageId: "NO_MIXING_CONTROLLED_AND_UNCONTROLLED",
+ node: node,
+ });
+ }
+ if (
+ O.isSome(findPropInProperties(props.properties, context, initialScope)("value"))
+ && O.isSome(findPropInProperties(props.properties, context, initialScope)("defaultValue"))
+ ) {
+ return context.report({
+ messageId: "NO_MIXING_CONTROLLED_AND_UNCONTROLLED",
+ node: node,
+ });
+ }
+ },
+ JSXOpeningElement(node) {
+ const name = elementType(node);
+ if (name !== "input") return;
+
+ const initialScope = context.sourceCode.getScope(node);
+ if (hasEveryProp(node.attributes, ["checked", "defaultChecked"], context, initialScope)) {
+ return context.report({
+ messageId: "NO_MIXING_CONTROLLED_AND_UNCONTROLLED",
+ node: node,
+ });
+ }
+
+ if (hasEveryProp(node.attributes, ["value", "defaultValue"], context, initialScope)) {
+ return context.report({
+ messageId: "NO_MIXING_CONTROLLED_AND_UNCONTROLLED",
+ node: node,
+ });
+ }
+ },
+ };
+ },
+ defaultOptions: [],
+}) satisfies ESLintUtils.RuleModule;
diff --git a/website/pages/rules/_meta.ts b/website/pages/rules/_meta.ts
index b6f73837b..a90bd4871 100644
--- a/website/pages/rules/_meta.ts
+++ b/website/pages/rules/_meta.ts
@@ -31,6 +31,7 @@ export default {
"no-leaked-conditional-rendering": "no-leaked-conditional-rendering",
"no-missing-component-display-name": "no-missing-component-display-name",
"no-missing-key": "no-missing-key",
+ "no-mixing-controlled-and-uncontrolled": "no-mixing-controlled-and-uncontrolled",
"no-nested-components": "no-nested-components",
"no-redundant-should-component-update": "no-redundant-should-component-update",
"no-set-state-in-component-did-mount": "no-set-state-in-component-did-mount",
diff --git a/website/pages/rules/no-mixing-controlled-and-uncontrolled.mdx b/website/pages/rules/no-mixing-controlled-and-uncontrolled.mdx
new file mode 100644
index 000000000..334fa6802
--- /dev/null
+++ b/website/pages/rules/no-mixing-controlled-and-uncontrolled.mdx
@@ -0,0 +1,45 @@
+# no-mixing-controlled-and-uncontrolled
+
+## Rule category
+
+Correctness.
+
+## What it does
+
+Prevents both `value` and `defaultValue` prop or both `checked` and `defaultChecked` prop on ``.
+
+## Why is this bad?
+
+A `` is either controlled or uncontrolled. Mixing controlled and uncontrolled props can lead to bugs and unexpected behavior.
+
+## Examples
+
+### Failing
+
+```tsx twoslash
+import React from "react";
+
+function Example() {
+ return ;
+ // ^^^^^^^^^^^^^^^^^^^^^^^^^
+ // - Disallow controlled prop and uncontrolled prop being used together.
+}
+```
+
+### Passing
+
+```tsx twoslash
+import React from "react";
+
+function Example1() {
+ return ;
+}
+
+function Example2() {
+ return ;
+}
+```
+
+## Further Reading
+
+- [react.dev: Controlled and uncontrolled components](https://react.dev/learn/sharing-state-between-components#controlled-and-uncontrolled-components)