diff --git a/README.md b/README.md
index 50be33dd8..169f37568 100644
--- a/README.md
+++ b/README.md
@@ -135,6 +135,7 @@ Rule Name | Description | Since
`react-a11y-img-has-alt` | Enforce that an `img` element contains the `alt` attribute or `role='presentation'` for a decorative image. All images must have `alt` text to convey their purpose and meaning to **screen reader users**. Besides, the `alt` attribute specifies an alternate text for an image, if the image cannot be displayed. This rule accepts as a parameter a string array for tag names other than img to also check. For example, if you use a custom tag named 'Image' then configure the rule with: `[true, ['Image']]` References: [Web Content Accessibility Guidelines 1.0](https://www.w3.org/TR/WCAG10/wai-pageauth.html#tech-text-equivalent) [ARIA Presentation Role](https://www.w3.org/TR/wai-aria/roles#presentation) [WCAG Rule 31: If an image has an alt or title attribute, it should not have a presentation role](http://oaa-accessibility.org/wcag20/rule/31/) | 2.0.11
`react-a11y-lang` | For accessibility of your website, HTML elements must have a lang attribute and the attribute must be a valid language code. References: * [H58: Using language attributes to identify changes in the human language](https://www.w3.org/TR/WCAG20-TECHS/H58.html) * [lang attribute must have a valid value](https://dequeuniversity.com/rules/axe/1.1/valid-lang) [List of ISO 639-1 codes](https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes) | 2.0.11
`react-a11y-meta` | For accessibility of your website, HTML meta elements must not have http-equiv="refresh". | 2.0.11
+`react-a11y-no-onchange` | For accessibility of your website, enforce usage of onBlur over onChange on select menus. | 5.2.3
`react-a11y-props` | For accessibility of your website, enforce all `aria-*` attributes are valid. Elements cannot use an invalid `aria-*` attribute. This rule will fail if it finds an `aria-*` attribute that is not listed in [WAI-ARIA states and properties](https://www.w3.org/WAI/PF/aria/states_and_properties#state_prop_values). | 2.0.11
`react-a11y-proptypes` | For accessibility of your website, enforce the type of aria state and property values are correct. | 2.0.11
`react-a11y-role-has-required-aria-props` | For accessibility of your website, elements with aria roles must have all required attributes according to the role. References: [ARIA Definition of Roles](https://www.w3.org/TR/wai-aria/roles#role_definitions) [WCAG Rule 90: Required properties and states should be defined](http://oaa-accessibility.org/wcag20/rule/90/) [WCAG Rule 91: Required properties and states must not be empty](http://oaa-accessibility.org/wcag20/rule/91/) | 2.0.11
diff --git a/recommended_ruleset.js b/recommended_ruleset.js
index feedb7c84..26d5071b3 100644
--- a/recommended_ruleset.js
+++ b/recommended_ruleset.js
@@ -188,6 +188,7 @@ module.exports = {
"react-a11y-img-has-alt": true,
"react-a11y-lang": true,
"react-a11y-meta": true,
+ "react-a11y-no-onchange": true,
"react-a11y-props": true,
"react-a11y-proptypes": true,
"react-a11y-role": true,
diff --git a/src/reactA11yNoOnchangeRule.ts b/src/reactA11yNoOnchangeRule.ts
new file mode 100644
index 000000000..838d2ba0b
--- /dev/null
+++ b/src/reactA11yNoOnchangeRule.ts
@@ -0,0 +1,64 @@
+import * as ts from 'typescript';
+import * as Lint from 'tslint';
+
+import {ErrorTolerantWalker} from './utils/ErrorTolerantWalker';
+import {ExtendedMetadata} from './utils/ExtendedMetadata';
+import {getJsxAttributesFromJsxElement} from './utils/JsxAttribute';
+
+/**
+ * Implementation of the react-a11y-no-onchange rule.
+ */
+export class Rule extends Lint.Rules.AbstractRule {
+
+ public static metadata: ExtendedMetadata = {
+ ruleName: 'react-a11y-no-onchange',
+ type: 'functionality',
+ description: 'For accessibility of your website, enforce usage of onBlur over onChange on select menus.',
+ options: 'string[]',
+ optionsDescription: 'Additional tag names to validate.',
+ optionExamples: ['true', '[true, ["Select"]]'],
+ typescriptOnly: false,
+ issueClass: 'Non-SDL',
+ issueType: 'Warning',
+ severity: 'Important',
+ level: 'Opportunity for Excellence',
+ group: 'Accessibility'
+ };
+
+ public apply(sourceFile: ts.SourceFile): Lint.RuleFailure[] {
+ return sourceFile.languageVariant === ts.LanguageVariant.JSX ?
+ this.applyWithWalker(new ReactA11yNoOnchangeRuleWalker(sourceFile, this.getOptions())) :
+ [];
+ }
+}
+
+class ReactA11yNoOnchangeRuleWalker extends ErrorTolerantWalker {
+ protected visitJsxSelfClosingElement(node: ts.JsxSelfClosingElement): void {
+ this.checkJsxOpeningElement(node);
+ super.visitJsxSelfClosingElement(node);
+ }
+
+ protected visitJsxElement(node: ts.JsxElement): void {
+ this.checkJsxOpeningElement(node.openingElement);
+ super.visitJsxElement(node);
+ }
+
+ private checkJsxOpeningElement(node: ts.JsxOpeningLikeElement) {
+ const tagName: string = node.tagName.getText();
+ const options: any[] = this.getOptions();
+
+ const additionalTagNames: string[] = options.length > 0 ? options[0] : [];
+
+ const targetTagNames: string[] = ['select', ...additionalTagNames];
+
+ if (!tagName || targetTagNames.indexOf(tagName) === -1) {
+ return;
+ }
+
+ const attributes = getJsxAttributesFromJsxElement(node);
+ if (attributes.hasOwnProperty('onchange')) {
+ const errorMessage = `onChange event handler should not be used with the <${tagName}>. Please use onBlur instead.`;
+ this.addFailureAt(node.getStart(), node.getWidth(), errorMessage);
+ }
+ }
+}
diff --git a/src/tests/ReactA11yNoOnchangeRuleTests.ts b/src/tests/ReactA11yNoOnchangeRuleTests.ts
new file mode 100644
index 000000000..0fdc38039
--- /dev/null
+++ b/src/tests/ReactA11yNoOnchangeRuleTests.ts
@@ -0,0 +1,61 @@
+import {Utils} from '../utils/Utils';
+import {TestHelper} from './TestHelper';
+
+/**
+ * Unit tests.
+ */
+describe('reactA11yNoOnchangeRule', () : void => {
+ const ruleName : string = 'react-a11y-no-onchange';
+ const errorMessage = (tagName: string): string =>
+ `onChange event handler should not be used with the <${tagName}>. Please use onBlur instead.`;
+
+ it('should pass if select element attributes without onChange event', () : void => {
+ const script: string = `
+ import React = require('react');
+ const selectElement =
+ const selectElementWithOnBlur = `;
+
+ TestHelper.assertNoViolation(ruleName, script);
+ });
+
+ it('should fail if select element attributes contains onChange event', () : void => {
+ const script : string = `
+ import React = require('react');
+ const selectElementWithOnChange = \`;
+ `;
+
+ TestHelper.assertViolations(ruleName, script, [{
+ failure: errorMessage('select'),
+ name: Utils.absolutePath('file.tsx'),
+ ruleName,
+ ruleSeverity: "ERROR",
+ startPosition: {character: 47, line: 3}
+ }]);
+ });
+
+ it('should fail if additional tag name specified in options contains onChange event', () => {
+ const script : string = `
+ import React = require('react');
+ const selectElementWithOnChange =
+ const selectElementWithOnChange =
+ `;
+
+ TestHelper.assertViolationsWithOptions(ruleName, ['Select'], script, [{
+ failure: errorMessage('Select'),
+ name: Utils.absolutePath('file.tsx'),
+ ruleName,
+ ruleSeverity: "ERROR",
+ startPosition: {character: 47, line: 3}
+ }, {
+ failure: errorMessage('select'),
+ name: Utils.absolutePath('file.tsx'),
+ ruleName,
+ ruleSeverity: 'ERROR',
+ startPosition: {
+ 'character': 47,
+ 'line': 4
+ }
+ }]);
+ });
+
+});
diff --git a/tslint-warnings.csv b/tslint-warnings.csv
index 90531cb2d..c4e08cae8 100644
--- a/tslint-warnings.csv
+++ b/tslint-warnings.csv
@@ -258,6 +258,7 @@ react-a11y-event-has-role,Elements with event handlers must have role attribute.
react-a11y-image-button-has-alt,Enforce that inputs element with type="image" must have alt attribute.,TSLINTVBN64L,tslint,Non-SDL,Warning,Important,Opportunity for Excellence,See description on the tslint or tslint-microsoft-contrib website,TSLint Procedure,,
react-a11y-img-has-alt,"Enforce that an img element contains the non-empty alt attribute. For decorative images, using empty alt attribute and role="presentation".",TSLINT1OM69KS,tslint,Non-SDL,Warning,Important,Opportunity for Excellence,See description on the tslint or tslint-microsoft-contrib website,TSLint Procedure,,
react-a11y-lang,"For accessibility of your website, html elements must have a valid lang attribute.",TSLINTQ046RM,tslint,Non-SDL,Warning,Low,Opportunity for Excellence,See description on the tslint or tslint-microsoft-contrib website,TSLint Procedure,,
+react-a11y-no-onchange,"For accessibility of your website, enforce usage of onBlur over onChange on select menus.",TSLINTNO0TDD,tslint,Non-SDL,Warning,Important,Opportunity for Excellence,See description on the tslint or tslint-microsoft-contrib website,TSLint Procedure,,
react-a11y-props,Enforce all `aria-*` attributes are valid. Elements cannot use an invalid `aria-*` attribute.,TSLINT1682S78,tslint,Non-SDL,Warning,Important,Opportunity for Excellence,See description on the tslint or tslint-microsoft-contrib website,TSLint Procedure,,
react-a11y-proptypes,Enforce ARIA state and property values are valid.,TSLINT1DLB1JE,tslint,Non-SDL,Warning,Important,Opportunity for Excellence,See description on the tslint or tslint-microsoft-contrib website,TSLint Procedure,,
react-a11y-role,"Elements with aria roles must use a **valid**, **non-abstract** aria role.",TSLINTQ0A2FU,tslint,Non-SDL,Warning,Important,Opportunity for Excellence,See description on the tslint or tslint-microsoft-contrib website,TSLint Procedure,,
diff --git a/tslint.json b/tslint.json
index 715246adc..bfbd7fdf3 100644
--- a/tslint.json
+++ b/tslint.json
@@ -229,6 +229,7 @@
"react-a11y-img-has-alt": true,
"react-a11y-lang": true,
"react-a11y-meta": true,
+ "react-a11y-no-onchange": true,
"react-a11y-props": true,
"react-a11y-proptypes": true,
"react-a11y-role": true,