Attachments and actions

Svelte · Advanced · 12 min

Integrating DOM libraries and element behaviours cleanly needs a hook that runs when an element is created.

Sometimes you need code that runs when a particular element is mounted: initialise a tooltip library, auto-focus, observe size or intersection, add a click-outside handler. Svelte 5.29+ provides attachments:

<div {@attach (node) => { console.log(node); return () => console.log('cleanup'); }}>

An attachment is a function that receives the element and may return a cleanup function. Key properties:

  • It runs inside an effect: if it reads reactive state, it is torn down and re-run when that state changes. Use a factory that takes arguments: {@attach tooltip(text)}, where tooltip(text) returns the attachment.
  • Attachments can be placed on components too: they are passed as props (with a symbol key) and spread onto the element the component forwards ...rest to. This makes wrapper components transparent.
  • Multiple attachments per element are fine; they can be conditional ({@attach enabled && fn}).

Before attachments, the same job was done by actions: use:action={param} with a function (node, param) => ({ update, destroy }). Actions still work and many libraries ship them; fromAction from svelte/attachments converts one into an attachment.

Choose attachments for new code because they compose with props and re-run automatically. Keep heavy work out of them, and always clean up listeners and library instances.

Example

<script>
  let text = $state('Hello!');
  let open = $state(true);

  // factory: returns an attachment that depends on `content`
  function tooltip(content) {
    return (node) => {
      const tip = document.createElement('span');
      tip.className = 'tip';
      tip.textContent = content;
      node.append(tip);
      return () => tip.remove();          // cleanup on re-run or unmount
    };
  }

  function clickOutside(node) {
    const handler = (e) => { if (!node.contains(e.target)) open = false; };
    document.addEventListener('click', handler, true);
    return () => document.removeEventListener('click', handler, true);
  }
</script>

<input bind:value={text} />
<button {@attach tooltip(text)}>hover me</button>   <!-- re-runs when text changes -->
{#if open}<div class="menu" {@attach clickOutside}>Menu</div>{/if}

Remember

  • {@attach fn} runs fn(element) on mount; a returned function cleans up
  • Attachments re-run when reactive state they read changes
  • Actions (use:) are the older equivalent; fromAction bridges them

Watch out for

  • Creating listeners or library instances in an attachment without returning a cleanup function.

Check yourself

  1. An attachment created by `tooltip(text)` reads `text`, which is $state. What happens when text changes?

Sign in to answer, see the explanations, and get this lesson back as a quick review later.

Official docs: svelte.dev/docs/svelte/@attach

Builds on

Unlocks

Nothing builds on this yet.