Markup & Ausdrücke
Das Markup einer Komponente ist HTML, angereichert um JavaScript-Ausdrücke in geschweiften Klammern. Attribute, Shorthand, Spread — und ein wichtiger Sicherheitshinweis zum @html-Tag.
Ausdrücke in geschweiften Klammern
Der Kern des Svelte-Markups ist einfach: Alles, was zwischen { } steht, ist
ein JavaScript-Ausdruck. Svelte wertet ihn aus und rendert das Ergebnis als
Text. Das ist nicht auf Variablen beschränkt — jeder gültige Ausdruck funktioniert.
<script lang="ts">
let name = $state('Anna');
let punkte = $state(42);
</script>
<!-- Jeder JS-Ausdruck in { } wird ausgewertet und gerendert: -->
<h1>Hallo {name}</h1>
<p>Punkte: {punkte}</p>
<p>Verdoppelt: {punkte * 2}</p>
<p>Länge des Namens: {name.length}</p>
<p>Groß: {name.toUpperCase()}</p>ℹ️ Ausdrücke, keine Anweisungen
In die Klammern gehört ein Ausdruck (etwas, das einen Wert ergibt), keine
Anweisung. {punkte * 2} geht, {if (...) ...} nicht — für
Bedingungen und Schleifen gibt es eigene Block-Tags, die wir uns in Modul M4 ansehen.
Ausdrücke in Attributen
Dieselben geschweiften Klammern funktionieren in Attributwerten. Du kannst ganze Attribute aus Ausdrücken setzen oder Text und Ausdruck innerhalb eines Strings mischen:
<script lang="ts">
let url = $state('https://svelte.dev');
let aktiv = $state(true);
</script>
<!-- Ausdrücke funktionieren auch in Attributen: -->
<a href={url}>Zur Doku</a>
<!-- Mischung aus Text und Ausdruck via String: -->
<img src="/bilder/{name}.png" alt="Bild von {name}" />
<!-- Boolesche Attribute: bei false wird das Attribut weggelassen -->
<button disabled={!aktiv}>Absenden</button>Ein praktisches Detail: Ist der Wert eines booleschen Attributs wie disabled gleich false, lässt Svelte das Attribut komplett weg — genau das Verhalten, das
HTML erwartet.
Attribut-Shorthand
Wenn ein Attribut denselben Namen hat wie die Variable, die es füllt, wird die Wiederholung
schnell lästig (src={src}). Dafür gibt es eine Kurzform: Schreib einfach {src}.
<script lang="ts">
let src = $state('/logo.svg');
let value = $state('Hallo');
</script>
<!-- Lang: Attributname = Variablenname -->
<img src={src} alt="Logo" />
<input value={value} />
<!-- Shorthand: {src} ist die Kurzform von src={src} -->
<img {src} alt="Logo" />
<input {value} />Spread: {...obj}
Hast du ein ganzes Objekt voller Attributwerte, musst du sie nicht einzeln durchreichen. Der Spread-Operator verteilt alle Felder eines Objekts als Attribute auf das Element — sehr nützlich, um Attribute an eine Kind-Komponente „durchzureichen":
<script lang="ts">
const attrs = {
type: 'text',
placeholder: 'Dein Name',
maxlength: 20,
class: 'eingabe'
};
</script>
<!-- Spread verteilt alle Objekt-Felder als Attribute: -->
<input {...attrs} />
<!-- Entspricht: -->
<input type="text" placeholder="Dein Name" maxlength={20} class="eingabe" />
<!-- Kombinierbar; spätere Attribute gewinnen: -->
<input {...attrs} placeholder="Überschrieben" />Du kannst Spread und einzelne Attribute kombinieren. Bei gleichem Namen gewinnt das, was später steht.
Kommentare
Kommentare im Markup sind schlicht HTML-Kommentare. Sie bleiben Teil des Markups (nicht des <script>) und dürfen mehrzeilig sein:
<!-- Ein ganz normaler HTML-Kommentar. -->
<h1>{titel}</h1>
<!--
Mehrzeilige Kommentare gehen auch.
Sie bleiben im Markup, nicht im <script>.
-->Text vs. HTML: Escaping & {@html}
Ein {ausdruck} gibt seinen Wert immer als Text aus. Enthält
der Wert HTML-Tags, werden sie escapt und erscheinen als sichtbarer Text —
nicht als Markup. Das ist die sichere Standardeinstellung.
Willst du eine Zeichenkette bewusst als echtes HTML rendern, brauchst du das Spezial-Tag {@html ...}:
<script lang="ts">
let text = $state('Hallo <strong>Welt</strong>');
</script>
<!-- Standard: Text wird ESCAPT, Tags erscheinen als Text -->
<p>{text}</p>
<!-- Ausgabe: Hallo <strong>Welt</strong> (als sichtbarer Text) -->
<!-- {@html ...}: Inhalt wird als echtes HTML gerendert -->
<p>{@html text}</p>
<!-- Ausgabe: Hallo Welt (mit fettem "Welt") -->🚫 Sicherheitshinweis: XSS-Gefahr
{@html} umgeht das Escaping vollständig. Gibst du damit Inhalte aus, die
von Nutzern stammen (Kommentare, Formulareingaben, API-Daten), öffnest du Tür und Tor für Cross-Site-Scripting (XSS). Nutze {@html} nur für Inhalte,
denen du vollständig vertraust — oder bereinige sie vorher mit einem Sanitizer wie
DOMPurify. Die weiteren Spezial-Tags behandeln wir in Modul M4.
🛤️ Rails-Vergleich: <%= %>, h() und html_safe
Das kennst du aus ERB: <%= @name %> escapt seinen Inhalt automatisch —
genau wie {ausdruck} in Svelte. Der Helper h() escapt explizit,
und mit raw bzw. html_safe umgehst du das Escaping bewusst.
<%# ERB: <%= %> gibt escapten Text aus (wie {ausdruck} in Svelte) %>
<h1>Hallo <%= @name %></h1>
<p>Punkte: <%= @punkte * 2 %></p>
<%# Escaping ist auch hier Standard. Zum bewussten Umgehen: %>
<%= raw @html_inhalt %> <%# oder @html_inhalt.html_safe %>
<%# Rails-Helper h() escapt explizit: %>
<%= h @user_input %>{@html} ist damit das direkte Gegenstück zu Rails' raw/html_safe — mit derselben Verantwortung: Was du hindurchlässt,
muss vertrauenswürdig sein.
Live ausprobieren
Unten steht ein Objekt produkt im $state. Die Anzeige rechnet und
formatiert live mit — ändere die Felder und beobachte jeden Ausdruck im Markup:
Svelte-Tasse
Preis: 12.00 €
Status: ✅ Auf Lager
Doppelpack: 24.00 €
Der Code hinter der Anzeige — nur Ausdrücke, keine Block-Logik:
<script lang="ts">
let produkt = $state({
name: 'Svelte-Tasse',
preis: 12,
verfuegbar: true
});
</script>
<article>
<h3>{produkt.name}</h3>
<p>Preis: {produkt.preis.toFixed(2)} €</p>
<p>Status: {produkt.verfuegbar ? 'Auf Lager' : 'Ausverkauft'}</p>
<p>Doppelpack: {(produkt.preis * 2).toFixed(2)} €</p>
</article>Zusammenfassung
💡 Das Wichtigste
{ausdruck}rendert jeden JS-Ausdruck als escapten Text.- Ausdrücke funktionieren auch in Attributen;
falselässt Attribute weg. - Shorthand:
{src}stattsrc={src}. - Spread
{...obj}verteilt Objekt-Felder als Attribute (später gewinnt). {@html}rendert rohes HTML — nur bei vertrauenswürdigem Inhalt (XSS!).
🎯 Probier es selbst
Erweitere die Produkt-Demo um ein Feld rabatt (0–50, als Slider) im $state. Zeige im Markup den reduzierten Preis mit einem Ausdruck an, z. B. {(produkt.preis * (1 - rabatt / 100)).toFixed(2)} €. Alles bleibt ein
einziger Ausdruck — ganz ohne {#if}.