Getting Started
You need Node 20.19 or later. The examples use pnpm; npm and yarn work the same way.
Scaffold the project
Section titled “Scaffold the project”The recommended starting point is the official template repo:
pnpm dlx degit svebcomponents/template my-projectcd my-projectpnpm installpnpm devnpx degit svebcomponents/template my-projectcd my-projectnpm installnpm run devyarn dlx degit svebcomponents/template my-projectcd my-projectyarn installyarn devdev starts the SvelteKit app that consumes the component, on
localhost:5173.
The template is a small pnpm workspace:
components/*— a Svelte custom element packageapps/svelte-kit— a SvelteKit app that consumes the custom elementconfigs/*— 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.
Write the component
Section titled “Write the component”A svebcomponent is a Svelte component that declares its own tag. This is the
whole of 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.
Declare the exports
Section titled “Declare the exports”The exports field of the component’s package.json tells consumers where the
custom element lives — and doubles as the build configuration:
{ "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.
Build it
Section titled “Build it”@svebcomponents/build installs a command called svebcomponents, and the
package’s build script runs it:
{ "scripts": { "build": "svebcomponents" }}So from the component package:
pnpm buildnpm run buildyarn buildDirectorydist
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:
{ "customElements": "custom-elements.json", "files": ["dist", "custom-elements.json"]}The build prints a hint if you forget. See Publishing your package.
Use it
Section titled “Use it”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.