Open-Source Wikis

/

Vue.js

/

Features

/

Custom elements

vuejs/core

Custom elements

defineCustomElement wraps a Vue component as a Web Component (HTMLElement subclass) that can be registered with customElements.define. The implementation lives in a single file, packages/runtime-dom/src/apiCustomElement.ts (~22KB), and ties together a standard Vue component, the Custom Elements API, and Shadow DOM.

Public API

import { defineCustomElement } from 'vue';

const MyButton = defineCustomElement({
  props: { label: String },
  template: `<button>{{ label }}</button>`,
});

customElements.define('my-button', MyButton);

The returned MyButton is a class extending VueElement, which extends HTMLElement. Inside a custom element you can use Composition API as normal; useHost() returns the host element and useShadowRoot() returns the shadow root.

VueElement class

VueElement (defined in apiCustomElement.ts) implements the four Custom Elements lifecycle callbacks plus the attribute-observation contract:

  • connectedCallback — mounts the wrapped Vue app into either a shadow root (default) or the element itself (shadowRoot: false), then runs the connected Vue lifecycle hook.
  • disconnectedCallback — unmounts the app on next microtask. The microtask delay handles the case of moving the element across the DOM (it is briefly disconnected and reconnected during a move).
  • adoptedCallback — invoked when the element is moved between documents.
  • attributeChangedCallback — propagates attribute changes into the inner Vue component's props.

Static observedAttributes is computed from the component's props definition: each prop that exists on the host element becomes a watched attribute. Hyphen-cased forms are accepted (my-propmyProp).

Shadow DOM

By default, the wrapped component mounts into a shadow root with mode: 'open'. defineCustomElement(component, { shadowRoot: false }) disables shadow DOM, in which case styles need extra care since they will leak into the page.

When shadow DOM is enabled:

  • <style> blocks from the SFC are injected as <style> tags inside the shadow root.
  • The renderer's nodeOps.insert is called against the shadow root.
  • The host element acts as the boundary; events still propagate through retargeting per Custom Elements spec.

Props vs attributes

The wrapper does double duty:

  • Attribute → prop: attribute changes are converted using the prop's declared type (Number/Boolean/Object/Array). Objects/arrays are JSON-parsed.
  • Property → prop: setting el.someProp = 42 directly bypasses the attribute and writes to the inner component.

The list of declared props determines which keys participate. Anything not declared as a prop is ignored at the custom-element level; you can still listen for attribute changes manually via attributeChangedCallback.

SSR

defineSSRCustomElement is a sister API for components that need server-rendering before being upgraded by the browser. It produces a custom element class whose initial markup is rendered server-side, and connectedCallback hydrates the existing children.

The shadow-root case is more involved because the SSR markup uses Declarative Shadow DOM (<template shadowrootmode="open">).

Composables

  • useHost() — returns the HTMLElement (the custom element) for the current instance, or null if not inside a custom element.
  • useShadowRoot() — returns the shadow root, or null if shadow DOM is disabled or not yet attached.

These are simple inject-style lookups that walk up the component tree looking for a marker provided by VueElement on mount.

Limitations

The implementation calls these out:

  • The host element only knows about declared props. Slots are exposed as light-DOM children (in non-shadow mode) or via <slot> projection (in shadow mode).
  • provide/inject does not cross the custom-element boundary by default; you have to pipe context through props.
  • The wrapped component cannot use <Teleport to="body"> to render into the page from inside a closed shadow root without help.

Files to know

  • packages/runtime-dom/src/apiCustomElement.ts
  • packages/runtime-dom/src/index.ts (re-exports defineCustomElement, useHost, useShadowRoot, VueElement)
  • packages/runtime-core/src/component.ts (ComponentCustomElementInterface)

Built by Factory AutoWiki from public repository content. It is a generated preview for codebase exploration, not source-maintained documentation.

Custom elements – Vue.js wiki | Factory