Chapter 04 · Svelte Core Bestpractices
Subchapter 4.1
references/attach.mdMarkdown5 KBView on GitHub
Attachments are functions that run in an effect (opens in a new tab) when an element is mounted to the DOM or when state (opens in a new tab) read inside the function updates.
Optionally, they can return a function that is called before the attachment re-runs, or after the element is later removed from the DOM.
[!NOTE] Attachments are available in Svelte 5.29 and newer.
<!--- file: App.svelte --->
<script>
/** @type {import('svelte/attachments').Attachment} */
function myAttachment(element) {
console.log(element.nodeName); // 'DIV'
return () => {
console.log('cleaning up');
};
}
</script>
<div {@attach myAttachment}>...</div>An element can have any number of attachments.
A useful pattern is for a function, such as tooltip in this example, to return an attachment (demo:
<!--- file: App.svelte --->
<script>
import tippy from 'tippy.js';
let content = $state('Hello!');
/**
* @param {string} content
* @returns {import('svelte/attachments').Attachment}
*/
function tooltip(content) {
return (element) => {
const tooltip = tippy(element, { content });
return tooltip.destroy;
};
}
</script>
<input bind:value={content} />
<button {@attach tooltip(content)}>
Hover me
</button>Since the tooltip(content) expression runs inside an effect (opens in a new tab), the attachment will be destroyed and recreated whenever content changes. The same thing would happen for any state read inside the attachment function when it first runs. (If this isn’t what you want, see Controlling when attachments re-run.)
Attachments can also be created inline (demo:
<!--- file: App.svelte --->
<canvas
width={32}
height={32}
{@attach (canvas) => {
const context = canvas.getContext('2d');
$effect(() => {
context.fillStyle = color;
context.fillRect(0, 0, canvas.width, canvas.height);
});
}}
></canvas>[!NOTE] The nested effect runs whenever
colorchanges, while the outer effect (wherecanvas.getContext(...)is called) only runs once, since it doesn’t read any reactive state.
Falsy values like false or undefined are treated as no attachment, enabling conditional usage:
<div {@attach enabled && myAttachment}>...</div>When used on a component, {@attach ...} will create a prop whose key is a Symbol (opens in a new tab). If the component then spreads (opens in a new tab) props onto an element, the element will receive those attachments.
This allows you to create wrapper components that augment elements (demo:
<!--- file: Button.svelte --->
<script>
/** @type {import('svelte/elements').HTMLButtonAttributes} */
let { children, ...props } = $props();
</script>
<!-- `props` includes attachments -->
<button {...props}>
{@render children?.()}
</button><!--- file: App.svelte --->
<script>
import tippy from 'tippy.js';
import Button from './Button.svelte';
let content = $state('Hello!');
/**
* @param {string} content
* @returns {import('svelte/attachments').Attachment}
*/
function tooltip(content) {
return (element) => {
const tooltip = tippy(element, { content });
return tooltip.destroy;
};
}
</script>
<input bind:value={content} />
<Button {@attach tooltip(content)}>
Hover me
</Button>Attachments, unlike actions (opens in a new tab), are fully reactive: {@attach foo(bar)} will re-run on changes to foo or bar (or any state read inside foo):
// @errors: 7006 2304 2552
function foo(bar) {
return (node) => {
veryExpensiveSetupWork(node);
update(node, bar);
};
}In the rare case that this is a problem (for example, if foo does expensive and unavoidable setup work) consider passing the data inside a function and reading it in a child effect:
// @errors: 7006 2304 2552
function foo(+++getBar+++) {
return (node) => {
veryExpensiveSetupWork(node);
+++ $effect(() => {
update(node, getBar());
});+++
}
}To add attachments to an object that will be spread onto a component or element, use createAttachmentKey (opens in a new tab).
If you’re using a library that only provides actions, you can convert them to attachments with fromAction (opens in a new tab), allowing you to (for example) use them with components.