diff --git a/README.md b/README.md
index 17c9c2c00..2b0322f84 100644
--- a/README.md
+++ b/README.md
@@ -139,6 +139,7 @@ Rule Name | Description | Since
`react-a11y-anchors` | For accessibility of your website, anchor element link text should be at least 4 characters long. Links with the same HREF should have the same link text. Links that point to different HREFs should have different link text. Links with images and text content, the alt attribute should be unique to the text content or empty. An an anchor element's href prop value must not be just #. References: [WCAG Rule 38: Link text should be as least four 4 characters long](http://oaa-accessibility.org/wcag20/rule/38/) [WCAG Rule 39: Links with the same HREF should have the same link text](http://oaa-accessibility.org/wcag20/rule/39/) [WCAG Rule 41: Links that point to different HREFs should have different link text](http://oaa-accessibility.org/wcag20/rule/41/) [WCAG Rule 43: Links with images and text content, the alt attribute should be unique to the text content or empty](http://oaa-accessibility.org/wcag20/rule/43/) | 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-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/TR/wai-aria/states_and_properties#state_prop_def). | 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
`react-a11y-role-supports-aria-props` | For accessibility of your website, enforce that elements with explicit or implicit roles defined contain only `aria-*` properties supported by that `role`. Many aria attributes (states and properties) can only be used on elements with particular roles. Some elements have implicit roles, such as ``, which will be resolved to `role='link'`. A reference for the implicit roles can be found at [Default Implicit ARIA Semantics](https://www.w3.org/TR/html-aria/#sec-strong-native-semantics). References: * [ARIA attributes can only be used with certain roles](http://oaa-accessibility.org/wcag20/rule/87/) * [Check aria properties and states for valid roles and properties](http://oaa-accessibility.org/wcag20/rule/84/) * [Check that 'ARIA-' attributes are valid properties and states](http://oaa-accessibility.org/wcag20/rule/93/)| 2.0.11
`react-a11y-role` | For accessibility of your website, elements with aria roles must use a **valid**, **non-abstract** aria role. A reference to role defintions can be found at [WAI-ARIA roles](https://www.w3.org/TR/wai-aria/roles#role_definitions). References: * [WCAG Rule 92: Role value must be valid](http://oaa-accessibility.org/wcag20/rule/92/)| 2.0.11
diff --git a/src/reactA11yProptypesRule.ts b/src/reactA11yProptypesRule.ts
new file mode 100644
index 000000000..6545d2fcc
--- /dev/null
+++ b/src/reactA11yProptypesRule.ts
@@ -0,0 +1,219 @@
+/**
+ * Enforce ARIA state and property values are valid.
+ */
+
+import * as ts from 'typescript';
+import * as Lint from 'tslint/lib/lint';
+
+import { AstUtils } from './utils/AstUtils';
+import { ExtendedMetadata } from './utils/ExtendedMetadata';
+import { getPropName, getStringLiteral } from './utils/JsxAttribute';
+import { IAria } from './utils/attributes/IAria';
+import {
+ isStringLiteral,
+ isNumericLiteral,
+ isJsxExpression,
+ isFalseKeyword,
+ isTrueKeyword,
+ isNullKeyword
+} from './utils/TypeGuard';
+
+// tslint:disable-next-line:no-require-imports no-var-requires
+const aria: { [attributeName: string]: IAria } = require('./utils/attributes/ariaSchema.json');
+
+export function getFailureString(propName: string, expectedType: string, permittedValues: string[]): string {
+ switch (expectedType) {
+ case 'tristate':
+ return `The value for ${propName} must be a boolean or the string 'mixed'.`;
+ case 'token':
+ return `The value for ${propName} must be a single token from the following: ${permittedValues}.`;
+ case 'tokenlist':
+ return `The value for ${propName} must be a list of one or more tokens from the following: ${permittedValues}.`;
+ case 'boolean':
+ case 'string':
+ case 'integer':
+ case 'number':
+ default: // tslint:disable-line:no-switch-case-fall-through
+ return `The value for ${propName} must be a ${expectedType}.`;
+ }
+}
+
+export class Rule extends Lint.Rules.AbstractRule {
+ public static metadata: ExtendedMetadata = {
+ ruleName: 'react-a11y-proptypes',
+ type: 'maintainability',
+ description: 'Enforce ARIA state and property values are valid.',
+ options: null,
+ 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 ReactA11yProptypesWalker(sourceFile, this.getOptions()))
+ : [];
+ }
+}
+
+class ReactA11yProptypesWalker extends Lint.RuleWalker {
+ public visitJsxAttribute(node: ts.JsxAttribute): void {
+ const propName: string = getPropName(node).toLowerCase();
+
+ // If there is no aria-* attribute, skip it.
+ if (!aria[propName]) {
+ return;
+ }
+
+ const allowUndefined: boolean = aria[propName].allowUndefined != null
+ ? aria[propName].allowUndefined
+ : false;
+ const expectedType: string = aria[propName].type;
+ const permittedValues: string[] = aria[propName].values;
+ const propValue: string = getStringLiteral(node);
+
+ if (this.isUndefined(node.initializer)) {
+ if (!allowUndefined) {
+ this.addFailure(this.createFailure(
+ node.getStart(), node.getWidth(), getFailureString(propName, expectedType, permittedValues)
+ ));
+ }
+ return;
+ } else if (this.isComplexType(node.initializer)) {
+ return;
+ }
+
+ if (!this.validityCheck(node.initializer, propValue, expectedType, permittedValues)) {
+ this.addFailure(this.createFailure(
+ node.getStart(),
+ node.getWidth(),
+ getFailureString(propName, expectedType, permittedValues)
+ ));
+ }
+ }
+
+ private validityCheck(
+ propValueExpression: ts.Expression,
+ propValue: string,
+ expectedType: string,
+ permittedValues: string[]
+ ): boolean {
+ switch (expectedType) {
+ case 'boolean': return this.isBoolean(propValueExpression);
+ case 'tristate': return this.isBoolean(propValueExpression) || this.isMixed(propValueExpression);
+ case 'integer': return this.isInteger(propValueExpression);
+ case 'number': return this.isNumber(propValueExpression);
+ case 'string': return this.isString(propValueExpression);
+ case 'token':
+ return this.isString(propValueExpression) && permittedValues.indexOf(propValue.toLowerCase()) > -1;
+ case 'tokenlist':
+ return this.isString(propValueExpression) &&
+ propValue.split(' ').every(token => permittedValues.indexOf(token.toLowerCase()) > -1);
+ default:
+ return false;
+ }
+ }
+
+ private isUndefined(node: ts.Expression): boolean {
+ if (!node) {
+ return true;
+ } else if (isJsxExpression(node)) {
+ const expression: ts.Expression = node.expression;
+ if (!expression) {
+ return true;
+ } else if (AstUtils.isUndefined(expression)) {
+ return true;
+ } else if (isNullKeyword(expression)) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * For this case