This skill covers Svelte 5's advanced TypeScript support, custom elements (Web Components), browser compatibility matrix, Svelte 4→5 migration guide, official best practices, and common Q&A.
When to use this skill
Use this skill when the user needs TypeScript type annotations (generics, wrapper components, DOM type augmentation), to create custom Web components and use $host() / $bindable / extend, to view the browser compatibility matrix and feature exceptions, to migrate from Svelte 4 to Svelte 5, or to follow the Svelte 5 recommended $state / $derived / $effect patterns.
Critical: TypeScript advanced
Svelte natively supports TypeScript (no configuration needed for Vite projects). Use <script lang="ts"> to enable type annotations; runtime features such as enums / parameter properties require a preprocessor.
<script lang="ts">
let name: string = 'world';
function greet(name: string) { alert(`Hello, ${name}!`); }
</script>
<button onclick={(e: Event) => greet((e.target as HTMLElement).innerText)}>
{name}
</button>
lang="ts"by default only supports "types disappear" features (annotations, interfaces, generics). Runtime code features (enum,private foo = 1parameter properties in the form) require configuringvitePreprocess({ script: true })。
Typed Props + passthrough
<script lang="ts">
import type { Snippet } from 'svelte';
interface Props {
requiredProperty: number;
optionalProperty?: boolean;
snippetWithStringArgument: Snippet<[string]>;
eventHandler: (arg: string) => void;
[key: string]: unknown; // 配合 ...rest 透传其他属性
}
let { requiredProperty, optionalProperty, snippetWithStringArgument,
eventHandler, ...rest }: Props = $props();
</script>
Generic components
<script> of generics attribute accepts the same syntax as function generics:
<script lang="ts" generics="Item extends { text: string }">
interface Props { items: Item[]; select(item: Item): void; }
let { items, select }: Props = $props();
</script>
{#each items as item}
<button onclick={() => select(item)}>{item.text}</button>
{/each}
Wrapper components (forwarding native attributes)
<script lang="ts">
import type { HTMLButtonAttributes } from 'svelte/elements';
let { children, ...rest }: HTMLButtonAttributes = $props();
</script>
<button {...rest}>{@render children?.()}</button>
For elements without a dedicated interface, use the SvelteHTMLElements['div'] form.Component / ComponentProps<T> is used to constrain dynamic components and extract the props of existing components.
In the class $state
class Counter {
count = $state() as number; // 无初值类型会扩为 undefined
constructor(initial: number) { this.count = initial; }
increment() { this.count += 1; }
}
Augment svelte/elements
/// file: additional-svelte-typings.d.ts
declare module 'svelte/elements' {
export interface SvelteHTMLElements { 'custom-button': HTMLButtonAttributes; }
export interface HTMLAttributes<T> { globalattribute?: string; }
}
export {}; // 必须——否则会覆盖原模块
Critical: Custom Elements(Web Components)
Basic usage
<svelte:options customElement="my-element" />
<script>
let { name = 'world' } = $props();
</script>
<h1>Hello {name}!</h1>
<slot />
Importing automatically customElements.define() registers. Consumers can read and write DOM properties:
const el = document.querySelector('my-element');
el.name = 'everybody'; // 触发 shadow DOM 更新
Props must be explicitly declared (you cannot just write
let props = $props()), otherwise Svelte does not know which keys to expose.
customElement options
<svelte:options customElement={{
tag: 'my-counter',
shadow: 'open',
props: { count: { type: 'Number', reflect: true, attribute: 'start' } },
extend: (Ctor) => class extends Ctor { /* ... */ }
}} />
| Field | Value | Default | Purpose |
|---|---|---|---|
tag |
string | — | Tag name; automatically registers when set |
shadow |
'none' | 'open' | 'closed' | ShadowRootInit |
'open' |
Shadow root configuration |
props.<name>.attribute |
string | Lowercase prop name | Custom attribute name |
props.<name>.reflect |
boolean | false |
Mirror the prop value to an HTML attribute |
props.<name>.type |
'String' | 'Boolean' | 'Number' | 'Array' | 'Object' |
'String' |
Attribute ↔ prop type conversion |
extend |
(Ctor) => class |
— | Wrap the generated class (can inject lifecycle / ElementInternals) |
$host() Rune + lifecycle
<svelte:options customElement="tooltip" />
<script>
import { $host } from 'svelte';
let { text = 'hi' } = $props();
function focus() { $host().focus(); }
</script>
<button onclick={focus}>{text}</button>
- Internal components are created at
connectedCallbackafter thenext tick ; properties set before insertion are buffered and applied. - Shadow DOM updates are batched to the next tick; DOM moves do not trigger unnecessary unmounts.
disconnectedCallbackdestroyed at the next tick after. Component methods are available only after mount; to expose them earlier, declare them in theextendclass.
Important considerations
- StylesEncapsulationrather than "scoping"—global CSS cannot penetrate the shadow root.
- CSS is inlined as a JS string rather than a separate
.cssfile. - Notsuitable for SSR (shadow DOM is not visible before JS loads).
- Slot contenteagerly rendered(unlike Svelte's default lazy rendering),
<slot>does not copy content in{#each}. let:directive on custom elementsinvalid。Sveltecontextcannot cross custom element boundaries.- Do not name
on*attribute (interpreted as an event listener).
Critical: Browser Support
Svelte 5 targets Baseline 2020。
| Browser | Minimum version |
|---|---|
| Chrome / Edge | 87 |
| Firefox | 83 |
| Safari | 14 |
| Opera | 73 |
| Opera (Android) | 62 |
| Samsung Internet | 14.0 |
| Android WebView | 87 |
| Internet Explorer | Not supported |
Exceptions
| Feature | Chrome/Edge | Firefox | Safari |
|---|---|---|---|
$state.snapshot |
98 | 94 | 15.4 |
bind:devicePixelContentBoxSize |
— | 93 | Not supported |
flip(from svelte/animate) |
— | 126 | — |
SSR browser API protection
<script>
import { onMount } from 'svelte';
onMount(() => {
window.addEventListener('resize', handleResize);
return () => window.removeEventListener('resize', handleResize);
});
</script>
Prefer
<svelte:window>/<svelte:document>; Svelte automatically handles SSR and unmounting.
Critical: Best Practices
$state — declare only when needed
Only declare values that trigger updates ($effect / $derived / template) as $state. For large values that are only reassigned (API responses), use $state.raw to avoid Proxy overhead:
let user = $state.raw<User | null>(null);
$derived > $effect for pure computation
let square = $derived(num * num); // 好
// let square; $effect(() => { square = num * num; }); // 差
For complex logic use $derived.by(() => { ... }). Derived can be written; object/array results are not deeply proxied.
$effect is an escape hatch
Preferred tools: syncing with external libraries → {@attach ...}; responding to user input → handlers; debug logging → $inspect / $inspect.trace; observing external sources → createSubscriber. Never if (browser) { ... } wrap effects — effects do not run on the server anyway.
Treat props as mutable, use attribute syntax for events
let { type } = $props();
let color = $derived(type === 'danger' ? 'red' : 'green');
<svelte:window> / <svelte:document> for global listening; do not onMount manual binding. Snippets replace Slots,{#each ... (item.id)} use stable keys (index cannot be used).style:-- pass JS variables through to CSS custom properties; use CSS variables (instead of :global) to pierce child component styles; use createContext instead of module-level shared state (provides type safety + prevents leaks between SSR users).
Avoid Legacy in new code
on:click → onclick;export let → $props();$: → $derived / $effect;<slot> → snippet;use:action → {@attach ...}。
Critical: Svelte 4 Migration Guide
Minimum version
| Dependency | Required |
|---|---|
| Node | 16+ |
| SvelteKit | 1.20.4+ (if used) |
vite-plugin-svelte |
2.4.1+ |
| TypeScript | 5+ |
Core migration comparison
| Svelte 4 | Svelte 5 |
|---|---|
let x = 0(top-level reactivity) |
let x = $state(0) |
$: x = a + b |
let x = $derived(a + b) |
$: { ... } |
$effect(() => { ... }) |
export let prop |
let { prop } = $props() |
<slot /> |
{@render children()} |
createEventDispatcher |
callback props |
on:click={fn} |
onclick={fn} |
Automated migration tools
npx svelte-migrate@latest svelte-5 my-project
Important behavior changes
- Transitions default to
local(outer control flow does not affect child transitions). - Default slot bindingswill notenter named slots.
- Preprocessor order: markup → script → style (each executed in declaration order).
createEventDispatcherchecks optional / required / no-parameter types in strict mode (later replaced by callback props).Action/ActionReturnDefault parameter type isundefined; declare explicitly when parameters are needed.SvelteComponentTypedis deprecated, useSvelteComponent。- CJS output and
svelte/registerwere removed. - Removed
svelte.JSX, usesvelteHTML/svelte/elements。 - Bundlers must specify the
browsercondition (SvelteKit / Vite automatically; Rollup use@rollup/plugin-node-resolveto setbrowser: true; Webpack add inconditionNamesadd"browser")。
Critical: Svelte 5 Migration Guide (Concept Summary)
Runes system:$state / $derived / $effect replaces implicit reactivity; Snippets replace Slots; event attributes replace on:;$effect explicitly tracks dependencies;$state deeply proxies objects/arrays.
| Removed | Replacement |
|---|---|
let top-level implicit reactivity |
$state |
$: statement |
$derived / $effect |
export let / $$props / $$restProps |
$props() |
createEventDispatcher |
callback props / $bindable |
| Slots | Snippets |
on:event |
onevent |
beforeUpdate / afterUpdate |
$effect.pre / $effect |
New:$state.eager、$effect.pre、$effect.tracking、$state.snapshot、hydratable、fork()、{#key} transition trigger,<svelte:boundary>、createContext。
FAQ
Q: What is the core difference between Svelte and Vue/React? A: Svelte has no virtual DOM — it generates precise DOM update code at compile time, with zero framework runtime overhead.
Q: Can Svelte 5 and Svelte 4 coexist?
A: Yes. Svelte 5 still supports Legacy Mode, and Svelte 4's $: and export let can continue to be used; you can migrate gradually within the same project.
Q: How do you upgrade to Svelte 5?
A: ① Upgrade svelte + sveltekit / vite-plugin-svelte; ② Run npx svelte-migrate@latest svelte-5; ③ Gradually migrate to Runes Mode.
Q: Does Svelte support SSR / TypeScript?
A: Both are natively supported. For SSR use render() or SvelteKit; for TS just use <script lang="ts">, and run in CI svelte-check。
Q: Does Svelte have a virtual DOM?
A: No. The Svelte compiler generates precise DOM manipulation code (element.textContent = ...), rather than going through vDOM diffing.
Q: Which browsers support Svelte 5? A: Baseline 2020 (Chrome / Edge 87+, Firefox 83+, Safari 14+). IE is not supported; a few features require newer versions (see the table above).
Q: How is the styling of custom elements controlled?
A: By default, shadow root mode 'open', global CSS will not penetrate. You can use shadow: 'none' to inherit global styles; use CSS variables to penetrate scoped styles.
Quick Fixes
| Problem | Solution |
|---|---|
| Svelte 4 code throws an error | Confirm the component does not use $state(entering Runes Mode) |
$: does not take effect |
Upgrade to Svelte 5; Legacy Mode is still supported |
| TypeScript throws an error | Confirm <script lang="ts"> and TypeScript 5+ |
| Bundle gets larger after migration | Check the tree-shaking configuration |
Properties starting with on are listened to |
Rename to avoid the on* prefix |
| Custom element SSR has no content | Prerendering does not support shadow DOM; wait for JS to load |
| generic component type throws an error | Check the generics string (same syntax as function generics) |
Gotchas
- Svelte 5 automatically enters Runes Mode — as long as any Rune is used (
$state, etc.), it is Runes Mode. - Legacy Mode — It can be forced on via
<svelte:options runes={false}>. on:clickis deprecated — Legacy Mode is still available, but will warn.createEventDispatcheris deprecated — New code should use callback props.$bindable(default)— Only used as a fallback when the parent component does not pass the prop; if the parent component passesundefined, undefined will be preserved.bind:thisGeneric components — The instance type isComponent<Props> | null, and an assertion is required.- Custom element inline CSS — There is no
.cssfile that can be linked by an external stylesheet. - Server-side effect does not run — Do not use
if (browser)to wrap the effect body.
Examples
Practical examples for Svelte 5 features.
- Dynamic Components: svelte:element and {#key} blocks
- Debug and HTML: @html, @debug, @const
- TypeScript:
- TypeScript Basics — Typed props, generics, event handlers, component refs
- TypeScript Advanced — 14 patterns: preprocessors, generic constraints, wrapper components,
Componenttype,$statein classes,svelte/elementsaugmentation,svelte-check,$bindable, form state
- Custom Elements: Custom Elements — 12 patterns:
customElementoption,$host(), type coercion, reflection,extend,ElementInternals, slot semantics - Best Practices: Svelte 5 Best Practices — 14 patterns:
$state.raw,$derived.by, keyed each blocks, CSS variables,createContext, async Svelte, legacy replacements - Migration: Svelte 3→4 Migration — 10 patterns:
tag→customElement, stricter types, transition scope, preprocessor order, CJS removal - Browser Support: Browser Support — 7 patterns: feature detection, polyfills, bundler target, SSR guards,
$state.snapshotfallback, Playwright pinning
References
In-depth reference documentation.
- Dynamic Components: Dynamic Components Reference —
svelte:element,{#key},{@html},@debug,@const - TypeScript:
- TypeScript Basics — Type inference, snippets, event types, generics
- TypeScript Advanced —
Componenttype,svelte/elements, DOM type augmentation,svelte-check, errors
- Custom Elements: Custom Elements —
customElementsurface,$host()rune, lifecycle, caveats,ElementInternals - Best Practices: Best Practices —
$state/$derived/$effect, props, events, snippets, each blocks, CSS variables, context, async - Browser Support: Browser Support — Minimum versions matrix, feature exceptions, polyfills, SSR safety, bundler config
Quick Links
| Feature | Reference |
|---|---|
svelte:element |
Dynamic Components |
{#key} |
Dynamic Components |
{@html} / @debug / @const |
Dynamic Components |
| TS basics | TypeScript Basics |
| TS advanced | TypeScript Advanced |
| Generic components | TypeScript Advanced |
| Wrapper components | TypeScript Advanced |
Component type |
TypeScript Advanced |
| DOM augmentation | TypeScript Advanced |
customElement options |
Custom Elements |
$host() rune |
Custom Elements |
| Web Component lifecycle | Custom Elements |
$state / $derived / $effect patterns |
Best Practices |
| Async Svelte | Best Practices |
| Browser matrix | Browser Support |
| Feature exceptions | Browser Support |
| Polyfills | Browser Support |