Eine Action ist im einfachsten Fall eine Funktion. Aber je nach Anforderung sieht ihr innerer Aufbau unterschiedlich aus — vom kleinen Mount-Listener bis hin zur reaktiv parametrisierten DOM-Erweiterung. Dieser Artikel zeigt die drei wichtigsten Schreibweisen, wann du welche nimmst und wie du Actions in TypeScript-Projekten korrekt typisierst.

Form 1 – Moderne Variante mit $effect

Die heute empfohlene Form: Setup und Cleanup laufen in einem $effect innerhalb der Action.

ts src/lib/actions/auto-focus.svelte.ts
export function autoFocus(node: HTMLElement) {
    $effect(() => {
        node.focus();
    });
}

Was passiert hier:

  • Die Action ist eine normale Funktion, die den DOM-Knoten als Argument bekommt.
  • Innerhalb der Action öffnet $effect einen Reactive-Scope, der mit dem Element-Lifecycle gebunden ist.
  • Wenn ein Cleanup nötig ist (z. B. Listener entfernen), kannst du eine Funktion aus dem $effect zurückgeben.
ts Mit Cleanup
export function tooltip(node: HTMLElement, text: string) {
    $effect(() => {
        const handler = () => console.log(text);
        node.addEventListener('mouseenter', handler);
        return () => node.removeEventListener('mouseenter', handler);
    });
}

Diese Form hat zwei wichtige Eigenschaften:

  • Setup und Cleanup stehen eng beieinander (gleicher $effect-Block) — keine Verteilung über die ganze Funktion.
  • Wenn die Action Parameter empfängt, die sich ändern können, kann der $effect darauf reagieren (siehe Action-Parameter).

Form 2 – Klassisches Pattern mit update und destroy

Vor der Runes-Ära war das Standard: Die Action gibt ein Objekt mit optionalen update- und destroy-Methoden zurück.

ts Klassische Form
export function tooltip(node: HTMLElement, text: string) {
    let currentText = text;

    function show() {
        console.log('Tooltip:', currentText);
    }

    node.addEventListener('mouseenter', show);

    return {
        update(newText: string) {
            currentText = newText;
        },
        destroy() {
            node.removeEventListener('mouseenter', show);
        },
    };
}

So funktioniert das:

  • update(newParam) wird von Svelte aufgerufen, wenn sich der use:-Parameter ändert.
  • destroy() wird beim Unmount aufgerufen.

Diese Form funktioniert in Svelte 5 weiterhin, ist aber etwas länger und hat keinen Reactive-Scope. Für neue Actions ist Form 1 (mit $effect) idiomatischer.

Form 3 – Inline-Attachments

Seit Svelte 5.29 gibt es eine neue Variante: das {@attach fn}-Tag. Statt eine Action über use: an ein Element zu hängen, hängst du sie direkt im Markup als Attachment ein:

svelte Mit @attach
<script>
    function focus(node) {
        $effect(() => {
            node.focus();
        });
    }
</script>

<input {@attach focus} />

Vorteile von Attachments:

  • Erlaubt inline-definierte Logik direkt im Markup.
  • Funktioniert mit höher-ordentlichen Funktionen, ohne Boilerplate.
  • Reagiert besser auf reaktive Änderungen.

Wann nimmst du was?

SituationEmpfehlung
Wiederverwendbares Verhalten als externes Moduluse: Action
Einmal-Logik direkt im Markup{@attach}
Bestandscode aus Svelte 4use: Action
Library-API mit Action-Typeuse: Action

Beide existieren parallel und werden weiter unterstützt. Wer in einem neuen Projekt startet, kann mit Attachments oft kompakter arbeiten — wer eine wiederverwendbare Library aufbaut, bleibt bei Actions.

TypeScript: der Action-Type

Aus dem Modul svelte/action kommt ein generischer Helfer, mit dem sich Actions sauber typisieren lassen:

ts Typisierte Action
import type { Action } from 'svelte/action';

type TooltipParams = { text: string; placement?: 'top' | 'bottom' };

export const tooltip: Action<HTMLElement, TooltipParams> = (node, params) => {
    $effect(() => {
        const handler = () => console.log(params.text);
        node.addEventListener('mouseenter', handler);
        return () => node.removeEventListener('mouseenter', handler);
    });
};

Der Type hat drei Generics:

  • NodeType — auf welchen Elementtyp passt die Action (z. B. HTMLElement, HTMLInputElement, HTMLDivElement).
  • ParameterType — wie das Parameter-Objekt aussieht.
  • Events — selten genutzt; zum Deklarieren von Custom-DOM-Events, die die Action auf dem Element auslöst.

In den meisten Fällen reichen die ersten beiden Generics.

Wenn keine Parameter

ts Aktion ohne Parameter
import type { Action } from 'svelte/action';

export const autoFocus: Action<HTMLElement> = (node) => {
    $effect(() => node.focus());
};

Element-spezifische Action

ts Nur auf input
import type { Action } from 'svelte/action';

export const selectOnFocus: Action<HTMLInputElement> = (node) => {
    $effect(() => {
        const handler = () => node.select();
        node.addEventListener('focus', handler);
        return () => node.removeEventListener('focus', handler);
    });
};

<button use:selectOnFocus> würde der TypeScript-Compiler als Fehler markieren — die Action akzeptiert nur <input>-Elemente.

Aufbau einer typischen Action

Ein bewährtes Schema für Actions, die mehrfach Listener oder Observer setzen:

ts Strukturiertes Skelett
import type { Action } from 'svelte/action';

type Params = {
    // ...Parameter-Felder
};

export const meineAction: Action<HTMLElement, Params> = (node, params) => {
    // 1. Optional: Initial-Setup, das nicht reaktiv sein soll
    //    (DOM-Attribute setzen, Klassen ergänzen)

    // 2. Reaktiver Bereich
    $effect(() => {
        // Setup mit aktuellen params
        const cleanup = () => {
            // Listener/Observer entfernen
        };

        // ...Listener registrieren

        return cleanup;
    });
};

Wenn eine Action keine reaktiven Parameter hat, reicht oft auch ein Top-Level-$effect. Wenn sie mit Parametern arbeitet, die sich ändern können, ist diese Struktur die saubere Wahl.

Wann .svelte.ts für Actions?

Damit $effect innerhalb einer Action funktioniert, muss die Datei runes-fähig sein. Das heißt:

  • In einer .svelte-Datei (z. B. lokal definierte Action) — funktioniert.
  • In einer .svelte.ts/.svelte.js-Datei — funktioniert.
  • In einer normalen .ts/.js-Datei$effect ist nicht verfügbar; dann nur die klassische Form mit update/destroy möglich.

Empfehlung: Actions, die $effect nutzen, gehören in .svelte.ts-Dateien.

Häufige Stolperfallen

$effect in einer normalen .ts-Datei.

Dann bleibt nur die klassische Form mit update/destroy. Oder Datei umbenennen auf .svelte.ts.

Klassische Form mit reaktiven Parametern erwartet.

update(newParam) wird zwar bei Parameter-Wechseln aufgerufen, ist aber nicht reactive im Runes-Sinn. Wer interne $state-Werte aus dem Parameter ableiten will, fährt mit der $effect-Variante besser.

Cleanup vergessen.

Egal welche Form: Wer Listener registriert und nicht entfernt, leckt sie. In $effect ist Cleanup eine Rückgabefunktion, in der klassischen Form eine destroy-Methode.

Action für Komponenten verwenden wollen.

use: funktioniert nur an HTML-Elementen, nicht an Komponenten-Tags. Wer Verhalten an eine Komponente hängen will, gibt es als Prop weiter oder kapselt die Logik in der Komponente selbst.

Mehrere $effect ohne Trennung.

Wenn dein Code mehrere unabhängige Listener registriert, mach mehrere $effect-Blöcke daraus — jeder mit eigener Aufgabe. Das hält Cleanup übersichtlich.

Weiterführende Ressourcen

Externe Quellen

/ Weiter

Zurück zu Actions

Zur Übersicht