diff --git a/docs/rules/no-reserved-component-names.md b/docs/rules/no-reserved-component-names.md
new file mode 100644
index 000000000..57a008592
--- /dev/null
+++ b/docs/rules/no-reserved-component-names.md
@@ -0,0 +1,28 @@
+# vue/no-reserved-component-names
+> disallow the use of reserved names in component definitions
+
+- :gear: This rule is included in all of `"plugin:vue/essential"`, `"plugin:vue/recommended"`, and `"plugin:vue/strongly-recommended"`.
+
+## :book: Rule Details
+
+This rule prevents name collisions between vue components and standard html elements.
+
+
+
+```vue
+
+```
+
+
+
+## :books: Further reading
+
+- [List of html elements](https://developer.mozilla.org/en-US/docs/Web/HTML/Element)
+- [List of SVG elements](https://developer.mozilla.org/en-US/docs/Web/SVG/Element)
+- [Kebab case elements](https://stackoverflow.com/questions/22545621/do-custom-elements-require-a-dash-in-their-name/22545622#22545622)
+- [Valid custom element name](https://w3c.github.io/webcomponents/spec/custom/#valid-custom-element-name)
\ No newline at end of file
diff --git a/lib/rules/no-reserved-component-names.js b/lib/rules/no-reserved-component-names.js
new file mode 100644
index 000000000..c08b34f5d
--- /dev/null
+++ b/lib/rules/no-reserved-component-names.js
@@ -0,0 +1,125 @@
+/**
+ * @fileoverview disallow the use of reserved names in component definitions
+ * @author Jake Hassel
+ */
+'use strict'
+
+const utils = require('../utils')
+const casing = require('../utils/casing')
+
+const htmlElements = require('../utils/html-elements.json')
+const deprecatedHtmlElements = require('../utils/deprecated-html-elements.json')
+const svgElements = require('../utils/svg-elements.json')
+
+const kebabCaseElements = [
+ 'annotation-xml',
+ 'color-profile',
+ 'font-face',
+ 'font-face-src',
+ 'font-face-uri',
+ 'font-face-format',
+ 'font-face-name',
+ 'missing-glyph'
+]
+
+const isLowercase = (word) => /^[a-z]*$/.test(word)
+const capitalizeFirstLetter = (word) => word[0].toUpperCase() + word.substring(1, word.length)
+
+const RESERVED_NAMES = new Set(
+ [
+ ...kebabCaseElements,
+ ...kebabCaseElements.map(casing.pascalCase),
+ ...htmlElements,
+ ...htmlElements.map(capitalizeFirstLetter),
+ ...deprecatedHtmlElements,
+ ...deprecatedHtmlElements.map(capitalizeFirstLetter),
+ ...svgElements,
+ ...svgElements.filter(isLowercase).map(capitalizeFirstLetter)
+ ])
+
+// ------------------------------------------------------------------------------
+// Rule Definition
+// ------------------------------------------------------------------------------
+
+module.exports = {
+ meta: {
+ type: 'suggestion',
+ docs: {
+ description: 'disallow the use of reserved names in component definitions',
+ category: undefined, // 'essential'
+ url: 'https://eslint.vuejs.org/rules/no-reserved-component-names.html'
+ },
+ fixable: null,
+ schema: []
+ },
+
+ create (context) {
+ function canVerify (node) {
+ return node.type === 'Literal' || (
+ node.type === 'TemplateLiteral' &&
+ node.expressions.length === 0 &&
+ node.quasis.length === 1
+ )
+ }
+
+ function reportIfInvalid (node) {
+ let name
+ if (node.type === 'TemplateLiteral') {
+ const quasis = node.quasis[0]
+ name = quasis.value.cooked
+ } else {
+ name = node.value
+ }
+ if (RESERVED_NAMES.has(name)) {
+ report(node, name)
+ }
+ }
+
+ function report (node, name) {
+ context.report({
+ node: node,
+ message: 'Name "{{name}}" is reserved.',
+ data: {
+ name: name
+ }
+ })
+ }
+
+ return Object.assign({},
+ {
+ "CallExpression > MemberExpression > Identifier[name='component']" (node) {
+ const parent = node.parent.parent
+ const calleeObject = utils.unwrapTypes(parent.callee.object)
+
+ if (calleeObject.type === 'Identifier' &&
+ calleeObject.name === 'Vue' &&
+ parent.arguments &&
+ parent.arguments.length === 2
+ ) {
+ const argument = parent.arguments[0]
+
+ if (canVerify(argument)) {
+ reportIfInvalid(argument)
+ }
+ }
+ }
+ },
+ utils.executeOnVue(context, (obj) => {
+ // Report if a component has been registered locally with a reserved name.
+ utils.getRegisteredComponents(obj)
+ .filter(({ name }) => RESERVED_NAMES.has(name))
+ .forEach(({ node, name }) => report(node, name))
+
+ const node = obj.properties
+ .find(item => (
+ item.type === 'Property' &&
+ item.key.name === 'name' &&
+ canVerify(item.value)
+ ))
+
+ if (!node) return
+ reportIfInvalid(node.value)
+ })
+ )
+ }
+}
diff --git a/lib/utils/deprecated-html-elements.json b/lib/utils/deprecated-html-elements.json
new file mode 100644
index 000000000..daf23f512
--- /dev/null
+++ b/lib/utils/deprecated-html-elements.json
@@ -0,0 +1 @@
+["acronym","applet","basefont","bgsound","big","blink","center","command","content","dir","element","font","frame","frameset","image","isindex","keygen","listing","marquee","menuitem","multicol","nextid","nobr","noembed","noframes","plaintext","shadow","spacer","strike","tt","xmp"]
\ No newline at end of file
diff --git a/tests/lib/rules/no-reserved-component-names.js b/tests/lib/rules/no-reserved-component-names.js
new file mode 100644
index 000000000..6d592396d
--- /dev/null
+++ b/tests/lib/rules/no-reserved-component-names.js
@@ -0,0 +1,375 @@
+/**
+ * @fileoverview disallow the use of reserved names in component definitions
+ * @author Jake Hassel
+ */
+'use strict'
+
+// ------------------------------------------------------------------------------
+// Requirements
+// ------------------------------------------------------------------------------
+
+const rule = require('../../../lib/rules/no-reserved-component-names')
+const RuleTester = require('eslint').RuleTester
+
+// ------------------------------------------------------------------------------
+// Tests
+// ------------------------------------------------------------------------------
+
+const invalidElements = [
+ 'annotation-xml', 'AnnotationXml',
+ 'color-profile', 'ColorProfile',
+ 'font-face', 'FontFace',
+ 'font-face-src', 'FontFaceSrc',
+ 'font-face-uri', 'FontFaceUri',
+ 'font-face-format', 'FontFaceFormat',
+ 'font-face-name', 'FontFaceName',
+ 'missing-glyph', 'MissingGlyph',
+ 'html', 'Html',
+ 'body', 'Body',
+ 'base', 'Base',
+ 'head', 'Head',
+ 'link', 'Link',
+ 'meta', 'Meta',
+ 'style', 'Style',
+ 'title', 'Title',
+ 'address', 'Address',
+ 'article', 'Article',
+ 'aside', 'Aside',
+ 'footer', 'Footer',
+ 'header', 'Header',
+ 'h1', 'H1',
+ 'h2', 'H2',
+ 'h3', 'H3',
+ 'h4', 'H4',
+ 'h5', 'H5',
+ 'h6', 'H6',
+ 'hgroup', 'Hgroup',
+ 'nav', 'Nav',
+ 'section', 'Section',
+ 'div', 'Div',
+ 'dd', 'Dd',
+ 'dl', 'Dl',
+ 'dt', 'Dt',
+ 'figcaption', 'Figcaption',
+ 'figure', 'Figure',
+ 'hr', 'Hr',
+ 'img', 'Img',
+ 'li', 'Li',
+ 'main', 'Main',
+ 'ol', 'Ol',
+ 'p', 'P',
+ 'pre', 'Pre',
+ 'ul', 'Ul',
+ 'a', 'A',
+ 'b', 'B',
+ 'abbr', 'Abbr',
+ 'bdi', 'Bdi',
+ 'bdo', 'Bdo',
+ 'br', 'Br',
+ 'cite', 'Cite',
+ 'code', 'Code',
+ 'data', 'Data',
+ 'dfn', 'Dfn',
+ 'em', 'Em',
+ 'i', 'I',
+ 'kbd', 'Kbd',
+ 'mark', 'Mark',
+ 'q', 'Q',
+ 'rp', 'Rp',
+ 'rt', 'Rt',
+ 'rtc', 'Rtc',
+ 'ruby', 'Ruby',
+ 's', 'S',
+ 'samp', 'Samp',
+ 'small', 'Small',
+ 'span', 'Span',
+ 'strong', 'Strong',
+ 'sub', 'Sub',
+ 'sup', 'Sup',
+ 'time', 'Time',
+ 'u', 'U',
+ 'var', 'Var',
+ 'wbr', 'Wbr',
+ 'area', 'Area',
+ 'audio', 'Audio',
+ 'map', 'Map',
+ 'track', 'Track',
+ 'video', 'Video',
+ 'embed', 'Embed',
+ 'object', 'Object',
+ 'param', 'Param',
+ 'source', 'Source',
+ 'canvas', 'Canvas',
+ 'script', 'Script',
+ 'noscript', 'Noscript',
+ 'del', 'Del',
+ 'ins', 'Ins',
+ 'caption', 'Caption',
+ 'col', 'Col',
+ 'colgroup', 'Colgroup',
+ 'table', 'Table',
+ 'thead', 'Thead',
+ 'tbody', 'Tbody',
+ 'tfoot', 'Tfoot',
+ 'td', 'Td',
+ 'th', 'Th',
+ 'tr', 'Tr',
+ 'button', 'Button',
+ 'datalist', 'Datalist',
+ 'fieldset', 'Fieldset',
+ 'form', 'Form',
+ 'input', 'Input',
+ 'label', 'Label',
+ 'legend', 'Legend',
+ 'meter', 'Meter',
+ 'optgroup', 'Optgroup',
+ 'option', 'Option',
+ 'output', 'Output',
+ 'progress', 'Progress',
+ 'select', 'Select',
+ 'textarea', 'Textarea',
+ 'details', 'Details',
+ 'dialog', 'Dialog',
+ 'menu', 'Menu',
+ 'menuitem', 'menuitem',
+ 'summary', 'Summary',
+ 'content', 'Content',
+ 'element', 'Element',
+ 'shadow', 'Shadow',
+ 'template', 'Template',
+ 'slot', 'Slot',
+ 'blockquote', 'Blockquote',
+ 'iframe', 'Iframe',
+ 'noframes', 'Noframes',
+ 'picture', 'Picture',
+
+ // SVG elements
+ 'animate', 'Animate',
+ 'animateMotion',
+ 'animateTransform',
+ 'circle', 'Circle',
+ 'clipPath',
+ 'defs', 'Defs',
+ 'desc', 'Desc',
+ 'discard', 'Discard',
+ 'ellipse', 'Ellipse',
+ 'feBlend',
+ 'feColorMatrix',
+ 'feComponentTransfer',
+ 'feComposite',
+ 'feConvolveMatrix',
+ 'feDiffuseLighting',
+ 'feDisplacementMap',
+ 'feDistantLight',
+ 'feDropShadow',
+ 'feFlood',
+ 'feFuncA',
+ 'feFuncB',
+ 'feFuncG',
+ 'feFuncR',
+ 'feGaussianBlur',
+ 'feImage',
+ 'feMerge',
+ 'feMergeNode',
+ 'feMorphology',
+ 'feOffset',
+ 'fePointLight',
+ 'feSpecularLighting',
+ 'feSpotLight',
+ 'feTile',
+ 'feTurbulence',
+ 'filter', 'Filter',
+ 'foreignObject',
+ 'g', 'G',
+ 'image', 'Image',
+ 'line', 'Line',
+ 'linearGradient',
+ 'marker', 'Marker',
+ 'mask', 'Mask',
+ 'metadata', 'Metadata',
+ 'mpath', 'Mpath',
+ 'path', 'Path',
+ 'pattern', 'Pattern',
+ 'polygon', 'Polygon',
+ 'polyline', 'Polyline',
+ 'radialGradient',
+ 'rect', 'Rect',
+ 'set', 'Set',
+ 'stop', 'Stop',
+ 'svg', 'Svg',
+ 'switch', 'Switch',
+ 'symbol', 'Symbol',
+ 'text', 'Text',
+ 'textPath',
+ 'tspan', 'Tspan',
+ 'unknown', 'Unknown',
+ 'use', 'Use',
+ 'view', 'View',
+
+ // Deprecated
+ 'acronym', 'Acronym',
+ 'applet', 'Applet',
+ 'basefont', 'Basefont',
+ 'bgsound', 'Bgsound',
+ 'big', 'Big',
+ 'blink', 'Blink',
+ 'center', 'Center',
+ 'command', 'Command',
+ 'dir', 'Dir',
+ 'font', 'Font',
+ 'frame', 'Frame',
+ 'frameset', 'Frameset',
+ 'isindex', 'Isindex',
+ 'keygen', 'Keygen',
+ 'listing', 'Listing',
+ 'marquee', 'Marquee',
+ 'multicol', 'Multicol',
+ 'nextid', 'Nextid',
+ 'nobr', 'Nobr',
+ 'noembed', 'Noembed',
+ 'plaintext', 'Plaintext',
+ 'spacer', 'Spacer',
+ 'strike', 'Strike',
+ 'tt', 'Tt',
+ 'xmp', 'Xmp'
+]
+
+const parserOptions = {
+ ecmaVersion: 2018,
+ sourceType: 'module'
+}
+
+const ruleTester = new RuleTester()
+ruleTester.run('no-reserved-component-names', rule, {
+
+ valid: [
+ {
+ filename: 'test.vue',
+ code: `
+ export default {
+ }
+ `,
+ parserOptions
+ },
+ {
+ filename: 'test.vue',
+ code: `
+ export default {
+ ...name
+ }
+ `,
+ parserOptions
+ },
+ {
+ filename: 'test.vue',
+ code: `
+ export default {
+ name: 'FooBar'
+ }
+ `,
+ parserOptions
+ },
+ {
+ filename: 'test.vue',
+ code: `Vue.component('FooBar', {})`,
+ parserOptions
+ },
+ {
+ filename: 'test.js',
+ code: `
+ new Vue({
+ name: 'foo!bar'
+ })
+ `,
+ parserOptions
+ },
+ {
+ filename: 'test.vue',
+ code: `Vue.component(\`fooBar\${foo}\`, component)`,
+ parserOptions
+ },
+ {
+ filename: 'test.vue',
+ code: `
+
+ `,
+ parser: 'vue-eslint-parser',
+ parserOptions
+ },
+ {
+ filename: 'test.vue',
+ code: `
+
+ `,
+ parser: 'vue-eslint-parser',
+ parserOptions
+ }
+ ],
+
+ invalid: [
+ ...invalidElements.map(name => {
+ return {
+ filename: `${name}.vue`,
+ code: `
+ export default {
+ name: '${name}'
+ }
+ `,
+ parserOptions,
+ errors: [{
+ message: `Name "${name}" is reserved.`,
+ type: 'Literal',
+ line: 3
+ }]
+ }
+ }),
+ ...invalidElements.map(name => {
+ return {
+ filename: 'test.vue',
+ code: `Vue.component('${name}', component)`,
+ parserOptions,
+ errors: [{
+ message: `Name "${name}" is reserved.`,
+ type: 'Literal',
+ line: 1
+ }]
+ }
+ }),
+ ...invalidElements.map(name => {
+ return {
+ filename: 'test.vue',
+ code: `Vue.component(\`${name}\`, {})`,
+ parserOptions,
+ errors: [{
+ message: `Name "${name}" is reserved.`,
+ type: 'TemplateLiteral',
+ line: 1
+ }]
+ }
+ }),
+ ...invalidElements.map(name => {
+ return {
+ filename: 'test.vue',
+ code: `export default {
+ components: {
+ '${name}': {},
+ }
+ }`,
+ parserOptions,
+ errors: [{
+ message: `Name "${name}" is reserved.`,
+ type: 'Property',
+ line: 3
+ }]
+ }
+ })
+ ]
+})