or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

component-system.mdcustom-elements.mddevelopment-tools.mdindex.mdjsx-support.mdreactivity.mdrendering.mdsfc-compilation.mdssr.mdutilities.md
tile.json

custom-elements.mddocs/

Custom Elements

Vue's Custom Elements API allows you to define Vue components as standard web components that can be used in any HTML document or framework. This provides interoperability and framework-agnostic component distribution.

Capabilities

Custom Element Definition

Define Custom Element

Create Vue-based custom elements that can be used as standard HTML elements.

/**
 * Define a Vue component as a custom element
 * @param tag - Custom element tag name
 * @param component - Vue component definition
 * @param options - Custom element options
 * @returns Custom element constructor
 */
function defineCustomElement<T extends Component = Component>(
  component: T,
  options?: CustomElementOptions
): CustomElementConstructor;

/**
 * Define a Vue component as a custom element from SFC
 * @param component - Single file component
 * @param options - Custom element options
 * @returns Custom element constructor
 */
function defineCustomElement<T extends Component = Component>(
  component: T extends ComponentOptions 
    ? T 
    : T extends (...args: any[]) => Component 
      ? T 
      : ComponentOptionsWithProps<T>,
  options?: CustomElementOptions
): CustomElementConstructor;

interface CustomElementOptions {
  styles?: string[];
  shadowRoot?: boolean;
  nonce?: string;
  configureApp?: (app: App) => void;
}

Usage Examples:

import { defineCustomElement, ref } from "vue";

// Define a simple custom element
const MyButton = defineCustomElement({
  props: {
    label: String,
    disabled: Boolean
  },
  emits: ['click'],
  setup(props, { emit }) {
    const handleClick = () => {
      if (!props.disabled) {
        emit('click');
      }
    };

    return {
      handleClick
    };
  },
  template: `
    <button 
      :disabled="disabled" 
      @click="handleClick"
      :style="{ backgroundColor: disabled ? '#ccc' : '#007bff' }"
    >
      {{ label }}
    </button>
  `,
  styles: [`
    button {
      padding: 8px 16px;
      border: none;
      border-radius: 4px;
      color: white;
      cursor: pointer;
    }
    button:disabled {
      cursor: not-allowed;
    }
  `]
});

// Register the custom element
customElements.define('my-button', MyButton);

// Now can be used in HTML
// <my-button label="Click me" @click="handleClick"></my-button>

SSR Custom Elements

Define custom elements that work with server-side rendering.

/**
 * Define SSR-compatible custom element
 * @param component - Vue component definition
 * @param options - Custom element options
 * @returns SSR custom element constructor
 */
function defineSSRCustomElement<T extends Component = Component>(
  component: T,
  options?: CustomElementOptions
): CustomElementConstructor;

Usage Example:

import { defineSSRCustomElement } from "vue";

const SSRCounter = defineSSRCustomElement({
  setup() {
    const count = ref(0);
    
    return {
      count,
      increment: () => count.value++
    };
  },
  template: `
    <div>
      <p>Count: {{ count }}</p>
      <button @click="increment">+</button>
    </div>
  `,
  styles: [`
    div { 
      padding: 16px; 
      border: 1px solid #ccc; 
    }
  `]
});

customElements.define('ssr-counter', SSRCounter);

Custom Element Base Class

VueElement

Base class for Vue custom elements providing lifecycle and reactivity integration.

/**
 * Base class for Vue custom elements
 */
class VueElement extends HTMLElement {
  /**
   * Vue app instance for this element
   */
  readonly _instance: ComponentInternalInstance | null;
  
  /**
   * Root element styles
   */
  readonly _styles?: HTMLStyleElement[];
  
  /**
   * Create Vue element instance
   * @param initialProps - Initial properties
   */
  constructor(initialProps?: Record<string, any>);
  
  /**
   * Connect element to DOM
   */
  connectedCallback(): void;
  
  /**
   * Disconnect element from DOM
   */
  disconnectedCallback(): void;
  
  /**
   * Handle attribute changes
   */
  attributeChangedCallback(
    name: string, 
    oldValue: string | null, 
    newValue: string | null
  ): void;
  
  /**
   * Element was adopted to new document
   */
  adoptedCallback(): void;
}

Custom Element Composition API

Shadow Root Access

Access the shadow root within custom element components.

/**
 * Get access to the shadow root in custom element context
 * @returns Shadow root or null
 */
function useShadowRoot(): ShadowRoot | null;

Usage Example:

import { defineCustomElement, useShadowRoot, onMounted } from "vue";

const CustomInput = defineCustomElement({
  setup() {
    const shadowRoot = useShadowRoot();
    
    onMounted(() => {
      if (shadowRoot) {
        // Access shadow DOM
        const input = shadowRoot.querySelector('input');
        input?.focus();
      }
    });
    
    return {};
  },
  template: `<input type="text" placeholder="Enter text...">`
});

Host Element Access

Access the host custom element from within the component.

/**
 * Get access to the host custom element
 * @returns Host element or null
 */
function useHost(): Element | null;

Usage Example:

import { defineCustomElement, useHost, onMounted } from "vue";

const HostAwareComponent = defineCustomElement({
  setup() {
    const host = useHost();
    
    onMounted(() => {
      if (host) {
        // Access host element attributes
        const theme = host.getAttribute('theme');
        console.log('Host theme:', theme);
      }
    });
    
    return {};
  },
  template: `<div>Component content</div>`
});

Advanced Custom Element Patterns

Prop Mapping

Custom elements automatically map HTML attributes to component props with type conversion.

// Component props
const props = {
  count: Number,        // Mapped from "count" attribute
  disabled: Boolean,    // Mapped from "disabled" attribute  
  items: Array,        // Mapped from "items" attribute (JSON)
  config: Object       // Mapped from "config" attribute (JSON)
};

// HTML usage
// <my-component 
//   count="42" 
//   disabled
//   items='["a", "b", "c"]'
//   config='{"theme": "dark"}'
// ></my-component>

Event Handling

Custom elements emit standard DOM events that can be listened to with addEventListener.

const CustomButton = defineCustomElement({
  emits: ['customClick', 'valueChange'],
  setup(props, { emit }) {
    const handleClick = () => {
      // Emits as 'customclick' DOM event
      emit('customClick', { timestamp: Date.now() });
    };
    
    return { handleClick };
  }
});

// HTML usage
// <my-button></my-button>
// <script>
//   document.querySelector('my-button')
//     .addEventListener('customclick', (e) => {
//       console.log('Custom click:', e.detail);
//     });
// </script>

Styling Integration

Custom elements support both scoped styles and global CSS custom properties.

const StyledComponent = defineCustomElement({
  setup() {
    return {
      primaryColor: 'var(--primary-color, #007bff)'
    };
  },
  template: `
    <div class="container">
      <button :style="{ color: primaryColor }">
        Styled Button
      </button>
    </div>
  `,
  styles: [`
    .container {
      padding: 16px;
      border: 1px solid var(--border-color, #ddd);
    }
    
    button {
      background: var(--button-bg, #f8f9fa);
      border: none;
      padding: 8px 16px;
      cursor: pointer;
    }
  `]
});

Type Definitions

interface CustomElementConstructor {
  new (...args: any[]): HTMLElement;
}

interface CustomElementOptions {
  /**
   * Styles to inject into shadow root
   */
  styles?: string[];
  
  /**
   * Whether to use shadow root (default: true)
   */
  shadowRoot?: boolean;
  
  /**
   * Nonce for CSP compliance
   */
  nonce?: string;
  
  /**
   * Configure the Vue app instance
   */
  configureApp?: (app: App) => void;
}

/**
 * Vue Element instance interface
 */
interface VueElementInstance extends HTMLElement {
  _instance: ComponentInternalInstance | null;
  _styles?: HTMLStyleElement[];
}