diff --git a/packages/plugins/eslint-plugin-react-x/src/rules/no-forbidden-props.md b/packages/plugins/eslint-plugin-react-x/src/rules/no-forbidden-props.md
new file mode 100644
index 0000000000..815bb886e3
--- /dev/null
+++ b/packages/plugins/eslint-plugin-react-x/src/rules/no-forbidden-props.md
@@ -0,0 +1,205 @@
+---
+title: no-forbidden-props
+---
+
+**Full Name in `eslint-plugin-react-x`**
+
+```sh copy
+react-x/no-forbidden-props
+```
+
+**Full Name in `@eslint-react/eslint-plugin`**
+
+```sh copy
+@eslint-react/no-forbidden-props
+```
+
+**Presets**
+
+None
+
+## Description
+
+Disallow certain props on components. This rule helps enforce consistent prop naming conventions and prevents the use of specific props that may be problematic or against your team's coding standards.
+
+By default, this rule forbids snake_case props (props containing underscores) to encourage camelCase naming conventions.
+
+## Options
+
+The rule accepts an object with the following properties:
+
+- `forbid` (array): An array of forbidden prop configurations. Each item can be:
+ - A string: The exact prop name to forbid
+ - An object with `prop` and optional `excludedNodes` or `includedNodes`:
+ - `prop` (string): The prop name or regex pattern to forbid
+ - `excludedNodes` (array): Component names where this prop is allowed
+ - `includedNodes` (array): Component names where this prop is forbidden (others are allowed)
+
+### Default Configuration
+
+```json
+{
+ "forbid": [{ "prop": "/_/" }]
+}
+```
+
+This default configuration forbids any prop containing an underscore (snake_case).
+
+## Examples
+
+### Default Behavior (Forbids snake_case props)
+
+#### Failing
+
+```tsx
+
+
+
+```
+
+#### Passing
+
+```tsx
+
+
+
+```
+
+### Custom Forbidden Props
+
+Configuration:
+
+```json
+{
+ "forbid": ["className", "style"]
+}
+```
+
+#### Failing
+
+```tsx
+
+
+```
+
+#### Passing
+
+```tsx
+
+
+```
+
+### Regex Patterns
+
+Configuration:
+
+```json
+{
+ "forbid": [
+ { "prop": "/^data-/" },
+ { "prop": "/^aria-/" }
+ ]
+}
+```
+
+#### Failing
+
+```tsx
+
+
+
+```
+
+#### Passing
+
+```tsx
+
+
+```
+
+### Node-Specific Exclusions
+
+Configuration:
+
+```json
+{
+ "forbid": [
+ {
+ "prop": "/_/",
+ "excludedNodes": ["Button"]
+ }
+ ]
+}
+```
+
+#### Failing
+
+```tsx
+
+
+```
+
+#### Passing
+
+```tsx
+ ;
+```
+
+### Node-Specific Inclusions
+
+Configuration:
+
+```json
+{
+ "forbid": [
+ {
+ "prop": "/_/",
+ "includedNodes": ["Button", "Input"]
+ }
+ ]
+}
+```
+
+#### Failing
+
+```tsx
+
+
+```
+
+#### Passing
+
+```tsx
+
+
+```
+
+### Mixed Configuration
+
+```json
+{
+ "forbid": [
+ "className",
+ { "prop": "/^data-/", "excludedNodes": ["Button"] },
+ { "prop": "/^aria-/", "includedNodes": ["Input"] }
+ ]
+}
+```
+
+This configuration:
+
+- Forbids `className` on all components
+- Forbids `data-*` props on all components except `Button`
+- Forbids `aria-*` props only on `Input` components
+
+## Implementation
+
+- [Rule source](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-x/src/rules/no-forbidden-props.ts)
+- [Test source](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-x/src/rules/no-forbidden-props.spec.ts)
+
+## Notes
+
+- Spread attributes (`{...props}`) are ignored by this rule
+- JSXMemberExpression components (like `React.Component`) are supported for prop checking, but node-specific filtering may not work as expected
+- Regex patterns should be wrapped in forward slashes (e.g., `/pattern/`)
+- The rule processes each forbidden prop configuration independently
diff --git a/packages/plugins/eslint-plugin-react-x/src/rules/no-forbidden-props.spec.ts b/packages/plugins/eslint-plugin-react-x/src/rules/no-forbidden-props.spec.ts
new file mode 100644
index 0000000000..52f605fc7d
--- /dev/null
+++ b/packages/plugins/eslint-plugin-react-x/src/rules/no-forbidden-props.spec.ts
@@ -0,0 +1,175 @@
+import tsx from "dedent";
+
+import { allValid, ruleTester } from "../../../../../test";
+import rule, { RULE_NAME } from "./no-forbidden-props";
+
+ruleTester.run(RULE_NAME, rule, {
+ invalid: [
+ // Default behavior - snake_case props are forbidden
+ {
+ code: tsx`
+
+ `,
+ errors: [{ messageId: "noForbiddenProps", data: { name: "snake_case" } }],
+ },
+ {
+ code: tsx`
+
+ `,
+ errors: [{ messageId: "noForbiddenProps", data: { name: "user_name" } }],
+ },
+ // String-based forbidden props
+ {
+ code: tsx`
+
+ `,
+ errors: [{ messageId: "noForbiddenProps", data: { name: "className" } }],
+ options: [{ forbid: ["className"] }],
+ },
+ // Regex-based forbidden props
+ {
+ code: tsx`
+
+ `,
+ errors: [{ messageId: "noForbiddenProps", data: { name: "data-testid" } }],
+ options: [{ forbid: [{ prop: "/^data-/" }] }],
+ },
+ // Node-specific exclusions - should still fail for non-excluded nodes
+ {
+ code: tsx`
+
+ `,
+ errors: [{ messageId: "noForbiddenProps", data: { name: "snake_case" } }],
+ options: [{
+ forbid: [{
+ excludedNodes: ["Button"],
+ prop: "/_/",
+ }],
+ }],
+ },
+ // Node-specific inclusions
+ {
+ code: tsx`
+
+ `,
+ errors: [{ messageId: "noForbiddenProps", data: { name: "snake_case" } }],
+ options: [{
+ forbid: [{
+ includedNodes: ["Button"],
+ prop: "/_/",
+ }],
+ }],
+ },
+ // Multiple forbidden props
+ {
+ code: tsx`
+
+ `,
+ errors: [
+ { messageId: "noForbiddenProps", data: { name: "className" } },
+ { messageId: "noForbiddenProps", data: { name: "style" } },
+ ],
+ options: [{ forbid: ["className", "style"] }],
+ },
+ // Mixed string and object configurations
+ {
+ code: tsx`
+
+ `,
+ errors: [
+ { messageId: "noForbiddenProps", data: { name: "className" } },
+ { messageId: "noForbiddenProps", data: { name: "data-testid" } },
+ ],
+ options: [{
+ forbid: [
+ "className",
+ { excludedNodes: ["Button"], prop: "/^data-/" },
+ ],
+ }],
+ },
+ // Namespaced JSX elements
+ {
+ code: tsx`
+
+ `,
+ errors: [{ messageId: "noForbiddenProps", data: { name: "snake_case" } }],
+ },
+ // JSXMemberExpression - should still be checked for forbidden props
+ {
+ code: tsx`
+
+ `,
+ errors: [{ messageId: "noForbiddenProps", data: { name: "snake_case" } }],
+ },
+ ],
+ valid: [
+ ...allValid,
+ // Default behavior - camelCase props are allowed
+ tsx`
+
+ `,
+ tsx`
+
+ `,
+ // String-based forbidden props - other props allowed
+ {
+ code: tsx`
+
+ `,
+ options: [{ forbid: ["className"] }],
+ },
+ {
+ code: tsx`
+
+ `,
+ options: [{ forbid: ["style"] }],
+ },
+ // Regex-based forbidden props - non-matching props allowed
+ {
+ code: tsx`
+
+ `,
+ options: [{ forbid: [{ prop: "/^data-/" }] }],
+ },
+ {
+ code: tsx`
+
+ `,
+ options: [{ forbid: [{ prop: "/^aria-/" }] }],
+ },
+ // Node-specific exclusions - excluded nodes should be allowed
+ {
+ code: tsx`
+
+ `,
+ options: [{
+ forbid: [{
+ excludedNodes: ["Button"],
+ prop: "/_/",
+ }],
+ }],
+ },
+ // Node-specific inclusions - other nodes allowed
+ {
+ code: tsx`
+
+ `,
+ options: [{
+ forbid: [{
+ includedNodes: ["Button"],
+ prop: "/_/",
+ }],
+ }],
+ },
+
+ // Complex nested structures
+ {
+ code: tsx`
+
+
+
+ `,
+ options: [{ forbid: ["style"] }],
+ },
+ ],
+});
diff --git a/packages/plugins/eslint-plugin-react-x/src/rules/no-forbidden-props.ts b/packages/plugins/eslint-plugin-react-x/src/rules/no-forbidden-props.ts
new file mode 100644
index 0000000000..e83806252e
--- /dev/null
+++ b/packages/plugins/eslint-plugin-react-x/src/rules/no-forbidden-props.ts
@@ -0,0 +1,129 @@
+import type { RuleListener } from "@typescript-eslint/utils/ts-eslint";
+import { type RuleContext, type RuleFeature, RegExp } from "@eslint-react/kit";
+
+import { AST_NODE_TYPES as T } from "@typescript-eslint/types";
+import { camelCase, type CamelCase } from "string-ts";
+import { createRule } from "../utils";
+
+export const RULE_NAME = "no-forbidden-props";
+
+export const RULE_FEATURES = ["CFG"] as const satisfies RuleFeature[];
+export type MessageID = CamelCase;
+const messageId = camelCase(RULE_NAME);
+
+type Options = readonly [
+ {
+ forbid: (string | {
+ excludedNodes?: string[];
+ prop: string;
+ } | {
+ includedNodes?: string[];
+ prop: string;
+ })[];
+ },
+];
+
+const defaultOptions = [{
+ forbid: [{ prop: "/_/" }],
+}] as const satisfies Options;
+
+export default createRule({
+ meta: {
+ type: "problem",
+ defaultOptions: [...defaultOptions],
+ docs: {
+ description: "Disallow certain props on components.",
+ },
+ messages: {
+ [messageId]: 'Prop "{{name}}" is forbidden.',
+ },
+ schema: [{
+ type: "object",
+ properties: {
+ forbid: {
+ type: "array",
+ items: {
+ anyOf: [
+ { type: "string" },
+ {
+ type: "object",
+ additionalProperties: false,
+ properties: {
+ excludedNodes: {
+ type: "array",
+ items: { type: "string" },
+ uniqueItems: true,
+ },
+ prop: { type: "string" },
+ },
+ required: ["prop"],
+ },
+ {
+ type: "object",
+ additionalProperties: false,
+ properties: {
+ includedNodes: {
+ type: "array",
+ items: { type: "string" },
+ uniqueItems: true,
+ },
+ prop: { type: "string" },
+ },
+ required: ["prop"],
+ },
+ ],
+ },
+ },
+ },
+ }],
+ },
+ name: RULE_NAME,
+ create,
+ defaultOptions,
+});
+
+export function create(context: RuleContext, [option]: Options): RuleListener {
+ const { forbid = [{ prop: "/_/" }] } = option;
+
+ return {
+ JSXOpeningElement(node) {
+ let nodeName: string | null = null;
+ if (node.name.type === T.JSXIdentifier) {
+ nodeName = node.name.name;
+ } else if (node.name.type === T.JSXNamespacedName) {
+ nodeName = node.name.name.name;
+ }
+
+ for (const attr of node.attributes) {
+ if (attr.type === T.JSXSpreadAttribute) {
+ continue;
+ }
+ const name = attr.name.name;
+ if (typeof name !== "string") {
+ continue;
+ }
+ for (const forbiddenPropItem of forbid) {
+ if (typeof forbiddenPropItem !== "string" && nodeName != null) {
+ if ("excludedNodes" in forbiddenPropItem && forbiddenPropItem.excludedNodes.includes(nodeName)) {
+ continue;
+ }
+ if ("includedNodes" in forbiddenPropItem && !forbiddenPropItem.includedNodes.includes(nodeName)) {
+ continue;
+ }
+ }
+ const forbiddenProp = typeof forbiddenPropItem === "string" ? forbiddenPropItem : forbiddenPropItem.prop;
+
+ const forbiddenPropRegExp = RegExp.toRegExp(forbiddenProp);
+ if (forbiddenPropRegExp.test(name)) {
+ context.report({
+ messageId,
+ node: attr,
+ data: { name },
+ });
+ }
+
+ }
+ }
+ },
+ };
+}