diff --git a/__tests__/src/rules/no-disabled-test.js b/__tests__/src/rules/no-disabled-test.js
new file mode 100644
index 000000000..97ce04d4b
--- /dev/null
+++ b/__tests__/src/rules/no-disabled-test.js
@@ -0,0 +1,53 @@
+/* eslint-env jest */
+/**
+ * @fileoverview Enforce disabled prop is not used unless specifically intended.
+ * @author Courtney Nguyen <@courtyenn>
+ */
+
+// -----------------------------------------------------------------------------
+// Requirements
+// -----------------------------------------------------------------------------
+
+import { RuleTester } from 'eslint';
+import rule from '../../../src/rules/no-disabled';
+import parserOptionsMapper from '../../__util__/parserOptionsMapper';
+
+// -----------------------------------------------------------------------------
+// Tests
+// -----------------------------------------------------------------------------
+
+const ruleTester = new RuleTester();
+
+const expectedWarning = {
+ message: 'The disabled prop removes the element from being detected by screen readers.',
+ type: 'JSXAttribute',
+};
+
+ruleTester.run('no-disabled', rule, {
+ valid: [
+ { code: '
' },
+ { code: '' },
+ { code: '' },
+ { code: '' },
+ { code: '' },
+ { code: '' },
+ { code: '' },
+ { code: '' },
+ { code: '' },
+ { code: '' },
+ { code: '' },
+ ].map(parserOptionsMapper),
+ invalid: [
+ { code: '', errors: [expectedWarning] },
+ { code: '', errors: [expectedWarning] },
+ { code: '', errors: [expectedWarning] },
+ { code: '', errors: [expectedWarning] },
+ { code: '', errors: [expectedWarning] },
+ { code: '', errors: [expectedWarning] },
+ { code: '', errors: [expectedWarning] },
+ { code: '', errors: [expectedWarning] },
+ { code: '', errors: [expectedWarning] },
+ { code: '', errors: [expectedWarning] },
+ { code: '', errors: [expectedWarning] },
+ ].map(parserOptionsMapper),
+});
diff --git a/docs/rules/no-disabled.md b/docs/rules/no-disabled.md
new file mode 100644
index 000000000..8733f32eb
--- /dev/null
+++ b/docs/rules/no-disabled.md
@@ -0,0 +1,28 @@
+# no-disabled
+
+Rule that `disabled` prop should be cautioned on elements. Disabling interactive elements removes the element from the accessibility tree. Consider using `aria-disabled`.
+
+## Rule details
+The general consensus is that `disabled` should be used with specific intent. It goes against intuition since `disabled` is a native HTML attribute, which disables. However, from a usability stand-point it is not a good UX for screen readers: It removes the element from the a11y tree, and may omit critical information. It is best to use `aria-disabled` and add the additional JS logic to "disable" the element.
+
+### Succeed
+```jsx
+
+
+
+
+
+
+```
+
+### Fail
+```jsx
+
+
+
+
+```
+
+### Resources
+- [MDN aria-disabled](https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Attributes/aria-disabled)
+- [W3 KBD Disabled Controls](https://www.w3.org/TR/wai-aria-practices/#kbd_disabled_controls)
diff --git a/src/index.js b/src/index.js
index 052e0f163..12faf9d3b 100644
--- a/src/index.js
+++ b/src/index.js
@@ -26,6 +26,7 @@ module.exports = {
'mouse-events-have-key-events': require('./rules/mouse-events-have-key-events'),
'no-access-key': require('./rules/no-access-key'),
'no-autofocus': require('./rules/no-autofocus'),
+ 'no-disabled': require('./rules/no-disabled'),
'no-distracting-elements': require('./rules/no-distracting-elements'),
'no-interactive-element-to-noninteractive-role': require('./rules/no-interactive-element-to-noninteractive-role'),
'no-noninteractive-element-interactions': require('./rules/no-noninteractive-element-interactions'),
@@ -113,6 +114,19 @@ module.exports = {
'jsx-a11y/mouse-events-have-key-events': 'error',
'jsx-a11y/no-access-key': 'error',
'jsx-a11y/no-autofocus': 'error',
+ 'jsx-a11y/no-disabled': ['warn', {
+ disabable: [
+ 'button',
+ 'command',
+ 'fieldset',
+ 'keygen',
+ 'optgroup',
+ 'option',
+ 'select',
+ 'textarea',
+ 'input',
+ ],
+ }],
'jsx-a11y/no-distracting-elements': 'error',
'jsx-a11y/no-interactive-element-to-noninteractive-role': [
'error',
@@ -270,6 +284,19 @@ module.exports = {
'jsx-a11y/mouse-events-have-key-events': 'error',
'jsx-a11y/no-access-key': 'error',
'jsx-a11y/no-autofocus': 'error',
+ 'jsx-a11y/no-disabled': ['warn', {
+ disabable: [
+ 'button',
+ 'command',
+ 'fieldset',
+ 'keygen',
+ 'optgroup',
+ 'option',
+ 'select',
+ 'textarea',
+ 'input',
+ ],
+ }],
'jsx-a11y/no-distracting-elements': 'error',
'jsx-a11y/no-interactive-element-to-noninteractive-role': 'error',
'jsx-a11y/no-noninteractive-element-interactions': [
diff --git a/src/rules/no-disabled.js b/src/rules/no-disabled.js
new file mode 100644
index 000000000..1292921f0
--- /dev/null
+++ b/src/rules/no-disabled.js
@@ -0,0 +1,64 @@
+/**
+ * @fileoverview Rule to flag 'disabled' prop
+ * @author Courtney Nguyen <@courtyenn>
+ */
+
+// ----------------------------------------------------------------------------
+// Rule Definition
+// ----------------------------------------------------------------------------
+
+import { propName, elementType } from 'jsx-ast-utils';
+import {
+ enumArraySchema,
+ generateObjSchema,
+} from '../util/schemas';
+
+const warningMessage = 'The disabled prop removes the element from being detected by screen readers.';
+
+const DEFAULT_ELEMENTS = [
+ 'button',
+ 'command',
+ 'fieldset',
+ 'keygen',
+ 'optgroup',
+ 'option',
+ 'select',
+ 'textarea',
+ 'input',
+];
+
+const schema = generateObjSchema({
+ disabable: enumArraySchema(DEFAULT_ELEMENTS)
+ .filter((name) => DEFAULT_ELEMENTS.includes(name)),
+});
+
+export default {
+ meta: {
+ type: 'suggestion',
+ docs: {
+ recommended: false,
+ url: 'https://github.com/jsx-eslint/eslint-plugin-jsx-a11y/tree/HEAD/docs/rules/no-disabled.md',
+ },
+ schema: [schema],
+ },
+
+ create: (context) => ({
+ JSXAttribute: (attribute) => {
+ const disabable = (
+ context.options && context.options[0] && context.options[0].disabable
+ ) || DEFAULT_ELEMENTS;
+ // Only monitor eligible elements for "disabled".
+ const type = elementType(attribute.parent);
+ if (!disabable.includes(type)) {
+ return;
+ }
+
+ if (propName(attribute) === 'disabled') {
+ context.report({
+ node: attribute,
+ message: warningMessage,
+ });
+ }
+ },
+ }),
+};