svelte-template-syntax について
Svelte 5 のテンプレート構文を扱う開発者向けのリファレンススキルです。マークアップ、ブロック、バインディング、イベント、スニペット、アニメーションを解説し、例とトラブルシューティングも掲載しています。
主な機能
- 01条件・リスト・非同期ブロックを解説
- 02スニペットの定義と描画を説明
- 03バインディングとイベント構文を参照
- 04アタッチメント、アクション、ディレクティブを解説
- 05トランジションとリストアニメーションを解説
活用シーン
- 01条件レンダリングやリスト表示を実装する
- 02スニペットを再利用しコンポーネント内容を渡す
- 03フォーム要素にバインディングとイベントを追加する
- 04要素にトランジションや並べ替えアニメーションを付ける
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ full-stack-skills/ svelte-skills --skill svelte-template-syntaxインストール後、Claude Code で/svelte-template-syntaxを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
agents/openai.yamlexamples/animations.mdattachments.mdawait-expressions.mdbasic-markup.mdbind-directives.mdevent-handlers.mdif-await-snippet.mdREADME.mdrender-tags.mdsnippet-advanced.mdstyle-class.mdtransitions.mduse-action.mdreferences/animations.mdattachments.mdawait-expressions.mdbasic-markup.mdbind-reference.mdevent-reference.mdif-await-each.mdREADME.mdsnippet-advanced.mdsnippet-render.mdspecial-tags.mdstyle-class.mdtransitions.md詳細
Svelte テンプレート構文リファレンス(Svelte 5)
このスキルでは、基本的なマークアップ、ブロック式、テンプレートタグ、イベント処理、属性バインディング、style/class ディレクティブ、アタッチメント/アクション、トランジション、アニメーションなど、Svelte 5 のテンプレート構文を扱います。
このスキルを使用する場面
ユーザーが Svelte コンポーネントのテンプレート構文を記述または理解したい場合に使用します。条件付きレンダリング、リストレンダリング、非同期処理、スニペットの再利用、イベントバインディング、双方向データバインディング、トランジションアニメーションなどが該当します。
重要:基本的なマークアップ
要素タグ
- 小文字のタグ(
<div>)=HTML 要素 - 大文字またはドットを含むタグ(
<Widget>、<my.stuff>)=コンポーネント
属性バインディング
<button disabled={!clickable}>...</button>
<input required={false} />
<button {disabled}>...</button>
<Widget {...props} a="b" />
<div data-active={isActive}>
イベント処理
<button onclick={() => count++}>+</button>
<input {onkeydown} />
<button {...handlerProps}>
テキスト式
<p>{expression}</p>
<p>Hello {name}!</p>
{@html rawContent}
<p>使用 { 和 }</p>
重要:{#if ...}
条件付きレンダリング:
{#if count > 10}
<p>big</p>
{:else if count > 5}
<p>medium</p>
{:else}
<p>small</p>
{/if}
{:else if} は複数回連結でき、{:else} が最後のフォールバックになります。
重要:{#each ...}
リストレンダリング:
{#each items as item}
<li>{item.name}</li>
{/each}
{#each items as item, i}
<li>{i + 1}: {item.name}</li>
{/each}
{#each todos as todo}
<p>{todo.text}</p>
{:else}
<p>No tasks!</p>
{/each}
key を使った効率的な更新
{#each items as item (item.id)}
<li>{item.name}</li>
{/each}
Key は一意の識別子(文字列/数値)である必要があります。Svelte が効率的に差分を検出して DOM を更新するために使用されます(全体を再レンダリングするのではなく、挿入/移動/削除します)。
分割代入と 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}
N 回レンダリングする
{#each { length: 8 }, rank}
<div class:black={(rank) % 2 === 1}></div>
{/each}
重要:{#key ...}
式が変化したときに内容を破棄して再構築します(トランジションアニメーションを実行します):
{#key value}
<div transition:fade>{value}</div>
{/key}
重要:{#await ...}
Promise の非同期状態に応じた分岐:
{#await promise}
<p>loading...</p>
{:then value}
<p>result: {value}</p>
{:catch error}
<p>error: {error.message}</p>
{/await}
{#await promise then value}
<p>{value}</p>
{/await}
{#await promise catch error}
<p>{error.message}</p>
{/await}
コンポーネントの遅延読み込み
{#await import('./Heavy.svelte') then { default: Component }}
<Component />
{/await}
await 式(Svelte 5.36+ experimental)
<svelte:boundary>
<p>{await fetchData()}</p>
{#snippet pending()}<p>loading</p>{/snippet}
</svelte:boundary>
特徴:同期更新、並行実行(独立した await)、<svelte:boundary> の pending スニペット、fork() API。
重要:{#snippet ...}
再利用可能なマークアップスニペットです。Svelte 4 の Slots に代わるものです:
{#snippet card(item)}
<div class="card">
<h3>{item.title}</h3>
<p>{item.body}</p>
</div>
{/snippet}
{@render card(item)}
スニペットのスコープ
スニペットは外部変数(script 変数または {#each} ブロック内の変数)を参照でき、同じ字句スコープ内の兄弟ノードや子ノードから参照できます:
{#each items as item}
{#snippet itemCard()}
<div>{item.name}</div>
{/snippet}
{@render itemCard()}
{/each}
明示的な prop と暗黙的な prop
<Table {data}>
{#snippet header()}
<th>Name</th>
{/snippet}
</Table>
<Table {data} header={myHeader} />
暗黙的な children
コンポーネントタグ内のスニペット以外のコンテンツは、自動的に children スニペットになります:
<Button>click me</Button>
<script>
let { children } = $props();
</script>
<button>{@render children()}</button>
オプションのスニペット
{@render children?.()}
{#if children}{:else}fallback{/if}
スニペットの型定義
<script lang="ts">
import type { Snippet } from 'svelte';
let { row }: { row: Snippet<[Item]> } = $props();
</script>
スニペットのエクスポート(5.5+)
<script module>
export { mySnippet };
</script>
{#snippet mySnippet()}
<div>content</div>
{/snippet}
プログラムによる作成(createRawSnippet)
import { createRawSnippet } from 'svelte';
const greet = createRawSnippet<[string]>((name) => ({
render: () => `<p>Hello, ${name}!</p>`
}));
重要:{@render ...}
スニペットをレンダリングします:
{@render snippetName(args)}
{@render children?.()}
{@render (cond ? a : b)()}
重要:{@html ...}
生の HTML を挿入します(XSS のリスクに注意):
{@html rawHtml}
{expression}は HTML エンティティを自動的にエスケープします{@html expression}は生の HTML を直接挿入するため、内容が信頼できることを確認してください- 完全に独立した HTML である必要があります(
{@html}をまたいでタグをつなげることはできません) - スコープ付きスタイルは適用されません。
:globalで囲んでください
重要:{@attach ...}
要素のマウント時に実行される関数です(Svelte 5.29+)。use: アクションに代わるものです:
<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
function tooltip(content) {
return (node) => {
const tip = createTip(node, { content });
return { destroy() { tip.destroy(); } };
};
}
<button {@attach tooltip(content)}>hover</button>
条件付き Attachment
<div {@attach enabled && myAttachment}>...</div>
インライン Attachment
<canvas {@attach (canvas) => {
const ctx = canvas.getContext('2d');
$effect(() => { ctx.fillStyle = color; /* ... */ });
}}></canvas>
再実行の動作
{@attach foo(bar)} は、foo または bar が変化すると完全に再構築されます(アクションとは異なります)。
Action からの移行
import { fromAction } from 'svelte/attachments';
const attach = fromAction(myAction);
重要:bind: ディレクティブ
子から親へデータを反映します:
<input bind:value />
<input bind:value={value} />
Function Bindings(5.9+)
<input bind:value={
() => value,
(v) => value = v.toLowerCase()
} />
bind: ディレクティブ一覧
| ディレクティブ | 要素 | 型 |
|---|---|---|
bind:value | input/textarea/select | 双方向 |
bind:checked | input[type=checkbox] | 双方向 |
bind:indeterminate | input[type=checkbox] | 双方向 |
bind:group | radio/checkbox グループ | 双方向 |
bind:files | input[type=file] | 双方向 |
bind:open | details | 双方向 |
bind:value | select multiple | 配列 |
bind:currentTime/paused/volume/muted/playbackRate | audio/video | 双方向 |
bind:duration/buffered/seeking/ended/readyState/played | audio/video | 読み取り専用 |
bind:videoWidth/Height | video | 読み取り専用 |
bind:naturalWidth/Height | img | 読み取り専用 |
bind:innerHTML/innerText/textContent | contenteditable | 双方向 |
bind:clientWidth/Height/offsetWidth/Height/contentRect | ブロック要素 | 読み取り専用 |
bind:this | 要素/コンポーネント | 参照 |
bind:innerWidth/Height | svelte:window | 双方向 |
bind:scrollX/Y | svelte:window | 双方向 |
bind:online | svelte:window | 双方向 |
bind:fullscreenElement | svelte:document | 双方向 |
bind:visibilityState | svelte:document | 双方向 |
$bindable Props
<script>
let { value = $bindable() } = $props();
</script>
<input bind:value />
<Child bind:value={text} />
重要:use: Action(Svelte 5.29 より前は主流。5.29+ では {@attach} を推奨)
<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 は一度だけ呼び出されます(SSR では実行されません)。引数が変化しても再実行されません。
重要:transition: / in: / out:
要素が 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:は双方向です(途中で反転できます)in:/out:は一方向です(並行して実行され、中断されません)- デフォルトはローカルです(
|global修飾子を付けると祖先ブロックに反応します)
組み込みトランジション
svelte/transition に含まれる fade / blur / fly / slide / scale / draw / crossfade
カスタムトランジション
function whoosh(node, params) {
return {
duration: 400,
easing: elasticOut,
css: (t, u) => `transform: scale(${t})`
};
}
トランジションイベント
onintrostart / onintroend / onoutrostart / onoutroend
重要:animate:
keyed each ブロック内の既存要素が並べ替えられたときのアニメーション:
<script>
import { flip } from 'svelte/animate';
</script>
{#each list as item, i (item)}
<li animate:flip={{ duration: 300 }}>{item}</li>
{/each}
- 追加/削除時には実行されません(
transition:を使用してください) - keyed
{#each}の直接の子要素である必要があります flipは組み込みです(First-Last-In-First-Out による滑らかな移動)
重要:style: / class: / class
style: ディレクティブ
<div style:color="red" style:width="12rem" style:--columns={n}>
</div>
style: ディレクティブは style 属性より優先されます(!important が付いていても同じです)。
class 属性(5.16+ ではオブジェクト/配列に対応)
<div class={{ cool, lame: !cool }}>...</div>
<div class={[faded && 'saturate-0', large && 'scale-200']}>...</div>
<div class={['btn', { primary: true }, ['rounded', { large }]]}>...</div>
ClassValue 型
<script lang="ts">
import type { ClassValue } from 'svelte/elements';
const props: { class: ClassValue } = $props();
</script>
class: ディレクティブ(従来の方式。5.16+ では非推奨)
<div class:cool={cool} class:lame={!cool}>
</div>
Critical: {@const ...}
⚠️ レガシー構文です。
{const x = $derived(y)}に置き換えることをおすすめします。
ブロックレベルのスコープで定数を定義します。
{#each boxes as box}
{@const area = box.width * box.height}
<p>{box.width} × {box.height} = {area}</p>
{/each}
{#if}、{#each}、{#snippet}、<Component />、<svelte:boundary> の直接の子要素にのみ使用できます。
Critical: {let/const ...}(5.56+ 推奨)
{#each boxes as box}
{const area = box.width * box.height}
<p>{area}</p>
{/each}
コンポーネント内の任意の場所で使用できます。外部の値を参照し、スコープは字句的な兄弟要素/子要素です。
リアクティブです。
{let name = $state(user.name)}
{const greeting = $derived(`Hello ${name}`)}
Critical: {@debug ...}
デバッグタグです。変数が変化すると、コンソールに出力します。
{@debug user, count}
{@debug}
注意:{@debug} が受け取れるのは変数名のみです(式は指定できません)。
Quick Fixes
| 問題 | 解決策 |
|---|---|
| リストが更新されない | キー付きの {#each items as item (item.id)} を使用します |
| 空のリストにフォールバックがない | {:else} 分岐を追加します |
| トランジションが実行されない | {#key value} で囲んで再作成をトリガーします |
{@html} のスタイルが適用されない | :global を使用するか、{@html} を使用しないようにします |
| Snippet が表示されない | 同じ字句スコープ内にあることを確認します |
await の同期動作が機能しない | experimental.async を有効にします |
| 逆方向のトランジションが機能しない | in: / out: ではなく transition: を使用します |
animate: が実行されない | keyed {#each} と直接の子要素が必要です |
Gotchas
{#each}の key は一意でなければなりません — インデックス(i)を key に使用すると、効率的な更新が正しく行われません{@html}はスコープ付きスタイルの影響を受けません — 挿入された HTML には Svelte のスコープハッシュが含まれません{@debug}では式をデバッグできません — 変数名のみ指定できます- Snippet は字句スコープ内で参照できます — 可視性はネストの階層によって決まります
{@attach}は Svelte 5.29+ で使用できます — 旧バージョンではuse:action を使用します- イベント属性は
onで始まります —onclickはon:clickではありません(Svelte 4 の構文は非推奨です) {@attach}は setup 全体を再実行します — action とは異なり、action は再実行されません。コストの高い setup はgetBar形式でラップしますclass属性のfalse/NaNは文字列にシリアライズされます — 5.16+ ではオブジェクト形式を使用しますbind:groupは同じコンポーネント内でのみ有効です — コンポーネント間では機能しません- SSR 中は Transitions が実行されません — クライアントでの hydration 後に有効になります
FAQ
Q: {#each} の key はどのような役割を果たしますか?
A: Svelte が効率的に差分を検出できるようにします。データが変化したとき、key によってどの要素が変化したか(挿入/移動/削除)を正確に特定し、全体の再レンダリングを防ぎます。
Q: Snippet と Slot の違いは何ですか? A: Snippet は Svelte 5 の新しい仕組みで、より強力で柔軟です。Slot ではマークアップのみを渡せますが、Snippet はパラメーターを受け取り、定義した場所で直接使用でき、コンポーネントに渡すこともでき、字句スコープを共有できます。
Q: {@html} と通常のテキスト補間の違いは何ですか?
A: {expr} は HTML を自動的にエスケープします。{@html expr} は生の HTML を直接挿入します。生の HTML はスコープ付きスタイルの影響を受けません。
Q: {#key} はどのような場合に使いますか?
A: 式の変化に応じてアニメーションや再作成をトリガーしたい場合に使います。たとえば、ルートの切り替えやユーザーの切り替えなどです。
Q: use: と {@attach} はどちらを選べばよいですか?
A: Svelte 5.29+ では、より柔軟な {@attach}(パラメーターのリアクティブな更新に対応し、コンポーネントにも使用できます)がおすすめです。古いライブラリが action のみを提供する場合は、fromAction(act) でラップします。
Q: transition: と in: / out: の違いは何ですか?
A: transition: は双方向で、逆方向にも再生できます。in: / out: は一方向で、互いに中断することなく並行して再生されます。
Q: animate: と transition: の違いは何ですか?
A: animate: は keyed each 内の既存要素が並べ替えられるときに使います。transition: は要素が DOM に入る/DOM から離れるときに使います。
Q: await 式と {#await} ブロックは、どのように使い分けますか?
A: 新しいコードでは await 式がおすすめです(experimental.async が必要)。同期更新と並行処理に対応しています。古いコードや単純な分岐では {#await} ブロックを使用できます。
Examples
実行可能なコード例は examples/ ディレクトリを参照してください。
| ファイル | 内容 |
|---|---|
basic-markup.md | タグ、属性、スプレッド、テキスト式、コメント、イベント委譲 |
if-await-snippet.md | {#if}/{#each}/{#await}/{#snippet} の基本から再帰まで |
snippet-advanced.md | Snippet のスコープ、明示的/暗黙的な prop、省略可能、型付き、モジュールエクスポート、createRawSnippet |
render-tags.md | {@render}/{@html}/{@debug}/{@const}/{@attach} |
attachments.md | {@attach} factory / inline / 条件付き / action への変換 |
use-action.md | use: ディレクティブ + Action 型シグネチャ + use→{@attach} への移行 |
transitions.md | transition:/in:/out: のパラメーター、カスタム関数、イベント、crossfade |
animations.md | animate:flip / カスタム関数 / transition との組み合わせ |
await-expressions.md | 同期 / 並行処理 / pending / fork / SSR |
style-class.md | style: ディレクティブ、class オブジェクト/配列、従来の class:、ClassValue |
bind-directives.md | bind:value/checked/group/files/this/offsetWidth/Function bindings |
event-handlers.md | onclick、イベントバブリング、委譲、window/document イベント |
References
詳しい技術リファレンスは references/ ディレクトリを参照してください。
| ファイル | 内容 |
|---|---|
basic-markup.md | タグの意味、属性のルール、イベント委譲、コメント、SSR の動作 |
if-await-each.md | {#if}/{#else}/{#each}/{#await} の完全な構文と keyed の仕組み |
snippet-render.md | Snippet の定義/呼び出し/再帰/パラメーター渡し、@render/@const の型シグネチャ |
snippet-advanced.md | 明示的/暗黙的な prop、省略可能な Snippet、createRawSnippet、モジュールエクスポート、Snippet と Slot の違い |
special-tags.md | {@html}/{@debug}/{@attach}/svelte:element |
attachments.md | {@attach} の意味、リアクティブな再実行、fromAction への移行、svelte/attachments API |
transitions.md | ローカル/グローバル、組み込み transitions、カスタム関数の css/tick、events、crossfade |
animations.md | animate:flip、カスタム関数(from/to DOMRect)、transition との組み合わせ |
await-expressions.md | 同期更新、並行処理、$effect.pending()、settled()、fork、SSR |
style-class.md | style: の優先順位、class オブジェクト/配列/ClassValue、!important のルール |
bind-reference.md | すべての bind: ディレクティブの完全な一覧と対象要素 |
event-reference.md | イベント処理:onclick と on:click の違い、window/document イベント |