Skip to main content
Svelte components can be compiled to custom elements (Web Components), allowing you to use them in any JavaScript framework or vanilla HTML.

Basic Setup

To create a custom element, use the customElement option in <svelte:options>:
MyElement.svelte
Enable custom element compilation in your build configuration:
svelte.config.js

Using Custom Elements

Once defined, use your component as a regular HTML element:
Props are exposed as both DOM properties and HTML attributes (where possible).

Defining Custom Elements

Automatic Registration

With customElement set to a tag name, the element auto-registers:

Manual Registration

Omit the tag name to register manually:
MyElement.svelte
Manual registration is useful for library components where consumers choose the tag name.

Advanced Configuration

Configure custom element behavior with an object:

Configuration Options

tag

Optional tag name for auto-registration:

shadow

Configure the shadow DOM:
  • "open" (default): Shadow root with mode: "open"
  • "none": No shadow root (styles won’t be encapsulated)
  • ShadowRootInit object: Custom shadow root configuration
Using shadow: "none" disables style encapsulation and prevents using <slot> elements.

props

Configure how props map to attributes:
Type Options:
  • 'String' (default): No conversion
  • 'Number': Parse as number
  • 'Boolean': Convert to boolean
  • 'Array': Parse as JSON array
  • 'Object': Parse as JSON object
You must explicitly list all props for them to be exposed as DOM properties. Using let props = $props() without listing them individually won’t work.

extend

Extend the custom element class for advanced features:
Use extend for ElementInternals integration with forms.

Accessing the Host Element

Use the $host rune to access the custom element:

Component Lifecycle

Creation Timing

Custom elements use a wrapper approach:
1
  • Custom element is created (constructor runs)
  • Element is inserted into DOM (connectedCallback)
  • Next tick: Svelte component is created
  • Props assigned before insertion are applied to the component
  • Properties assigned before DOM insertion are saved and applied after component creation. However, function calls are only available after mounting.

    Updates and Batching

    Updates are batched and applied on the next tick:

    Destruction

    The Svelte component is destroyed on the next tick after disconnectedCallback:

    Slots and Content Projection

    Custom elements use native <slot> elements:
    Card.svelte
    Slotted content renders eagerly in the DOM (unlike Svelte’s lazy rendering). Content is always created even if inside {#if} blocks.

    Caveats and Limitations

    Style Encapsulation

    Styles are encapsulated in the shadow DOM by default:
    • Global styles (from global.css) don’t apply inside custom elements
    • :global() styles don’t escape the shadow DOM
    • Styles are inlined as JavaScript, not extracted to separate CSS files

    Server-Side Rendering

    Custom elements are not suitable for SSR. The shadow DOM is invisible until JavaScript loads.

    Slot Differences

    • Svelte’s {#each} blocks don’t render slotted content multiple times
    • The deprecated let: directive doesn’t work with custom elements
    • No parent-to-slot data passing mechanism

    Context API

    Context works within a custom element but not across custom element boundaries.

    Naming Constraints

    Avoid properties/attributes starting with on:

    Browser Support

    Custom elements work in all modern browsers. For older browsers:

    Testing Custom Elements

    Test as standard DOM elements:

    When to Use Custom Elements

    Good use cases:
    • Component libraries for non-Svelte apps
    • Embedding Svelte in existing applications
    • Framework-agnostic design systems
    • Progressive enhancement of static HTML
    Not recommended for:
    • Server-side rendered applications
    • Sharing components between Svelte apps (use regular components)
    • Heavy data passing between components (context won’t work)
    For Svelte-to-Svelte communication, use regular components. Custom elements are best for framework interoperability.