Skip to content

Getting Started

You need Node 20.19 or later. The examples use pnpm; npm and yarn work the same way.

The recommended starting point is the official template repo:

Terminal window
pnpm dlx degit svebcomponents/template my-project
cd my-project
pnpm install
pnpm dev

dev starts the SvelteKit app that consumes the component, on localhost:5173.

The template is a small pnpm workspace:

  • components/* — a Svelte custom element package
  • apps/svelte-kit — a SvelteKit app that consumes the custom element
  • configs/* — shared linting, formatting, and TypeScript config

The rest of this page walks through what that component package contains, so you can also build one from scratch in an existing repo.

A svebcomponent is a Svelte component that declares its own tag. This is the whole of src/FavoriteNumber.svelte:

src/FavoriteNumber.svelte
<svelte:options customElement="favorite-number" />
<script lang="ts">
let {
increments = 1,
}: {
increments: number;
} = $props();
let count = $state(0);
</script>
<button onclick={() => (count += increments)}>
add {increments} to {count}
</button>

If you know Svelte 5 there is nothing unfamiliar here: typed props, and <svelte:options> to declare the custom element tag.

The one line that matters is the type annotation:

increments: number;

That is enough for auto-options to generate the prop metadata Svelte needs to convert the increments attribute from a string to a number. Without it you would write the customElement={{ props: … }} object yourself.

The exports field of the component’s package.json tells consumers where the custom element lives — and doubles as the build configuration:

package.json
{
"exports": {
".": {
"types": "./dist/client/FavoriteNumber.d.ts",
"svelte": "./dist/client-svelte/FavoriteNumber.js",
"default": "./dist/client/FavoriteNumber.js"
}
}
}

The svebcomponents command maps dist/client/FavoriteNumber.js back to the same-basename src/FavoriteNumber.svelte and builds that entrypoint. The svelte condition asks for a second, lighter build that Svelte tooling can select to share the host application’s Svelte runtime.

The component file is the entrypoint. Do not add an index.ts that re-exports it: the build would treat that as an ordinary module and skip the custom-element pipeline entirely.

@svebcomponents/build installs a command called svebcomponents, and the package’s build script runs it:

package.json
{
"scripts": {
"build": "svebcomponents"
}
}

So from the component package:

Terminal window
pnpm build
  • Directorydist
    • Directoryclient
      • FavoriteNumber.js standalone build, bundles Svelte
      • FavoriteNumber.d.ts element, attribute and event types
    • Directoryclient-svelte
      • FavoriteNumber.js shares the host’s Svelte runtime
  • custom-elements.json manifest for editors and tooling

The manifest only reaches consumers if package.json points at it, so declare it alongside the exports:

package.json
{
"customElements": "custom-elements.json",
"files": ["dist", "custom-elements.json"]
}

The build prints a hint if you forget. See Publishing your package.

The generated custom element behaves like any other web component:

<script lang="ts">
import "my-components";
</script>
<favorite-number increments={5}></favorite-number>

The build also emitted TypeScript declarations for the element. If the package declares svelte as a dependency of its consumers, those are registered with Svelte’s template types automatically, making unknown attributes and increments={"nope"} type errors — see Publishing your package.

Read Authoring components to add events, slots and styling to your component. When you are ready to ship it, see Publishing your package.