Under The Hood
frontend svelte Svelte 5 (actions)

use:action Directive: DOM Element Lifecycle Attachment

Last updated
Prerequisites:
Svelte element directives
Understanding of DOM lifecycle and cleanup
  • svelte
  • actions
  • use-directive
  • lifecycle
  • dom
  • directives

Read at your depth

The practical view

Actions run a function when an element is created: <div use:tooltip>Hover me</div>. The action receives the element (and an optional parameter): function tooltip(node, params) { ... return { update(params) {}, destroy() {} }; }. The action runs once on mount, update() runs when the parameter changes, and destroy() runs when the element is removed. Use actions for: focus management, click-outside handling, IntersectionObserver attachment, drag-and-drop, and any imperative DOM work that has a lifecycle. They are Svelte's replacement for the imperative parts of old Angular/React directives.

The same idea in other frameworks

react equivalent
React custom hooks + refs approximate actions, but with manual lifecycle management.

Legacy vs modern

Manual addEventListener + cleanup vs use:action

Attaching listeners in onMount requires manual removal in onDestroy and manual re-wiring on param change; the action lifecycle handles all three phases.

before → after
Manual listener lifecycle
onMount(() => {
  el.addEventListener('click', handler); // ❌ OLD
  return () => el.removeEventListener('click', handler); // ❌ OLD: manual
});
use:action lifecycle
function tooltip(node, params) {
  function onEnter() { params.show = true; }
  // ✅ NEW: attach in the action, clean up in destroy()
  node.addEventListener('mouseenter', onEnter);
  return { update(p) {}, destroy() { node.removeEventListener('mouseenter', onEnter); } };
}

Interview gotchas

Context

This tests the three-phase contract — interviewers want to confirm you know the action runs on mount, update() reacts to param changes, and destroy() runs on unmount, and how the reactive param tracking works.

The mechanical answer

The action function runs once when the element is inserted into the DOM, receiving the node and the current parameter value. If it returns an object with update(), Svelte calls update(newParams) whenever the action's parameter expression changes — the compiler wraps the update call in an effect that tracks any $state read inside the parameter expression, so reactive params trigger updates automatically. destroy() is called when the element is removed (during block teardown), letting the action release listeners/observers. The action does not auto-re-run its main body on param changes; only update() does. This contract makes actions safe for third-party integrations: setup in the body, reaction in update, teardown in destroy.

Trap

The naive answer is 'the action function re-runs when params change'. The body runs once; update() is the reactive phase. Another trap: forgetting destroy() — actions that attach listeners without cleanup leak them when the element unmounts, exactly the bug actions are meant to prevent. Candidates who enumerate the three phases and the effect-wrapped update mechanism show real understanding.

Under The Hood — a multi-depth technical interview hub.

Third-party licenses Press ⌘ K to search.