SuperAPI AI Directory

svelte-template-syntax

Frontend & websites

View repository

Updated 10-16 05:48

About this skill

Svelte Template Syntax Reference (Svelte 5)

This skill covers Svelte 5 template syntax, including basic markup, block-level expressions, template tags, event handling, attribute binding, style/class directives, attachments/actions, transitions and animations.

When to use this skill

Use this skill when the user needs to write or understand template syntax in Svelte components, including scenarios such as conditional rendering, list rendering, async handling, snippet reuse, event binding, two-way data binding, transition animations, etc.

Critical: Basic Markup

Element tags

  • Lowercase tags (<div>) = HTML elements
  • Uppercase/dot tags (<Widget>、<my.stuff>) = components

Attribute binding

<!-- 布尔属性 -->
<button disabled={!clickable}>...</button>
<input required={false} />

<!-- 简写(同名字相同) -->
<button {disabled}>...</button>

<!-- 展开属性(顺序决定优先级:后写覆盖先写) -->
<Widget {...props} a="b" />

<!-- 布尔属性:truthy 包含,falsy 排除 -->
<div data-active={isActive}>  <!-- true → 包含,false/null/undefined → 排除 -->

Event handling

<!-- onclick 是属性,不是指令 -->
<button onclick={() => count++}>+</button>
<input {onkeydown} />          <!-- 简写形式 -->
<button {...handlerProps}>     <!-- 展开形式 -->

<!-- 事件委托:大多数事件委托到根节点,无需 stopPropagation -->
<!-- 若需阻止:使用 svelte/events 的 on 函数 -->

Text expressions

<p>{expression}</p>           <!-- null/undefined → 省略,其余转字符串 -->
<p>Hello {name}!</p>

<!-- 原始 HTML(注意 XSS) -->
{@html rawContent}

<!-- 转义花括号 -->
<p>使用 &lbrace; 和 &rbrace;</p>

Critical: {#if ...}

Conditional rendering:

{#if count > 10}
  <p>big</p>
{:else if count > 5}
  <p>medium</p>
{:else}
  <p>small</p>
{/if}

{:else if} can be chained multiple times,{:else} is the final fallback.

Critical: {#each ...}

List rendering:

{#each items as item}
  <li>{item.name}</li>
{/each}

<!-- 带索引 -->
{#each items as item, i}
  <li>{i + 1}: {item.name}</li>
{/each}

<!-- 空列表 fallback -->
{#each todos as todo}
  <p>{todo.text}</p>
{:else}
  <p>No tasks!</p>
{/each}

Efficient updates with keys

{#each items as item (item.id)}
  <li>{item.name}</li>
{/each}

The key must be a unique identifier (string/number), used by Svelte for efficient diffing and DOM updates (insert/move/delete instead of full re-rendering).

Destructuring and rest

{#each items as { id, name, ...rest }}
  <li><span>{id}</span><MyComponent {...rest} /></li>
{/each}

{#each items as [id, ...values]}
  <li><span>{id}</span></li>
{/each}

Render N times

{#each { length: 8 }, rank}
  <div class:black={(rank) % 2 === 1}></div>
{/each}

Critical: {#key ...}

Destroy and rebuild content when the expression changes (triggering transition animations):

{#key value}
  <div transition:fade>{value}</div>
{/key}

Critical: {#await ...}

Promise async state branches:

{#await promise}
  <p>loading...</p>
{:then value}
  <p>result: {value}</p>
{:catch error}
  <p>error: {error.message}</p>
{/await}

<!-- 简洁形式(无 pending UI) -->
{#await promise then value}
  <p>{value}</p>
{/await}

<!-- 仅错误处理 -->
{#await promise catch error}
  <p>{error.message}</p>
{/await}

Lazy-loaded components

{#await import('./Heavy.svelte') then { default: Component }}
  <Component />
{/await}

await expressions (Svelte 5.36+ experimental)

<!-- 需 svelte.config.js 中启用 experimental.async -->
<svelte:boundary>
  <p>{await fetchData()}</p>
  {#snippet pending()}<p>loading</p>{/snippet}
</svelte:boundary>

Features: synchronous updates, concurrent execution (independent await)、<svelte:boundary> pending snippet、fork() API。

Critical: {#snippet ...}

Reusable markup snippets, replacing Svelte 4's Slots:

{#snippet card(item)}
  <div class="card">
    <h3>{item.title}</h3>
    <p>{item.body}</p>
  </div>
{/snippet}

{@render card(item)}

Snippet scope

Snippets can reference external variables (script variables or {#each} block-level variables), and are visible to siblings/child nodes ofthe same lexical scope:

{#each items as item}
  {#snippet itemCard()}
    <div>{item.name}</div>  <!-- 引用 item -->
  {/snippet}
  {@render itemCard()}
{/each}

Explicit vs implicit prop

<!-- 隐式 prop(推荐)-->
<Table {data}>
  {#snippet header()}
    <th>Name</th>
  {/snippet}
</Table>

<!-- 显式 prop -->
<Table {data} header={myHeader} />

Implicit children

Non-snippet content within a component tag automatically becomes children snippet:

<Button>click me</Button>

<!-- Button.svelte -->
<script>
  let { children } = $props();
</script>
<button>{@render children()}</button>

Optional snippet

{@render children?.()}              <!-- 可选链 -->
{#if children}{:else}fallback{/if}  <!-- #if fallback -->

Snippet type definition

<script lang="ts">
  import type { Snippet } from 'svelte';
  let { row }: { row: Snippet<[Item]> } = $props();
</script>

Exported snippet (5.5+)

<script module>
  export { mySnippet };
</script>
{#snippet mySnippet()}
  <div>content</div>
{/snippet}

Programmatic creation (createRawSnippet)

import { createRawSnippet } from 'svelte';
const greet = createRawSnippet<[string]>((name) => ({
  render: () => `<p>Hello, ${name}!</p>`
}));

Critical: {@render ...}

Render snippet:

{@render snippetName(args)}
{@render children?.()}  <!-- 可选链安全调用 -->
{@render (cond ? a : b)()}  <!-- 任意表达式 -->

Critical: {@html ...}

Insert raw HTML (beware XSS risk):

{@html rawHtml}
  • {expression} automatically escapes HTML entities
  • {@html expression} directly inserts raw HTML; ensure content is trusted
  • Must becomplete and self-contained HTML (cannot span {@html} to concatenate tags)
  • Not affected by scoped styles — use :global to wrap

Critical: {@attach ...}

Functions that run when an element is mounted (Svelte 5.29+), replacing use: action:

<script>
  /** @type {import('svelte/attachments').Attachment} */
  function tooltip(node) {
    const tip = createTip(node);
    return { destroy() { tip.destroy(); } };
  }
</script>

<button {@attach tooltip()}>hover</button>

Attachment Factory with parameters

function tooltip(content) {
  return (node) => {
    const tip = createTip(node, { content });
    return { destroy() { tip.destroy(); } };
  };
}

<button {@attach tooltip(content)}>hover</button>

Conditional Attachment

<div {@attach enabled && myAttachment}>...</div>

Inline Attachment

<canvas {@attach (canvas) => {
  const ctx = canvas.getContext('2d');
  $effect(() => { ctx.fillStyle = color; /* ... */ });
}}></canvas>

Re-run behavior

{@attach foo(bar)} in foo or bar changesfully rebuilds(unlike actions).

Converting from Action

import { fromAction } from 'svelte/attachments';
const attach = fromAction(myAction);

Critical: bind: directive

Data flows back from child to parent:

<!-- 简写 -->
<input bind:value />
<input bind:value={value} />

Function Bindings(5.9+)

<input bind:value={
  () => value,
  (v) => value = v.toLowerCase()
} />

Complete bind: list

Directive Element Type
bind:value input/textarea/select Two-way
bind:checked input[type=checkbox] Two-way
bind:indeterminate input[type=checkbox] Two-way
bind:group radio/checkbox group Two-way
bind:files input[type=file] two-way
bind:open details two-way
bind:value select multiple array
bind:currentTime/paused/volume/muted/playbackRate audio/video two-way
bind:duration/buffered/seeking/ended/readyState/played audio/video read-only
bind:videoWidth/Height video read-only
bind:naturalWidth/Height img read-only
bind:innerHTML/innerText/textContent contenteditable two-way
bind:clientWidth/Height/offsetWidth/Height/contentRect block-level element read-only
bind:this element/component reference
bind:innerWidth/Height svelte:window two-way
bind:scrollX/Y svelte:window two-way
bind:online svelte:window two-way
bind:fullscreenElement svelte:document two-way
bind:visibilityState svelte:document two-way

$bindable Props

<!-- 子组件 -->
<script>
  let { value = $bindable() } = $props();
</script>
<input bind:value />

<!-- 父组件 -->
<Child bind:value={text} />

Critical: use: Action (primarily promoted before Svelte 5.29, recommended {@attach} for 5.29+)

<script>
  /** @type {import('svelte/action').Action<HTMLElement, {color: string}>} */
  function paint(node, data) {
    node.style.color = data.color;
  }
</script>

<div use: paint={{ color: 'red' }}>red text</div>

Action is only called once (no SSR); when parameters changedoes notre-run.

Critical: transition: / in: / out:

Transition when an element enters/leaves the DOM:

<script>
  import { fade, fly } from 'svelte/transition';
  let visible = $state(false);
</script>

{#if visible}
  <div transition:fade>fades in and out</div>
  <div in:fly={{ y: 200 }} out:fade>flies in, fades out</div>
{/if}
  • transition: two-way (can reverse mid-way)
  • in: / out: one-way (parallel without interruption)
  • defaultlocal(|global modifier responds to ancestor block)

built-in transitions

fade / blur / fly / slide / scale / draw / crossfade(from svelte/transition)

custom transition

function whoosh(node, params) {
  return {
    duration: 400,
    easing: elasticOut,
    css: (t, u) => `transform: scale(${t})`
  };
}

transition events

onintrostart / onintroend / onoutrostart / onoutroend

Critical: animate:

Animation when existing elements within a keyed each blockreorder:

<script>
  import { flip } from 'svelte/animate';
</script>

{#each list as item, i (item)}
  <li animate:flip={{ duration: 300 }}>{item}</li>
{/each}
  • does nottrigger on add/remove (use transition:)
  • must keyed {#each} direct child element
  • flip is built-in (First-Last-In-First-Out smooth movement)

Critical: style: / class: / class

style: directive

<div style:color="red" style:width="12rem" style:--columns={n}>
  <!-- 简写、表达式、!important、CSS 自定义属性都支持 -->
</div>

style: directive priorityhigher than style attribute (even with !important)。

class attribute (5.16+ supports objects/arrays)

<!-- 对象:truthy key 添加 -->
<div class={{ cool, lame: !cool }}>...</div>

<!-- 数组:falsy 过滤后合并 -->
<div class={[faded && 'saturate-0', large && 'scale-200']}>...</div>

<!-- 嵌套扁平化 -->
<div class={['btn', { primary: true }, ['rounded', { large }]]}>...</div>

ClassValue type

<script lang="ts">
  import type { ClassValue } from 'svelte/elements';
  const props: { class: ClassValue } = $props();
</script>

class: directive (traditional, no longer recommended in 5.16+)

<div class:cool={cool} class:lame={!cool}>
  <!-- 简写 class:cool -->
</div>

Critical: {@const ...}

⚠️ Legacy syntax, recommend using {const x = $derived(y)} instead

Define constants within block scope:

{#each boxes as box}
  {@const area = box.width * box.height}
  <p>{box.width} × {box.height} = {area}</p>
{/each}

Can only be {#if}、{#each}、{#snippet}、<Component />、<svelte:boundary> direct child element.

Critical: {let/const ...} (recommended in 5.56+)

{#each boxes as box}
  {const area = box.width * box.height}
  <p>{area}</p>
{/each}

Can be anywhere in the component; references external values, scope is lexical siblings/children.

Reactive:

{let name = $state(user.name)}
{const greeting = $derived(`Hello ${name}`)}

Critical: {@debug ...}

Debug label, prints to console when variable changes:

{@debug user, count}       <!-- 指定变量 -->
{@debug}                   <!-- 任意状态变化断点 -->

Note:{@debug} can only accept variable names (not expressions).

Quick Fixes

Problem Solution
List does not update Use keyed {#each items as item (item.id)}
Empty list has no fallback Add {:else} branch
Transition does not trigger Use {#key value} wrapping to trigger rebuild
{@html} style does not take effect Use :global or do not use {@html}
Snippet is not visible Confirm it is within the same lexical scope
await sync behavior does not work Enable experimental.async
Reverse transition does not work Use transition: instead of in: / out:
animate: does not trigger Must be keyed {#each} + direct child element

Gotchas

  1. {#each} key must be a unique value — using the index (i) as the key will cause incorrect efficient updates
  2. {@html} is not affected by scoped styles — inserted HTML does not include Svelte scoped hash
  3. {@debug} cannot debug expression — only variable names are accepted
  4. Snippet is visible within lexical scope — nesting level determines visibility
  5. {@attach} is Svelte 5.29+ — older versions use use: action
  6. Event attributes start with on start with — onclick is not on:click(Svelte 4 syntax is deprecated)
  7. {@attach} rerun full setup — opposite of action not rerunning; for expensive setup use getBar form wrapper
  8. class property's false/NaN serialized as string — 5.16+ use object form
  9. bind:group is valid only within the same component — invalid across components
  10. Transitions do not run during SSR — activated after client hydration

FAQ

Q: {#each} in key what is the purpose of A: Helps Svelte diff efficiently: when data changes, precisely identify which element changed by key (insert/move/delete), avoiding full re-rendering.

Q: What is the difference between Snippet and Slot? A: Snippet is a new mechanism in Svelte 5, more powerful and flexible. Slot can only pass markup; Snippet can receive parameters, can be used directly at the definition site, can be passed to components, and shares lexical scope.

Q: {@html} and ordinary text interpolation? A: {expr} automatically escapes HTML;{@html expr} inserts raw HTML directly. Raw HTML is not affected by scoped styles.

Q: When to use {#key}? A: When an animation/rebuild needs to be triggered by an expression change, such as route switching, user switching, etc.

Q: use: and {@attach} how to choose? A: Svelte 5.29+ recommends {@attach} — more flexible (reactive parameters, usable with components). When the old library only provides action, use fromAction(act) wrapper.

Q: transition: vs in: / out:? A: transition: bidirectional reversible;in: / out: one-way parallel playback does not interrupt each other.

Q: animate: and transition: difference? A: animate: is existing elements in keyed eachreorderingwhen;transition: is when elements enter/leave the DOM.

Q: When to use await expression vs {#await} block? A: New code recommends await expression (requires experimental.async), supports synchronous updates, concurrent. Old code or simple branching can use {#await} block.

Examples

For runnable code examples, see examples/ directory:

File Content
basic-markup.md Tags, attributes, spread, text expressions, comments, event delegation
if-await-snippet.md {#if}/{#each}/{#await}/{#snippet} from basics to recursion
snippet-advanced.md Snippet scope, explicit/implicit prop, optional, typed, module export, createRawSnippet
render-tags.md {@render}/{@html}/{@debug}/{@const}/{@attach}
attachments.md {@attach} factory / inline / conditional / convert action
use-action.md use: directive + Action type signature + use→{@attach} migration
transitions.md transition: / in: / out: parameters, custom functions, events, crossfade
animations.md animate:flip / custom function / combined with transition
await-expressions.md synchronized / concurrent / pending / fork / SSR
style-class.md style: directives, class object/array, class: legacy, ClassValue
bind-directives.md bind:value/checked/group/files/this/offsetWidth/Function bindings
event-handlers.md onclick, event bubbling, delegation, window/document events

References

For an in-depth technical reference, see references/ Table of contents:

File Content
basic-markup.md Tag semantics, attribute rules, event delegation, comments, SSR behavior
if-await-each.md Complete syntax of {#if}/{#else}/{#each}/{#await} and the keyed mechanism
snippet-render.md Snippet definition/call/recursion/argument passing, @render/@const type signatures
snippet-advanced.md Explicit/implicit props, optional snippets, createRawSnippet, module exports, Snippet vs Slot
special-tags.md {@html}/{@debug}/{@attach}/svelte:element
attachments.md {@attach} semantics, reactive re-runs, fromAction migration, svelte/attachments API
transitions.md Local/global, built-in transitions, custom function css/tick, events, crossfade
animations.md animate:flip, custom functions (from/to DOMRect), combining with transition
await-expressions.md Synchronous updates, concurrency, $effect.pending(), settled(), fork, SSR
style-class.md style: precedence, class object/array/ClassValue, !important rules
bind-reference.md Complete list of all bind: directives and the elements they apply to
event-reference.md Event handling: onclick vs on:click, window/document events