Reaktive Preisumrechnung ohne Page Reload
Eine Preistabelle mit Umschalter zwischen monatlicher und jährlicher Abrechnung ist bei SaaS-Produkten und Abo-Modellen Standard, weil sie den Jahresrabatt direkt sichtbar macht und die Kaufentscheidung erleichtert. Mit Alpine.js entsteht eine Preistabelle, die Beträge reaktiv umrechnet, Rabatt-Badges anzeigt und den empfohlenen Tarif hervorhebt, komplett ohne Page Reload und ohne serverseitige Logik im Frontend.
Inhaltsverzeichnis
- 1. Warum eine Preistabelle mit Umschalter Conversion verbessert
- 2. Grundgerüst: Tarifdaten und Abrechnungsmodus im x-data State
- 3. Reaktive Preisberechnung mit computed Gettern
- 4. Rabatt-Badge: den Jahresvorteil sichtbar machen
- 5. Der Umschalter selbst: Toggle-Switch mit x-model
- 6. Empfohlenen Tarif hervorheben ohne Overengineering
- 7. Nutzerwahl über die Session hinweg merken
- 8. Barrierefreiheit: Umschalter als echte Radiogroup
- 9. Preistabelle Implementierungen im Vergleich
- 10. Zusammenfassung
- 11. FAQ
1. Warum eine Preistabelle mit Umschalter Conversion verbessert
Eine Preistabelle mit Monats-Jahres-Umschalter löst ein klassisches SaaS-Problem: Ein Jahresabo ist für den Anbieter wirtschaftlich attraktiver, weil es die Kündigungsrate senkt und Cashflow im Voraus sichert, aber der monatliche Preis wirkt psychologisch günstiger, weil die absolute Zahl kleiner erscheint. Eine Preistabelle mit Umschalter löst diesen Konflikt, indem sie beide Ansichten anbietet und den Wechsel zwischen ihnen als bewusste, informierte Entscheidung des Nutzers gestaltet.
Technisch ist eine gut gemachte Preistabelle mehr als zwei nebeneinander liegende HTML-Tabellen, von denen eine ausgeblendet wird. Der Umschalter sollte den Betrag live umrechnen, während der Nutzer den Toggle bedient, ohne Flackern und ohne dass der Nutzer eine neue Seite laden muss. Alpine.js eignet sich hierfür besonders gut, weil reaktive Preisberechnung genau die Stärke ist, für die Alpine ursprünglich entwickelt wurde: kleine, in sich geschlossene UI-Zustände, die direkt im Markup leben.
In den folgenden Abschnitten entsteht eine vollständige Preistabelle mit reaktiver Preisberechnung, Rabatt-Badge, hervorgehobenem empfohlenen Tarif und barrierefreiem Umschalter, direkt einsetzbar in jedem Hyvä-Theme oder jeder anderen Alpine-basierten Landingpage.
2. Grundgerüst: Tarifdaten und Abrechnungsmodus im x-data State
Bevor die eigentliche Implementierung beginnt, lohnt sich ein kurzer Blick auf die Zielgruppe: Eine Preistabelle für ein B2B-SaaS-Produkt mit wenigen, erklärungsbedürftigen Tarifen braucht andere Detailtiefe als eine Preistabelle für ein Consumer-Abo mit einfachen, selbsterklärenden Stufen.
Der State einer Preistabelle braucht zwei Kernbestandteile: ein Array mit den einzelnen Tarifen, jeweils mit Monats- und Jahrespreis, und einen booleschen Wert, der den aktuell gewählten Abrechnungsmodus hält. Der Jahrespreis wird dabei üblicherweise nicht als Zwölffaches des Monatspreises gespeichert, sondern als eigener, reduzierter Wert, weil genau in dieser Differenz der Rabatt liegt, den die Preistabelle sichtbar machen soll.
Jeder Tarif in der Datenstruktur bekommt zusätzlich ein Feature-Array und ein boolesches Flag für den empfohlenen Tarif. Diese flache, deklarative Struktur macht die Preistabelle leicht wartbar, weil neue Tarife oder geänderte Preise ausschließlich in den Daten angepasst werden müssen, ohne dass Markup oder Berechnungslogik angefasst werden.
// pricingTable.js — Alpine.data component with plan data
document.addEventListener('alpine:init', () => {
Alpine.data('pricingTable', () => ({
yearly: false, // false = monthly billing, true = yearly billing
plans: [
{ name: 'Starter', monthly: 19, yearly: 190, recommended: false,
features: ['1 Nutzer', '5 Projekte', 'E-Mail Support'] },
{ name: 'Pro', monthly: 49, yearly: 490, recommended: true,
features: ['10 Nutzer', 'Unbegrenzte Projekte', 'Priorisierter Support'] },
{ name: 'Business', monthly: 99, yearly: 990, recommended: false,
features: ['Unbegrenzte Nutzer', 'API-Zugang', 'Dedizierter Ansprechpartner'] }
]
}));
});
3. Reaktive Preisberechnung mit computed Gettern
Der angezeigte Preis pro Tarif darf nicht manuell bei jedem Toggle-Klick neu berechnet und in eine separate Variable geschrieben werden, das würde die Logik über mehrere Stellen verteilen und Fehlerquellen schaffen. Stattdessen nutzt eine gut gebaute Preistabelle JavaScript Getter-Funktionen innerhalb des x-data Objekts, die automatisch neu ausgewertet werden, sobald sich der Abrechnungsmodus ändert, weil Alpine die Abhängigkeiten reaktiv verfolgt.
Für die monatliche Darstellung des Jahrespreises, die viele Preistabellen zusätzlich anzeigen, um den Vergleich zu erleichtern, wird der Jahrespreis durch zwölf geteilt und gerundet. Diese abgeleitete Größe sollte niemals im Daten-Array selbst gespeichert werden, sondern immer als Getter berechnet, damit eine Preistabelle bei einer Preisänderung nur an einer einzigen Stelle angepasst werden muss.
Alpine.data('pricingTable', () => ({
yearly: false,
plans: [ /* ... plan data ... */ ],
// Getter recalculates automatically whenever `yearly` changes
priceFor(plan) {
return this.yearly ? plan.yearly : plan.monthly;
},
// Effective monthly rate when paying yearly — for comparison display
monthlyEquivalent(plan) {
return Math.round(plan.yearly / 12);
},
get billingLabel() {
return this.yearly ? 'pro Jahr' : 'pro Monat';
}
}));
Ein weiterer, leicht übersehener Punkt bei der Preisberechnung betrifft Rundungsfehler durch Fließkommazahlen. Eine Preistabelle, die Preise in Cent statt in ganzen Euro-Beträgen speichert, umgeht klassische JavaScript-Rundungsprobleme wie 0.1 plus 0.2 ergibt nicht exakt 0.3. Für die reine Anzeige gerundeter Beträge reicht Math.round() zwar aus, bei tatsächlichen Zahlungsvorgängen sollte die Rundung jedoch immer serverseitig in der Zahlungsabwicklung erfolgen, nicht im Frontend.
4. Rabatt-Badge: den Jahresvorteil sichtbar machen
Ein Rabatt-Badge, meist als kleines Label wie minus zwanzig Prozent direkt neben dem Umschalter platziert, ist ein zentrales Element jeder überzeugenden Preistabelle. Der Prozentsatz wird nicht hart codiert, sondern aus der Differenz zwischen zwölf Monatszahlungen und dem tatsächlichen Jahrespreis berechnet, damit das Badge automatisch korrekt bleibt, selbst wenn sich einzelne Tarifpreise später ändern.
Bei einer Preistabelle mit mehreren Tarifen, die unterschiedliche prozentuale Rabatte haben, kann entweder ein einzelnes, durchschnittliches Rabatt-Badge am Umschalter angezeigt werden, oder pro Tarif ein individuelles Badge direkt an der Preiszeile. Letzteres ist präziser, aber visuell unruhiger, die Entscheidung hängt vom jeweiligen Preismodell ab.
Alpine.data('pricingTable', () => ({
yearly: false,
plans: [ /* ... plan data ... */ ],
// Percentage saved by choosing yearly billing over 12x monthly
discountPercent(plan) {
const fullPrice = plan.monthly * 12;
const saved = fullPrice - plan.yearly;
return Math.round((saved / fullPrice) * 100);
}
}));
5. Der Umschalter selbst: Toggle-Switch mit x-model
Der Umschalter selbst ist im Kern eine Checkbox, die per x-model direkt an die yearly Eigenschaft im State gebunden wird. Visuell wird daraus meist ein Pill-förmiger Toggle-Switch mit zwei Labels links und rechts, wobei das aktive Label per dynamischer Klassenbindung optisch hervorgehoben wird. Diese Bindung ist der Kern der Reaktivität der gesamten Preistabelle, denn sobald sich yearly ändert, aktualisieren sich alle abhängigen Getter automatisch.
Ein Detail, das oft übersehen wird: Der komplette Toggle-Bereich, inklusive beider Labels, sollte klickbar sein, nicht nur der kleine visuelle Schalter selbst. Ein zu kleiner Klickbereich frustriert Nutzer auf mobilen Geräten besonders, wo Touch-Ziele mindestens 44 mal 44 Pixel groß sein sollten, damit die Preistabelle auch auf dem Smartphone komfortabel bedienbar bleibt.
<!-- Toggle switch bound directly to the `yearly` state property -->
<div x-data="pricingTable()" class="pricing-toggle">
<span :class="{ 'font-bold': !yearly }">Monatlich</span>
<button
type="button"
role="switch"
:aria-checked="yearly"
@click="yearly = !yearly"
class="toggle-switch"
>
<span class="toggle-knob" :class="{ 'translate-x-5': yearly }"></span>
</button>
<span :class="{ 'font-bold': yearly }">Jährlich</span>
<span class="discount-badge" x-text="`minus ${discountPercent(plans[1])}%`"></span>
</div>
6. Empfohlenen Tarif hervorheben ohne Overengineering
Manche Preistabellen gehen einen Schritt weiter und passen die Empfehlung dynamisch an das Verhalten des Nutzers an, etwa basierend auf der bereits genutzten Anzahl an Projekten oder Team-Mitgliedern in einem bestehenden Kundenkonto. Diese Personalisierung ist technisch aufwendiger, weil sie Daten aus dem Backend benötigt, kann aber die Trefferquote der Empfehlung deutlich erhöhen, verglichen mit einer statisch im Frontend hinterlegten Voreinstellung.
Für die meisten Projekte reicht jedoch die statische Variante völlig aus. Wichtig ist, dass die Entscheidung, welcher Tarif als empfohlen markiert wird, auf echten Nutzungsdaten oder zumindest auf einer bewussten Produktentscheidung beruht, und nicht willkürlich der teuerste oder der günstigste Tarif hervorgehoben wird, nur um kurzfristig den Umsatz pro Nutzer zu steigern. Eine Preistabelle, die Vertrauen aufbauen soll, muss die Empfehlung nachvollziehbar kommunizieren.
Die meisten Preistabellen heben einen mittleren Tarif visuell hervor, meist durch einen größeren Rahmen, einen Badge wie beliebteste Wahl und eine leicht angehobene Kartenposition. Dieses Muster funktioniert psychologisch, weil es dem Nutzer eine Entscheidung abnimmt, ohne sie ihm aufzuzwingen, und weil mittlere Optionen in Preisvergleichen generell überdurchschnittlich oft gewählt werden.
Technisch ist die Hervorhebung nur eine bedingte Klassenbindung basierend auf dem recommended Flag im Tarif-Objekt, kein separater Komponentenzweig. Wichtig ist, diese Hervorhebung nicht mit zu vielen zusätzlichen visuellen Elementen zu überladen, eine Preistabelle mit drei animierten Badges pro Tarif wirkt schnell überladen statt vertrauenswürdig.
7. Nutzerwahl über die Session hinweg merken
Wenn ein Nutzer bereits auf jährliche Abrechnung umgeschaltet hat und danach zu einer Detailseite eines einzelnen Tarifs navigiert, sollte diese Wahl beim Zurückkehren zur Preistabelle erhalten bleiben, statt wieder auf den Standardwert monatlich zurückzufallen. sessionStorage eignet sich hierfür besser als localStorage, weil die Präferenz meist nur für den aktuellen Besuch relevant ist und nicht über Wochen hinweg gespeichert werden muss.
Die Implementierung ist dieselbe Grundstruktur wie bei anderen Alpine-Komponenten mit Persistenz: beim init() wird der gespeicherte Wert gelesen und übernommen, bei jeder Änderung des Umschalters wird der neue Wert sofort zurückgeschrieben. So bleibt die Preistabelle konsistent, auch wenn der Nutzer zwischen mehreren Seiten hin und her navigiert.
Alpine.data('pricingTable', () => ({
yearly: false,
plans: [ /* ... plan data ... */ ],
init() {
// Restore the user's last choice for this browser session
const stored = sessionStorage.getItem('billing_mode');
if (stored === 'yearly') this.yearly = true;
this.$watch('yearly', (value) => {
sessionStorage.setItem('billing_mode', value ? 'yearly' : 'monthly');
});
}
}));
8. Barrierefreiheit: Umschalter als echte Radiogroup
Neben der reinen Persistenz der Nutzerwahl lohnt es sich, bei einer Preistabelle auch das Verhalten beim Zurück-Navigieren im Browser zu bedenken. Wird die Seite über den Zurück-Button des Browsers aus dem Bfcache wiederhergestellt, sollte der zuletzt gewählte Abrechnungsmodus ebenfalls erhalten bleiben, statt auf den Standardwert zurückzuspringen. Das lässt sich über das pageshow Ereignis prüfen, das bei einer Bfcache-Wiederherstellung mit event.persisted === true ausgelöst wird.
Ein visueller Toggle-Switch für eine Preistabelle sollte semantisch entweder als Checkbox mit klarem Label oder, bei mehr als zwei Optionen, als Radiogroup ausgezeichnet werden, nicht als reines <div> mit Klick-Handler. Screenreader-Nutzer müssen sowohl den aktuellen Zustand als auch die verfügbaren Optionen angesagt bekommen, was native Formularelemente automatisch leisten, während nachgebaute Div-Buttons zusätzliche ARIA-Attribute brauchen.
Zusätzlich sollte der Fokus-Ring beim Toggle sichtbar bleiben, viele Design-Systeme entfernen outline versehentlich komplett für ein saubereres visuelles Erscheinungsbild. Für eine Preistabelle, die auch mit Tastatur bedienbar sein soll, ist ein sichtbarer Fokus-Indikator auf dem Umschalter nicht optional, sondern eine WCAG-Grundanforderung.
Ein weiterer, oft unterschätzter Vorteil einer Preistabelle mit sauber getrennten Daten und Berechnungslogik zeigt sich beim A/B-Testen unterschiedlicher Preismodelle. Wenn Preise, Rabatte und Feature-Listen ausschließlich im Datenarray liegen, lässt sich eine Testvariante durch einfaches Austauschen der Werte erzeugen, ohne Markup oder Berechnungslogik zu duplizieren. Diese Trennung zahlt sich langfristig aus, sobald ein Produktteam beginnt, systematisch mit unterschiedlichen Preispunkten zu experimentieren.
9. Preistabelle Implementierungen im Vergleich
Für die Umsetzung einer Preistabelle mit Umschalter gibt es mehrere gängige Wege, mit deutlichen Unterschieden bei Interaktivität und Wartbarkeit.
| Ansatz | Interaktivität | Wartbarkeit | Ladezeit |
|---|---|---|---|
| Zwei statische Tabellen mit CSS-Toggle | Kein echter Umschalter | Doppelte Preisdaten im HTML | Sehr schnell |
| React Komponente mit useState | Voll reaktiv | Build-Step nötig | Zusätzliches JS-Bundle |
| Preistabelle mit Alpine.js | Voll reaktiv, minimal Code | Preisdaten zentral im x-data | Alpine bereits geladen |
| jQuery Toggle mit DOM-Manipulation | Funktioniert, aber unübersichtlich | Preise oft im Markup dupliziert | +30 KB jQuery Kern |
Der Vergleich zeigt: eine Preistabelle mit Alpine.js bietet dieselbe volle Reaktivität wie ein React-Ansatz, ohne einen zusätzlichen Build-Step oder ein separates JavaScript-Bundle zu benötigen, weil Alpine in einem Hyvä-Theme bereits geladen ist.
Mironsoft
Alpine.js Komponenten und Conversion Optimierung für Magento Hyvä Shops
Eine Preistabelle, die Jahresabos wirklich verkauft?
Wir bauen individuelle Alpine.js Komponenten für euren Hyvä-Shop, von Preistabellen über Testimonial Slider bis zu barrierefreien Formularen, performant und ohne unnötige Abhängigkeiten.
Komponenten-Audit
Bestehende Preistabellen auf Konversion und Barrierefreiheit prüfen
Individuelle Entwicklung
Preistabellen und weitere Marketing-Widgets mit Alpine.js
CRO-Beratung
Rabatt-Kommunikation und Tarif-Hervorhebung datenbasiert optimieren
10. Zusammenfassung
Eine gute Preistabelle mit Monats Jahres Umschalter braucht eine saubere Trennung von Tarifdaten und Berechnungslogik, reaktive Getter statt manueller Neuberechnung, ein automatisch korrektes Rabatt-Badge, einen dezent hervorgehobenen empfohlenen Tarif und einen barrierefrei bedienbaren Umschalter. Mit Alpine.js entsteht eine solche Preistabelle ohne Build-Step und ohne zusätzliches JavaScript-Bundle, direkt im bestehenden Hyvä-Setup.
Der entscheidende technische Vorteil liegt in den computed Gettern: Ändert sich der Abrechnungsmodus, aktualisieren sich alle abhängigen Werte automatisch, ohne dass Preise an mehreren Stellen manuell synchronisiert werden müssen. Wer die Nutzerwahl über die Session hinweg speichert und den Umschalter sauber mit nativen Formularelementen umsetzt, hat eine Preistabelle, die sowohl konvertiert als auch für alle Nutzergruppen zugänglich bleibt.
Preistabelle mit Monats Jahres Umschalter — Das Wichtigste auf einen Blick
Datenstruktur
Tarif-Array mit Monats- und Jahrespreis, Rabatt aus der Differenz berechnet.
Reaktivität
Getter-Methoden statt gespeicherter Werte, automatische Neuberechnung bei Toggle.
Persistenz
sessionStorage merkt die Nutzerwahl für den aktuellen Besuch.
Barrierefreiheit
Native Checkbox oder Radiogroup statt reinem Div, sichtbarer Fokus-Ring.