Skip to main content

Template Syntax

Element directives

Edit this page on GitHub

As well as attributes, elements can have directives, which control the element's behaviour in some way.

on:eventname

on:eventname={handler}
on:eventname|modifiers={handler}

Use the on: directive to listen to DOM events.

App.svelte
<script>
  let count = 0;

  /** @param {MouseEvent} event */
  function handleClick(event) {
    count += 1;
  }
</script>

<button on:click={handleClick}>
  count: {count}
</button>
App.svelte
<script lang="ts">
  let count = 0;
  
  /** @param {MouseEvent} event */
  function handleClick(event) {
    count += 1;
  }
</script>

<button on:click={handleClick}>
  count: {count}
</button>

Handlers can be declared inline with no performance penalty. As with attributes, directive values may be quoted for the sake of syntax highlighters.

<button on:click={() => (count += 1)}>
  count: {count}
</button>

Add modifiers to DOM events with the | character.

<form on:submit|preventDefault={handleSubmit}>
  <!-- the `submit` event's default is prevented,
     so the page won't reload -->
</form>

The following modifiers are available:

  • preventDefault — calls event.preventDefault() before running the handler
  • stopPropagation — calls event.stopPropagation(), preventing the event reaching the next element
  • stopImmediatePropagation - calls event.stopImmediatePropagation(), preventing other listeners of the same event from being fired.
  • passive — improves scrolling performance on touch/wheel events (Svelte will add it automatically where it's safe to do so)
  • nonpassive — explicitly set passive: false
  • capture — fires the handler during the capture phase instead of the bubbling phase
  • once — remove the handler after the first time it runs
  • self — only trigger handler if event.target is the element itself
  • trusted — only trigger handler if event.isTrusted is true. I.e. if the event is triggered by a user action.

Modifiers can be chained together, e.g. on:click|once|capture={...}.

If the on: directive is used without a value, the component will forward the event, meaning that a consumer of the component can listen for it.

<button on:click> The component itself will emit the click event </button>

It's possible to have multiple event listeners for the same event:

App.svelte
<script>
  let counter = 0;
  function increment() {
    counter = counter + 1;
  }

  /** @param {MouseEvent} event */
  function track(event) {
    trackEvent(event);
  }
</script>

<button on:click={increment} on:click={track}>Click me!</button>
App.svelte
<script lang="ts">
  let counter = 0;
  function increment() {
    counter = counter + 1;
  }
  
  /** @param {MouseEvent} event */
  function track(event) {
    trackEvent(event);
  }
</script>

<button on:click={increment} on:click={track}>Click me!</button>

bind:property

bind:property={variable}

Data ordinarily flows down, from parent to child. The bind: directive allows data to flow the other way, from child to parent. Most bindings are specific to particular elements.

The simplest bindings reflect the value of a property, such as input.value.

<input bind:value={name} />
<textarea bind:value={text} />

<input type="checkbox" bind:checked={yes} />

If the name matches the value, you can use a shorthand.

<input bind:value />
<!-- equivalent to
<input bind:value={value} />
-->

Numeric input values are coerced; even though input.value is a string as far as the DOM is concerned, Svelte will treat it as a number. If the input is empty or invalid (in the case of type="number"), the value is undefined.

<input type="number" bind:value={num} />
<input type="range" bind:value={num} />

On <input> elements with type="file", you can use bind:files to get the FileList of selected files. It is readonly.

<label for="avatar">Upload a picture:</label>
<input accept="image/png, image/jpeg" bind:files id="avatar" name="avatar" type="file" />

If you're using bind: directives together with on: directives, the order that they're defined in affects the value of the bound variable when the event handler is called.

<script>
  let value = 'Hello World';
</script>

<input
  on:input={() => console.log('Old value:', value)}
  bind:value
  on:input={() => console.log('New value:', value)}
/>

Here we were binding to the value of a text input, which uses the input event. Bindings on other elements may use different events such as change.

Binding <select> value

A <select> value binding corresponds to the value property on the selected <option>, which can be any value (not just strings, as is normally the case in the DOM).

<select bind:value={selected}>
  <option value={a}>a</option>
  <option value={b}>b</option>
  <option value={c}>c</option>
</select>

A <select multiple> element behaves similarly to a checkbox group. The bound variable is an array with an entry corresponding to the value property of each selected <option>.

<select multiple bind:value={fillings}>
  <option value="Rice">Rice</option>
  <option value="Beans">Beans</option>
  <option value="Cheese">Cheese</option>
  <option value="Guac (extra)">Guac (extra)</option>
</select>

When the value of an <option> matches its text content, the attribute can be omitted.

<select multiple bind:value={fillings}>
  <option>Rice</option>
  <option>Beans</option>
  <option>Cheese</option>
  <option>Guac (extra)</option>
</select>

Elements with the contenteditable attribute support the following bindings:

There are slight differences between each of these, read more about them here.

<div contenteditable="true" bind:innerHTML={html} />

<details> elements support binding to the open property.

<details bind:open={isOpen}>
  <summary>Details</summary>
  <p>Something small enough to escape casual notice.</p>
</details>

Media element bindings

Media elements (<audio> and <video>) have their own set of bindings — seven readonly ones...

  • duration (readonly) — the total duration of the video, in seconds
  • buffered (readonly) — an array of {start, end} objects
  • played (readonly) — ditto
  • seekable (readonly) — ditto
  • seeking (readonly) — boolean
  • ended (readonly) — boolean
  • readyState (readonly) — number between (and including) 0 and 4

...and five two-way bindings:

  • currentTime — the current playback time in the video, in seconds
  • playbackRate — how fast or slow to play the video, where 1 is 'normal'
  • paused — this one should be self-explanatory
  • volume — a value between 0 and 1
  • muted — a boolean value indicating whether the player is muted

Videos additionally have readonly videoWidth and videoHeight bindings.

<video
  src={clip}
  bind:duration
  bind:buffered
  bind:played
  bind:seekable
  bind:seeking
  bind:ended
  bind:readyState
  bind:currentTime
  bind:playbackRate
  bind:paused
  bind:volume
  bind:muted
  bind:videoWidth
  bind:videoHeight
/>

Image element bindings

Image elements (<img>) have two readonly bindings:

  • naturalWidth (readonly) — the original width of the image, available after the image has loaded
  • naturalHeight (readonly) — the original height of the image, available after the image has loaded
<img
  bind:naturalWidth
  bind:naturalHeight
></img>

Block-level element bindings

Block-level elements have 4 read-only bindings, measured using a technique similar to this one:

  • clientWidth
  • clientHeight
  • offsetWidth
  • offsetHeight
<div bind:offsetWidth={width} bind:offsetHeight={height}>
  <Chart {width} {height} />
</div>

bind:group

bind:group={variable}

Inputs that work together can use bind:group.

App.svelte
<script>
  let tortilla = 'Plain';

  /** @type {Array<string>} */
  let fillings = [];
</script>

<!-- grouped radio inputs are mutually exclusive -->
<input type="radio" bind:group={tortilla} value="Plain" />
<input type="radio" bind:group={tortilla} value="Whole wheat" />
<input type="radio" bind:group={tortilla} value="Spinach" />

<!-- grouped checkbox inputs populate an array -->
<input type="checkbox" bind:group={fillings} value="Rice" />
<input type="checkbox" bind:group={fillings} value="Beans" />
<input type="checkbox" bind:group={fillings} value="Cheese" />
<input type="checkbox" bind:group={fillings} value="Guac (extra)" />
App.svelte
<script lang="ts">
  let tortilla = 'Plain';
  
  /** @type {Array<string>} */
  let fillings = [];
</script>

<!-- grouped radio inputs are mutually exclusive -->
<input type="radio" bind:group={tortilla} value="Plain" />
<input type="radio" bind:group={tortilla} value="Whole wheat" />
<input type="radio" bind:group={tortilla} value="Spinach" />

<!-- grouped checkbox inputs populate an array -->
<input type="checkbox" bind:group={fillings} value="Rice" />
<input type="checkbox" bind:group={fillings} value="Beans" />
<input type="checkbox" bind:group={fillings} value="Cheese" />
<input type="checkbox" bind:group={fillings} value="Guac (extra)" />

bind:group only works if the inputs are in the same Svelte component.

bind:this

bind:this={dom_node}

To get a reference to a DOM node, use bind:this.

App.svelte
<script>
  import { onMount } from 'svelte';

  /** @type {HTMLCanvasElement} */
  let canvasElement;

  onMount(() => {
    const ctx = canvasElement.getContext('2d');
    drawStuff(ctx);
  });
</script>

<canvas bind:this={canvasElement} />
App.svelte
<script lang="ts">
  import { onMount } from 'svelte';
  
  /** @type {HTMLCanvasElement} */
  let canvasElement;
  
  onMount(() => {
    const ctx = canvasElement.getContext('2d');
    drawStuff(ctx);
  });
</script>

<canvas bind:this={canvasElement} />

class:name

class:name={value}
class:name

A class: directive provides a shorter way of toggling a class on an element.

<!-- These are equivalent -->
<div class={isActive ? 'active' : ''}>...</div>
<div class:active={isActive}>...</div>

<!-- Shorthand, for when name and value match -->
<div class:active>...</div>

<!-- Multiple class toggles can be included -->
<div class:active class:inactive={!active} class:isAdmin>...</div>

style:property

style:property={value}
style:property="value"
style:property

The style: directive provides a shorthand for setting multiple styles on an element.

<!-- These are equivalent -->
<div style:color="red">...</div>
<div style="color: red;">...</div>

<!-- Variables can be used -->
<div style:color={myColor}>...</div>

<!-- Shorthand, for when property and variable name match -->
<div style:color>...</div>

<!-- Multiple styles can be included -->
<div style:color style:width="12rem" style:background-color={darkMode ? 'black' : 'white'}>...</div>

<!-- Styles can be marked as important -->
<div style:color|important="red">...</div>

When style: directives are combined with style attributes, the directives will take precedence:

<div style="color: blue;" style:color="red">This will be red</div>

use:action

use:action
use:action={parameters}
ts
action = (node: HTMLElement, parameters: any) => {
update?: (parameters: any) => void,
destroy?: () => void
}

Actions are functions that are called when an element is created. They can return an object with a destroy method that is called after the element is unmounted:

App.svelte
<script>
  /** @type {import('svelte/action').Action}  */
  function foo(node) {
    // the node has been mounted in the DOM

    return {
      destroy() {
        // the node has been removed from the DOM
      }
    };
  }
</script>

<div use:foo />
App.svelte
<script lang="ts">
  /** @type {import('svelte/action').Action}  */
  function foo(node) {
    // the node has been mounted in the DOM
  
    return {
      destroy() {
        // the node has been removed from the DOM
      },
    };
  }
</script>

<div use:foo />

An action can have a parameter. If the returned value has an update method, it will be called whenever that parameter changes, immediately after Svelte has applied updates to the markup.

Don't worry about the fact that we're redeclaring the foo function for every component instance — Svelte will hoist any functions that don't depend on local state out of the component definition.

App.svelte
<script>
  export let bar;

  /** @type {import('svelte/action').Action}  */
  function foo(node, bar) {
    // the node has been mounted in the DOM

    return {
      update(bar) {
        // the value of `bar` has changed
      },

      destroy() {
        // the node has been removed from the DOM
      }
    };
  }
</script>

<div use:foo={bar} />
App.svelte
<script lang="ts">
  export let bar;
  
  /** @type {import('svelte/action').Action}  */
  function foo(node, bar) {
    // the node has been mounted in the DOM
  
    return {
      update(bar) {
        // the value of `bar` has changed
      },
  
      destroy() {
        // the node has been removed from the DOM
      },
    };
  }
</script>

<div use:foo={bar} />

Read more in the svelte/action page.

transition:fn

transition:fn
transition:fn={params}
transition:fn|global
transition:fn|global={params}
transition:fn|local
transition:fn|local={params}
ts
transition = (node: HTMLElement, params: any, options: { direction: 'in' | 'out' | 'both' }) => {
delay?: number,
duration?: number,
easing?: (t: number) => number,
css?: (t: number, u: number) => string,
tick?: (t: number, u: number) => void
}

A transition is triggered by an element entering or leaving the DOM as a result of a state change.

When a block is transitioning out, all elements inside the block, including those that do not have their own transitions, are kept in the DOM until every transition in the block has been completed.

The transition: directive indicates a bidirectional transition, which means it can be smoothly reversed while the transition is in progress.

{#if visible}
  <div transition:fade>fades in and out</div>
{/if}

Transitions are local by default (in Svelte 3, they were global by default). Local transitions only play when the block they belong to is created or destroyed, not when parent blocks are created or destroyed.

{#if x}
  {#if y}
    <!-- Svelte 3: <p transition:fade|local> -->
    <p transition:fade>fades in and out only when y changes</p>

    <!-- Svelte 3: <p transition:fade> -->
    <p transition:fade|global>fades in and out when x or y change</p>
  {/if}
{/if}

By default intro transitions will not play on first render. You can modify this behaviour by setting intro: true when you create a component and marking the transition as global.

Transition parameters

Like actions, transitions can have parameters.

(The double {{curlies}} aren't a special syntax; this is an object literal inside an expression tag.)

{#if visible}
  <div transition:fade={{ duration: 2000 }}>fades in and out over two seconds</div>
{/if}

Custom transition functions

Transitions can use custom functions. If the returned object has a css function, Svelte will create a CSS animation that plays on the element.

The t argument passed to css is a value between 0 and 1 after the easing function has been applied. In transitions run from 0 to 1, out transitions run from 1 to 0 — in other words, 1 is the element's natural state, as though no transition had been applied. The u argument is equal to 1 - t.

The function is called repeatedly before the transition begins, with different t and u arguments.

App.svelte
<script>
  import { elasticOut } from 'svelte/easing';

  /** @type {boolean} */
  export let visible;

  /**
   * @param {HTMLElement} node
   * @param {{ delay?: number, duration?: number, easing?: (t: number) => number }} params
   */
  function whoosh(node, params) {
    const existingTransform = getComputedStyle(node).transform.replace('none', '');

    return {
      delay: params.delay || 0,
      duration: params.duration || 400,
      easing: params.easing || elasticOut,
      css: (t, u) => `transform: ${existingTransform} scale(${t})`
    };
  }
</script>

{#if visible}
  <div in:whoosh>whooshes in</div>
{/if}
App.svelte
<script lang="ts">
  import { elasticOut } from 'svelte/easing';
  
  /** @type {boolean} */
  export let visible;
  
  /**
   * @param {HTMLElement} node
   * @param {{ delay?: number, duration?: number, easing?: (t: number) => number }} params
   */
  function whoosh(node, params) {
    const existingTransform = getComputedStyle(node).transform.replace('none', '');
  
    return {
      delay: params.delay || 0,
      duration: params.duration || 400,
      easing: params.easing || elasticOut,
      css: (t, u) => `transform: ${existingTransform} scale(${t})`,
    };
  }
</script>

{#if visible}
  <div in:whoosh>whooshes in</div>
{/if}

A custom transition function can also return a tick function, which is called during the transition with the same t and u arguments.

If it's possible to use css instead of tick, do so — CSS animations can run off the main thread, preventing jank on slower devices.

App.svelte
<script>
  export let visible = false;

  /**
   * @param {HTMLElement} node
   * @param {{ speed?: number }} params
   */
  function typewriter(node, { speed = 1 }) {
    const valid = node.childNodes.length === 1 && node.childNodes[0].nodeType === Node.TEXT_NODE;

    if (!valid) {
      throw new Error(`This transition only works on elements with a single text node child`);
    }

    const text = node.textContent;
    const duration = text.length / (speed * 0.01);

    return {
      duration,
      tick: (t) => {
        const i = ~~(text.length * t);
        node.textContent = text.slice(0, i);
      }
    };
  }
</script>

{#if visible}
  <p in:typewriter={{ speed: 1 }}>The quick brown fox jumps over the lazy dog</p>
{/if}
App.svelte
<script lang="ts">
  export let visible = false;
  
  /**
   * @param {HTMLElement} node
   * @param {{ speed?: number }} params
   */
  function typewriter(node, { speed = 1 }) {
    const valid = node.childNodes.length === 1 && node.childNodes[0].nodeType === Node.TEXT_NODE;
  
    if (!valid) {
      throw new Error(`This transition only works on elements with a single text node child`);
    }
  
    const text = node.textContent;
    const duration = text.length / (speed * 0.01);
  
    return {
      duration,
      tick: (t) => {
        const i = ~~(text.length * t);
        node.textContent = text.slice(0, i);
      },
    };
  }
</script>

{#if visible}
  <p in:typewriter={{ speed: 1 }}>The quick brown fox jumps over the lazy dog</p>
{/if}

If a transition returns a function instead of a transition object, the function will be called in the next microtask. This allows multiple transitions to coordinate, making crossfade effects possible.

Transition functions also receive a third argument, options, which contains information about the transition.

Available values in the options object are:

  • direction - one of in, out, or both depending on the type of transition

Transition events

An element with transitions will dispatch the following events in addition to any standard DOM events:

  • introstart
  • introend
  • outrostart
  • outroend
{#if visible}
  <p
    transition:fly={{ y: 200, duration: 2000 }}
    on:introstart={() => (status = 'intro started')}
    on:outrostart={() => (status = 'outro started')}
    on:introend={() => (status = 'intro ended')}
    on:outroend={() => (status = 'outro ended')}
  >
    Flies in and out
  </p>
{/if}

in:fn/out:fn

in:fn
in:fn={params}
in:fn|global
in:fn|global={params}
in:fn|local
in:fn|local={params}
out:fn
out:fn={params}
out:fn|global
out:fn|global={params}
out:fn|local
out:fn|local={params}

Similar to transition:, but only applies to elements entering (in:) or leaving (out:) the DOM.

Unlike with transition:, transitions applied with in: and out: are not bidirectional — an in transition will continue to 'play' alongside the out transition, rather than reversing, if the block is outroed while the transition is in progress. If an out transition is aborted, transitions will restart from scratch.

{#if visible}
  <div in:fly out:fade>flies in, fades out</div>
{/if}

animate:fn

animate:name
animate:name={params}
ts
animation = (node: HTMLElement, { from: DOMRect, to: DOMRect } , params: any) => {
delay?: number,
duration?: number,
easing?: (t: number) => number,
css?: (t: number, u: number) => string,
tick?: (t: number, u: number) => void
}
ts
DOMRect {
bottom: number,
height: number,
​​left: number,
right: number,
top: number,
width: number,
x: number,
y: number
}

An animation is triggered when the contents of a keyed each block are re-ordered. Animations do not run when an element is added or removed, only when the index of an existing data item within the each block changes. Animate directives must be on an element that is an immediate child of a keyed each block.

Animations can be used with Svelte's built-in animation functions or custom animation functions.

<!-- When `list` is reordered the animation will run-->
{#each list as item, index (item)}
  <li animate:flip>{item}</li>
{/each}

Animation Parameters

As with actions and transitions, animations can have parameters.

(The double {{curlies}} aren't a special syntax; this is an object literal inside an expression tag.)

{#each list as item, index (item)}
  <li animate:flip={{ delay: 500 }}>{item}</li>
{/each}

Custom animation functions

Animations can use custom functions that provide the node, an animation object and any parameters as arguments. The animation parameter is an object containing from and to properties each containing a DOMRect describing the geometry of the element in its start and end positions. The from property is the DOMRect of the element in its starting position, and the to property is the DOMRect of the element in its final position after the list has been reordered and the DOM updated.

If the returned object has a css method, Svelte will create a CSS animation that plays on the element.

The t argument passed to css is a value that goes from 0 and 1 after the easing function has been applied. The u argument is equal to 1 - t.

The function is called repeatedly before the animation begins, with different t and u arguments.

App.svelte
<script>
  import { cubicOut } from 'svelte/easing';

  /**
   * @param {HTMLElement} node
   * @param {{ from: DOMRect; to: DOMRect }} states
   * @param {any} params
   */
  function whizz(node, { from, to }, params) {
    const dx = from.left - to.left;
    const dy = from.top - to.top;

    const d = Math.sqrt(dx * dx + dy * dy);

    return {
      delay: 0,
      duration: Math.sqrt(d) * 120,
      easing: cubicOut,
      css: (t, u) => `transform: translate(${u * dx}px, ${u * dy}px) rotate(${t * 360}deg);`
    };
  }
</script>

{#each list as item, index (item)}
  <div animate:whizz>{item}</div>
{/each}
App.svelte
<script lang="ts">
  import { cubicOut } from 'svelte/easing';
  
  /**
   * @param {HTMLElement} node
   * @param {{ from: DOMRect; to: DOMRect }} states
   * @param {any} params
   */
  function whizz(node, { from, to }, params) {
    const dx = from.left - to.left;
    const dy = from.top - to.top;
  
    const d = Math.sqrt(dx * dx + dy * dy);
  
    return {
      delay: 0,
      duration: Math.sqrt(d) * 120,
      easing: cubicOut,
      css: (t, u) => `transform: translate(${u * dx}px, ${u * dy}px) rotate(${t * 360}deg);`,
    };
  }
</script>

{#each list as item, index (item)}
  <div animate:whizz>{item}</div>
{/each}

A custom animation function can also return a tick function, which is called during the animation with the same t and u arguments.

If it's possible to use css instead of tick, do so — CSS animations can run off the main thread, preventing jank on slower devices.

App.svelte
<script>
  import { cubicOut } from 'svelte/easing';

  /**
   * @param {HTMLElement} node
   * @param {{ from: DOMRect; to: DOMRect }} states
   * @param {any} params
   */
  function whizz(node, { from, to }, params) {
    const dx = from.left - to.left;
    const dy = from.top - to.top;

    const d = Math.sqrt(dx * dx + dy * dy);

    return {
      delay: 0,
      duration: Math.sqrt(d) * 120,
      easing: cubicOut,
      tick: (t, u) => Object.assign(node.style, { color: t > 0.5 ? 'Pink' : 'Blue' })
    };
  }
</script>

{#each list as item, index (item)}
  <div animate:whizz>{item}</div>
{/each}
App.svelte
<script lang="ts">
  import { cubicOut } from 'svelte/easing';
  
  /**
   * @param {HTMLElement} node
   * @param {{ from: DOMRect; to: DOMRect }} states
   * @param {any} params
   */
  function whizz(node, { from, to }, params) {
    const dx = from.left - to.left;
    const dy = from.top - to.top;
  
    const d = Math.sqrt(dx * dx + dy * dy);
  
    return {
      delay: 0,
      duration: Math.sqrt(d) * 120,
      easing: cubicOut,
      tick: (t, u) => Object.assign(node.style, { color: t > 0.5 ? 'Pink' : 'Blue' }),
    };
  }
</script>

{#each list as item, index (item)}
  <div animate:whizz>{item}</div>
{/each}