Vue custom elements support and compatibility
Vue scores a perfect 100% in the Custom Elements Everywhere tests. Vue has excellent support for both consuming and creating custom elements. Vue and Web Components are considered primarily complementary technologies.
Skipping component resolution for custom elements
By default, Vue attempts to resolve non-native HTML tags as registered Vue components before falling back to rendering them as custom elements. To skip this resolution and treat certain elements as custom elements, use the compilerOptions.isCustomElement option. This is a compile-time option that must be passed via build configs or app.config.compilerOptions.isCustomElement for in-browser compilation.
isCustomElement in-browser config example
For in-browser compilation: app.config.compilerOptions.isCustomElement = (tag) => tag.includes('-')
isCustomElement Vite config example
In vite.config.js, configure the Vue plugin to skip component resolution for custom elements:
```js
import vue from '@vitejs/plugin-vue'
export default {
plugins: [
vue({
template: {
compilerOptions: {
isCustomElement: (tag) => tag.includes('-')
}
}
})
]
}
```
isCustomElement Vue CLI config example
In vue.config.js, configure vue-loader to skip component resolution for custom elements:
```js
module.exports = {
chainWebpack: (config) => {
config.module
.rule('vue')
.use('vue-loader')
.tap((options) => ({
...options,
compilerOptions: {
isCustomElement: (tag) => tag.startsWith('ion-')
}
}))
}
}
```
Passing DOM properties to custom elements
Vue 3 automatically checks for DOM-property presence using the 'in' operator when setting props on custom elements and prefers setting values as DOM properties if the key is present. In rare cases where data must be passed as a DOM property but the custom element does not properly define/reflect the property, use the .prop modifier or . shorthand to force the binding as a DOM property.
.prop modifier for custom element properties
Force a v-bind binding to be set as a DOM property using the .prop modifier:
```vue-html
<my-element :user.prop="{ name: 'jack' }"></my-element>
<!-- shorthand equivalent -->
<my-element .user="{ name: 'jack' }"></my-element>
```
defineCustomElement creates custom element constructor
Use the defineCustomElement method to create custom elements using Vue component APIs. It accepts the same arguments as defineComponent but returns a custom element constructor that extends HTMLElement. defineCustomElement accepts standard Vue component options (props, emits, template) plus a styles option for CSS to be injected into the shadow root.
defineCustomElement example
```js
import { defineCustomElement } from 'vue'
const MyVueElement = defineCustomElement({
props: {},
emits: {},
template: `...`,
styles: [`/* inlined css */`]
})
// Register the custom element
customElements.define('my-vue-element', MyVueElement)
// Programmatically instantiate (after registration)
document.body.appendChild(
new MyVueElement({
// initial props (optional)
})
)
```
Component instance type extraction
To get the instance type of an imported component, use TypeScript's built-in InstanceType utility: `type FooType = InstanceType<typeof Foo>`. This can be used to type template refs for components.
ComponentPublicInstance for generic component refs
ComponentPublicInstance can be used to type component template refs when the exact component type isn't available or important. This type includes only properties shared by all components, such as `$el`: `const child = useTemplateRef<ComponentPublicInstance>('child')`.
Generic component template ref typing with ComponentExposed
When referencing a generic component with useTemplateRef, use ComponentExposed from the 'vue-component-type-helpers' library, as InstanceType does not work with generic components: `const modal = useTemplateRef<ComponentExposed<typeof MyGenericModal>>('modal')`. With @vue/language-tools 2.1+, static template refs' types can be automatically inferred.
defineComponent required for Options API TypeScript prop type inference
To enable type inference for props in Options API, you must wrap the component with defineComponent(). Without it, Vue cannot infer types for props based on the props option, including options like required: true and default.
Runtime props options only support constructor functions as types
The runtime props option only accepts constructor functions (like String, Number, Object, Function) as prop types. Complex types such as objects with nested properties or function call signatures cannot be specified directly at runtime.
PropType utility for typing complex component props
Use the PropType utility type from Vue to annotate complex prop types. Example: book: { type: Object as PropType<Book>, required: true } where Book is an interface. You can also use it with functions: callback: Function as PropType<(id: number) => void>.
TypeScript less than 4.7 requires arrow functions in prop validators and defaults
If using TypeScript version less than 4.7, you must use arrow functions for validator and default prop options to prevent type inference failure. This is because arrow functions prevent TypeScript from having to infer the type of 'this' inside these functions. This limitation was improved in TypeScript 4.7.
Example: PropType utility for complex object prop types
import { defineComponent } from 'vue'
import type { PropType } from 'vue'
interface Book {
title: string
author: string
year: number
}
export default defineComponent({
props: {
book: {
type: Object as PropType<Book>,
required: true
},
callback: Function as PropType<(id: number) => void>
},
mounted() {
this.book.title // string
this.book.year // number
// TS Error: argument of type 'string' is not assignable to parameter of type 'number'
this.callback?.('123')
}
})
Example: Arrow functions in prop validators for TypeScript < 4.7
import { defineComponent } from 'vue'
import type { PropType } from 'vue'
interface Book {
title: string
year?: number
}
export default defineComponent({
props: {
bookA: {
type: Object as PropType<Book>,
// Make sure to use arrow functions if your TypeScript version is less than 4.7
default: () => ({
title: 'Arrow Function Expression'
}),
validator: (book: Book) => !!book.title
}
}
})