Svelte and SvelteKit course · Module 10: Actions
Mjolnir - Actions
In this lesson4
Welcome to the forge of Mjolnir! Thor's hammer has extraordinary powers. Actions in Svelte are just like those powers - they extend the capabilities of DOM elements.
What are Actions?
Actions are functions called when an element is mounted in the DOM. In Svelte 5 you put setup and cleanup in an $effect inside the action - the function returned from $effect runs when the element goes away:
<script>
function myAction(node) {
// node is the DOM element - it is already in the document
console.log('Element mounted:', node);
$effect(() => {
// setup goes here: listeners, libraries
return () => {
// and cleanup here, when the element goes away
console.log('Element unmounted');
};
});
}
</script>
<div use:myAction>
Element with action
</div>Actions do not run during server-side rendering (SSR) - they only start in the browser.
The classic API: an object with destroy
Before $effect existed, an action returned an object with a destroy() method (and update(), covered in the next lesson). This API still works and is handy in plain .js files - runes such as $effect are only available in .svelte and .svelte.js files.
// actions.js - a plain JS file, no runes
export function myAction(node) {
console.log('Element mounted:', node);
return {
destroy() {
console.log('Element unmounted');
}
};
}Example: Click Outside
// actions.js
export function clickOutside(node, callback) {
function handleClick(event) {
if (!node.contains(event.target)) {
callback();
}
}
document.addEventListener('click', handleClick, true);
return {
destroy() {
document.removeEventListener('click', handleClick, true);
}
};
}<script>
import { clickOutside } from './actions.js';
let isOpen = $state(false);
</script>
{#if isOpen}
<div use:clickOutside={() => isOpen = false} class="dropdown">
Click outside me to close
</div>
{/if}Actions with parameters
<script>
function tooltip(node, text) {
let tooltipEl;
function show() {
tooltipEl = document.createElement('div');
tooltipEl.className = 'tooltip';
tooltipEl.textContent = text;
document.body.appendChild(tooltipEl);
const rect = node.getBoundingClientRect();
tooltipEl.style.left = rect.left + 'px';
tooltipEl.style.top = rect.bottom + 5 + 'px';
}
function hide() {
tooltipEl?.remove();
}
$effect(() => {
node.addEventListener('mouseenter', show);
node.addEventListener('mouseleave', hide);
return () => {
hide();
node.removeEventListener('mouseenter', show);
node.removeEventListener('mouseleave', hide);
};
});
}
</script>
<button use:tooltip={'This is a button'}>
Hover over me
</button>Mjolnir grants power - Actions grant power to elements!
Code for this lesson: App.svelte
<script>
// Mjölnir - Powers assigned to elements
// use:action - additional DOM behaviors
// Simple action - focus on mount
function autofocus(node) {
node.focus();
// Cleanup in Svelte 5: the function returned from $effect
$effect(() => {
return () => console.log('Element removed');
});
}
// Action with parameters
function tooltip(node, text) {
let tooltipEl;
function show() {
tooltipEl = document.createElement('div');
tooltipEl.textContent = text;
tooltipEl.className = 'tooltip';
document.body.appendChild(tooltipEl);
const rect = node.getBoundingClientRect();
tooltipEl.style.left = rect.left + rect.width / 2 + 'px';
tooltipEl.style.top = rect.top - 10 + 'px';
}
function hide() {
if (tooltipEl) {
tooltipEl.remove();
tooltipEl = null;
}
}
node.addEventListener('mouseenter', show);
node.addEventListener('mouseleave', hide);
// Classic action API (still works): update and destroy
return {
// update - when the parameter changes
update(newText) {
text = newText;
},
// cleanup
destroy() {
hide();
node.removeEventListener('mouseenter', show);
node.removeEventListener('mouseleave', hide);
}
};
}
let tooltipText = $state('I am a tooltip!');
</script>
<main class="mjolnir">
<h1>Mjölnir - Actions</h1>
<p class="subtitle">use: - add powers to elements</p>
<section>
<h2>use:autofocus</h2>
<input use:autofocus placeholder="Auto-focus on start!" />
</section>
<section>
<h2>use:tooltip</h2>
<input
bind:value={tooltipText}
placeholder="Change tooltip text"
/>
<button use:tooltip={tooltipText}>
Hover over me!
</button>
</section>
</main>
<style>
.mjolnir {
font-family: 'Georgia', serif;
max-width: 500px;
margin: 0 auto;
padding: 2rem;
background: linear-gradient(135deg, #1a1a2e 0%, #16213e 100%);
color: #e8e8f7;
min-height: 100vh;
}
h1 {
color: #7cb3f0;
text-align: center;
}
.subtitle {
text-align: center;
color: #6a8ab9;
}
section {
background: rgba(124, 179, 240, 0.1);
border: 1px solid #7cb3f0;
border-radius: 8px;
padding: 1rem;
margin: 1rem 0;
}
h2 {
color: #7cb3f0;
font-size: 1rem;
margin-top: 0;
}
input {
width: 100%;
padding: 0.75rem;
background: rgba(0,0,0,0.3);
border: 1px solid #3a5f8a;
border-radius: 4px;
color: #e8e8f7;
margin-bottom: 0.5rem;
}
input:focus {
border-color: #7cb3f0;
outline: none;
}
button {
padding: 0.75rem 1.5rem;
background: #3a5f8a;
color: #e8e8f7;
border: 1px solid #7cb3f0;
border-radius: 4px;
cursor: pointer;
}
:global(.tooltip) {
position: fixed;
background: #ffd700;
color: #1a1a2e;
padding: 0.5rem 1rem;
border-radius: 4px;
font-size: 0.9rem;
transform: translate(-50%, -100%);
z-index: 1000;
pointer-events: none;
}
</style>Docs entries used in this lesson
Spotted a mistake in this lesson?
Check yourself
Answer the questions from this lesson. Pick an answer to see right away whether it is correct.
1. How do we use an action on a DOM element?
2. What can an action function return?