Vue and Web Components
Web Components is an umbrella term for a set of web native APIs that allows developers to create reusable custom elements.
We consider Vue and Web Components to be primarily complementary technologies. Vue has excellent support for both consuming and creating custom elements. Whether you are integrating custom elements into an existing Vue application, or using Vue to build and distribute custom elements, you are in good company.
Using Custom Elements in Vue
Vue scores a perfect 100% in the Custom Elements Everywhere tests . Consuming custom elements inside a Vue application largely works the same as using native HTML elements, with a few things to keep in mind:
Skipping Component Resolution
By default, Vue will attempt to resolve a non-native HTML tag as a registered Vue component before falling back to rendering it as a custom element. This will cause Vue to emit a "failed to resolve component" warning during development. To let Vue know that certain elements should be treated as custom elements and skip component resolution, we can specify the
compilerOptions.isCustomElement
option
.
If you are using Vue with a build setup, the option should be passed via build configs since it is a compile-time option.
Example In-Browser Config
Example Vite Config
Example Vue CLI Config
Passing DOM Properties
Since DOM attributes can only be strings, we need to pass complex data to custom elements as DOM properties. When setting props on a custom element, Vue 3 automatically checks DOM-property presence using the
in
operator and will prefer setting the value as a DOM property if the key is present. This means that, in most cases, you won't need to think about this if the custom element follows the
recommended best practices
.
However, there could be rare cases where the data must be passed as a DOM property, but the custom element does not properly define/reflect the property (causing the
in
check to fail). In this case, you can force a
v-bind
binding to be set as a DOM property using the
.prop
modifier:
Building Custom Elements with Vue
The primary benefit of custom elements is that they can be used with any framework, or even without a framework. This makes them ideal for distributing components where the end consumer may not be using the same frontend stack, or when you want to insulate the end application from the implementation details of the components it uses.
defineCustomElement
Vue supports creating custom elements using exactly the same Vue component APIs via the
defineCustomElement
method. The method accepts the same argument as
defineComponent
, but instead returns a custom element constructor that extends
HTMLElement
:
Lifecycle
-
A Vue custom element will mount an internal Vue component instance inside its shadow root when the element's
connectedCallback
is called for the first time. -
When the element's
disconnectedCallback
is invoked, Vue will check whether the element is detached from the document after a microtask tick.-
If the element is still in the document, it's a move and the component instance will be preserved;
-
If the element is detached from the document, it's a removal and the component instance will be unmounted.
-
Props
-
All props declared using the
props
option will be defined on the custom element as properties. Vue will automatically handle the reflection between attributes / properties where appropriate.-
Attributes are always reflected to corresponding properties.
-
Properties with primitive values (
string
,boolean
ornumber
) are reflected as attributes.
-
-
Vue also automatically casts props declared with
Boolean
orNumber
types into the desired type when they are set as attributes (which are always strings). For example, given the following props declaration:And the custom element usage:
In the component,
selected
will be cast totrue
(boolean) andindex
will be cast to1
(number).
Events
Events emitted via
this.$emit
or setup
emit
are dispatched as native
CustomEvents
on the custom element. Additional event arguments (payload) will be exposed as an array on the CustomEvent object as its
detail
property.
Slots
Inside the component, slots can be rendered using the
<slot/>
element as usual. However, when consuming the resulting element, it only accepts
native slots syntax
:
-
Scoped slots are not supported.
-
When passing named slots, use the
slot
attribute instead of thev-slot
directive:
Provide / Inject
The Provide / Inject API and its Composition API equivalent also work between Vue-defined custom elements. However, note that this works only between custom elements . i.e. a Vue-defined custom element won't be able to inject properties provided by a non-custom-element Vue component.
App Level Config
You can configure the app instance of a Vue custom element using the
configureApp
option:
SFC as Custom Element
defineCustomElement
also works with Vue Single-File Components (SFCs). However, with the default tooling setup, the
<style>
inside the SFCs will still be extracted and merged into a single CSS file during production build. When using an SFC as a custom element, it is often desirable to inject the
<style>
tags into the custom element's shadow root instead.
The official SFC toolings support importing SFCs in "custom element mode" (requires
@vitejs/plugin-vue@^1.4.0
or
vue-loader@^16.5.0
). An SFC loaded in custom element mode inlines its
<style>
tags as strings of CSS and exposes them under the component's
styles
option. This will be picked up by
defineCustomElement
and injected into the element's shadow root when instantiated.
To opt-in to this mode, simply end your component file name with
.ce.vue
:
If you wish to customize what files should be imported in custom element mode (for example, treating
all
SFCs as custom elements), you can pass the
customElement
option to the respective build plugins:
Tips for a Vue Custom Elements Library
When building custom elements with Vue, the elements will rely on Vue's runtime. There is a ~16kb baseline size cost depending on how many features are being used. This means it is not ideal to use Vue if you are shipping a single custom element - you may want to use vanilla JavaScript, petite-vue , or frameworks that specialize in small runtime size. However, the base size is more than justifiable if you are shipping a collection of custom elements with complex logic, as Vue will allow each component to be authored with much less code. The more elements you are shipping together, the better the trade-off.
If the custom elements will be used in an application that is also using Vue, you can choose to externalize Vue from the built bundle so that the elements will be using the same copy of Vue from the host application.
It is recommended to export the individual element constructors to give your users the flexibility to import them on-demand and register them with desired tag names. You can also export a convenience function to automatically register all elements. Here's an example entry point of a Vue custom element library:
A consumer can use the elements in a Vue file,
or in any other framework such as one with JSX, and with custom names:
Vue-based Web Components and TypeScript
When writing Vue SFC templates, you may want to type check your Vue components, including those that are defined as custom elements.
Custom elements are registered globally in browsers using their built-in APIs, and by default they won't have type inference when used in Vue templates. To provide type support for Vue components registered as custom elements, we can register global component typings by augmenting the
GlobalComponents
interface
for type checking in Vue templates (JSX users can augment the
JSX.IntrinsicElements
type instead, which is not shown here).
Here is how to define the type for a custom element made with Vue:
Non-Vue Web Components and TypeScript
Here is the recommended way to enable type checking in SFC templates of Custom Elements that are not built with Vue.
NOTE
This approach is one possible way to do it, but it may vary depending on the framework being used to create the custom elements.
Suppose we have a custom element with some JS properties and events defined, and it is shipped in a library called
some-lib
:
The implementation details have been omitted, but the important part is that we have type definitions for two things: prop types and event types.
Let's create a type helper for easily registering custom element type definitions in Vue:
NOTE
We marked
$props
and
$emit
as deprecated so that when we get a
ref
to a custom element we will not be tempted to use these properties, as these properties are for type checking purposes only when it comes to custom elements. These properties do not actually exist on the custom element instances.
Using the type helper we can now select the JS properties that should be exposed for type checking in Vue templates:
Suppose that
some-lib
builds its source TypeScript files into a
dist/
folder. A user of
some-lib
can then import
SomeElement
and use it in a Vue SFC like so:
If an element does not have type definitions, the types of the properties and events can be defined in a more manual fashion: