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)}, wheretooltip(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
...restto. 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;fromActionbridges them
Watch out for
- Creating listeners or library instances in an attachment without returning a cleanup function.