Skip to main content
Basic Svelte
Introduction
Reactivity
Props
Logic
Events
Bindings
Classes and styles
Actions
Transitions
Advanced Svelte
Advanced reactivity
Reusing content
Motion
Advanced bindings
Advanced transitions
Context API
Special elements
<script module>
Next steps
Basic SvelteKit
Introduction
Routing
Loading data
Headers and cookies
Shared modules
Forms
API routes
$app/state
Errors and redirects
Advanced SvelteKit
Hooks
Page options
Link options
Advanced routing
Advanced loading
Environment variables
Conclusion

Actions are essentially element-level lifecycle functions. They’re useful for things like:

  • interfacing with third-party libraries
  • lazy-loaded images
  • tooltips
  • adding custom event handlers

In this app, you can scribble on the <canvas>, and change colours and brush size via the menu. But if you open the menu and cycle through the options with the Tab key, you’ll soon find that the focus isn’t trapped inside the modal.

We can fix that with an action. Import trapFocus from actions.svelte.js...

App
<script>
	import Canvas from './Canvas.svelte';
	import { trapFocus } from './actions.svelte.js';

	const colors = ['red', 'orange', 'yellow', 'green', 'blue', 'indigo', 'violet', 'white', 'black'];

	let selected = $state(colors[0]);
	let size = $state(10);
	let showMenu = $state(true);
</script>
<script lang="ts">
	import Canvas from './Canvas.svelte';
	import { trapFocus } from './actions.svelte.js';

	const colors = ['red', 'orange', 'yellow', 'green', 'blue', 'indigo', 'violet', 'white', 'black'];

	let selected = $state(colors[0]);
	let size = $state(10);
	let showMenu = $state(true);
</script>

...then add it to the menu with the use: directive:

App
<div class="menu" use:trapFocus>

Let’s take a look at the trapFocus function in actions.svelte.js. An action function is called with a node — the <div class="menu"> in our case — when the node is mounted to the DOM. Inside the action, we have an effect.

First, we need to add an event listener that intercepts Tab key presses:

actions.svelte
$effect(() => {
	focusable()[0]?.focus();
	node.addEventListener('keydown', handleKeydown);
});

Second, we need to do some cleanup when the node is unmounted — removing the event listener, and restoring focus to where it was before the element mounted:

actions.svelte
$effect(() => {
	focusable()[0]?.focus();
	node.addEventListener('keydown', handleKeydown);

	return () => {
		node.removeEventListener('keydown', handleKeydown);
		previous?.focus();
	};
});

Now, when you open the menu, you can cycle through the options with the Tab key.

Edit this page on GitHub

<script>
import Canvas from './Canvas.svelte';

const colors = ['red', 'orange', 'yellow', 'green', 'blue', 'indigo', 'violet', 'white', 'black'];

let selected = $state(colors[0]);
let size = $state(10);
let showMenu = $state(true);
</script>

<div class="container">
<Canvas color={selected} size={size} />

{#if showMenu}
<div
role="presentation"
class="modal-background"
onclick={(event) => {
if (event.target === event.currentTarget) {
showMenu = false;
}
}}
onkeydown={(e) => {
if (e.key === 'Escape') {
showMenu = false;
}
}}
>
<div class="menu">
<div class="colors">
{#each colors as color}
<button
class="color"
aria-label={color}
aria-current={selected === color}
style="--color: {color}"

Error compiling component

WebAssembly.instantiateStreaming(): value type opcode @+13