diff --git a/docs/rules/button-has-type.md b/docs/rules/button-has-type.md
new file mode 100644
index 0000000000..32ae8dcd3e
--- /dev/null
+++ b/docs/rules/button-has-type.md
@@ -0,0 +1,58 @@
+# Prevent usage of `button` elements without an explicit `type` attribute (react/button-has-type)
+
+The default value of `type` attribute for `button` HTML element is `"submit"` which is often not the desired behavior and may lead to unexpected page reloads.
+This rules enforces an explicit `type` attribute for all the `button` elements and checks that its value is valid per spec (i.e., is one of `"button"`, `"submit"`, and `"reset"`).
+
+## Rule Details
+
+The following patterns are considered errors:
+
+```jsx
+var Hello =
+var Hello =
+
+var Hello = React.createElement('button', {}, 'Hello')
+var Hello = React.createElement('button', {type: 'foo'}, 'Hello')
+```
+
+The following patterns are **not** considered errors:
+
+```jsx
+var Hello = Hello
+var Hello = Hello
+var Hello =
+var Hello =
+var Hello =
+
+var Hello = React.createElement('span', {}, 'Hello')
+var Hello = React.createElement('span', {type: 'foo'}, 'Hello')
+var Hello = React.createElement('button', {type: 'button'}, 'Hello')
+var Hello = React.createElement('button', {type: 'submit'}, 'Hello')
+var Hello = React.createElement('button', {type: 'reset'}, 'Hello')
+```
+
+## Rule Options
+
+```js
+...
+"react/default-props-match-prop-types": [, {
+ "button": ,
+ "submit": ,
+ "reset":
+}]
+...
+```
+
+You can forbid particular type attribute values by passing `false` as corresponding option (by default all of them are `true`).
+
+The following patterns are considered errors when using `"react/default-props-match-prop-types": ["error", {reset: false}]`:
+
+```jsx
+var Hello =
+
+var Hello = React.createElement('button', {type: 'reset'}, 'Hello')
+```
+
+## When Not To Use It
+
+If you use only `"submit"` buttons, you can disable this rule
diff --git a/index.js b/index.js
index a798443eb4..6c1a933ac9 100644
--- a/index.js
+++ b/index.js
@@ -29,6 +29,7 @@ const allRules = {
'jsx-no-duplicate-props': require('./lib/rules/jsx-no-duplicate-props'),
'jsx-no-literals': require('./lib/rules/jsx-no-literals'),
'jsx-no-target-blank': require('./lib/rules/jsx-no-target-blank'),
+ 'button-has-type': require('./lib/rules/button-has-type'),
'jsx-no-undef': require('./lib/rules/jsx-no-undef'),
'jsx-curly-brace-presence': require('./lib/rules/jsx-curly-brace-presence'),
'jsx-pascal-case': require('./lib/rules/jsx-pascal-case'),
diff --git a/lib/rules/button-has-type.js b/lib/rules/button-has-type.js
new file mode 100644
index 0000000000..e720bcd040
--- /dev/null
+++ b/lib/rules/button-has-type.js
@@ -0,0 +1,121 @@
+/**
+ * @fileoverview Forbid "button" element without an explicit "type" attribute
+ * @author Filipp Riabchun
+ */
+'use strict';
+
+const getProp = require('jsx-ast-utils/getProp');
+const getLiteralPropValue = require('jsx-ast-utils/getLiteralPropValue');
+
+// ------------------------------------------------------------------------------
+// Helpers
+// ------------------------------------------------------------------------------
+
+function isCreateElement(node) {
+ return node.callee
+ && node.callee.type === 'MemberExpression'
+ && node.callee.property.name === 'createElement'
+ && node.arguments.length > 0;
+}
+
+// ------------------------------------------------------------------------------
+// Rule Definition
+// ------------------------------------------------------------------------------
+
+module.exports = {
+ meta: {
+ docs: {
+ description: 'Forbid "button" element without an explicit "type" attribute',
+ category: 'Possible Errors',
+ recommended: false
+ },
+ schema: [{
+ type: 'object',
+ properties: {
+ button: {
+ default: true,
+ type: 'boolean'
+ },
+ submit: {
+ default: true,
+ type: 'boolean'
+ },
+ reset: {
+ default: true,
+ type: 'boolean'
+ }
+ },
+ additionalProperties: false
+ }]
+ },
+
+ create: function(context) {
+ const configuration = Object.assign({
+ button: true,
+ submit: true,
+ reset: true
+ }, context.options[0]);
+
+ function reportMissing(node) {
+ context.report({
+ node: node,
+ message: 'Missing an explicit type attribute for button'
+ });
+ }
+
+ function checkValue(node, value) {
+ if (!(value in configuration)) {
+ context.report({
+ node: node,
+ message: `"${value}" is an invalid value for button type attribute`
+ });
+ } else if (!configuration[value]) {
+ context.report({
+ node: node,
+ message: `"${value}" is a forbidden value for button type attribute`
+ });
+ }
+ }
+
+ return {
+ JSXElement: function(node) {
+ if (node.openingElement.name.name !== 'button') {
+ return;
+ }
+
+ const typeProp = getProp(node.openingElement.attributes, 'type');
+
+ if (!typeProp) {
+ reportMissing(node);
+ return;
+ }
+
+ checkValue(node, getLiteralPropValue(typeProp));
+ },
+ CallExpression: function(node) {
+ if (!isCreateElement(node)) {
+ return;
+ }
+
+ if (node.arguments[0].type !== 'Literal' || node.arguments[0].value !== 'button') {
+ return;
+ }
+
+ if (!node.arguments[1] || node.arguments[1].type !== 'ObjectExpression') {
+ reportMissing(node);
+ return;
+ }
+
+ const props = node.arguments[1].properties;
+ const typeProp = props.find(prop => prop.key && prop.key.name === 'type');
+
+ if (!typeProp || typeProp.value.type !== 'Literal') {
+ reportMissing(node);
+ return;
+ }
+
+ checkValue(node, typeProp.value.value);
+ }
+ };
+ }
+};
diff --git a/tests/lib/rules/button-has-type.js b/tests/lib/rules/button-has-type.js
new file mode 100644
index 0000000000..151ffa9a2e
--- /dev/null
+++ b/tests/lib/rules/button-has-type.js
@@ -0,0 +1,89 @@
+/**
+ * @fileoverview Forbid "button" element without an explicit "type" attribute
+ * @author Filipp Riabchun
+ */
+'use strict';
+
+// ------------------------------------------------------------------------------
+// Requirements
+// ------------------------------------------------------------------------------
+
+const rule = require('../../../lib/rules/button-has-type');
+const RuleTester = require('eslint').RuleTester;
+
+const parserOptions = {
+ ecmaVersion: 8,
+ sourceType: 'module',
+ ecmaFeatures: {
+ experimentalObjectRestSpread: true,
+ jsx: true
+ }
+};
+
+// ------------------------------------------------------------------------------
+// Tests
+// ------------------------------------------------------------------------------
+
+const ruleTester = new RuleTester({parserOptions});
+ruleTester.run('button-has-type', rule, {
+ valid: [
+ {code: ''},
+ {code: ''},
+ {code: ''},
+ {code: ''},
+ {code: ''},
+ {
+ code: '',
+ options: [{reset: false}]
+ },
+ {code: 'React.createElement("span")'},
+ {code: 'React.createElement("span", {type: "foo"})'},
+ {code: 'React.createElement("button", {type: "button"})'},
+ {code: 'React.createElement("button", {type: "submit"})'},
+ {code: 'React.createElement("button", {type: "reset"})'},
+ {
+ code: 'React.createElement("button", {type: "button"})',
+ options: [{reset: false}]
+ }
+ ],
+ invalid: [
+ {
+ code: '',
+ errors: [{
+ message: 'Missing an explicit type attribute for button'
+ }]
+ },
+ {
+ code: '',
+ errors: [{
+ message: '"foo" is an invalid value for button type attribute'
+ }]
+ },
+ {
+ code: '',
+ options: [{reset: false}],
+ errors: [{
+ message: '"reset" is a forbidden value for button type attribute'
+ }]
+ },
+ {
+ code: 'React.createElement("button")',
+ errors: [{
+ message: 'Missing an explicit type attribute for button'
+ }]
+ },
+ {
+ code: 'React.createElement("button", {type: "foo"})',
+ errors: [{
+ message: '"foo" is an invalid value for button type attribute'
+ }]
+ },
+ {
+ code: 'React.createElement("button", {type: "reset"})',
+ options: [{reset: false}],
+ errors: [{
+ message: '"reset" is a forbidden value for button type attribute'
+ }]
+ }
+ ]
+});