Magento 2 Experten — Hyvä Theme, Tailwind CSS & SEO aus einer Hand ›

Customizing the Product Detail Page in Hyvä (Custom Options, Gallery as an Example)

Customizing the Product Detail Page in Hyvä (Custom Options, Gallery as an Example)

~8 Min. Lesezeit Zuletzt aktualisiert am August 9, 2026

After our own team page module, let's look at how the same knowledge (Layout XML, ViewModel, Alpine, CSP) applies to an existing Magento default page - the product detail page. Instead of a dedicated module, we mostly work here with targeted Layout XML adjustments and template overrides (chapter 10).

Hyvä's default product gallery is itself an Alpine component: main image, thumbnails, zoom, and lightbox view all run entirely client-side, with the image URLs already embedded server-side (via a ViewModel) as JSON - the same pattern as the mini cart from chapter 15 and the team filtering from chapter 21.

<div x-data="initGallery(<?= /* @noEscape */ $json->serialize($galleryImages) ?>)">
    <img :src="activeImage.full" class="w-full rounded-xl">
    <div class="mt-4 flex gap-2">
        <template x-for="image in images" :key="image.id">
            <button @click="activeImage = image">
                <img :src="image.thumbnail" class="h-16 w-16 rounded-lg object-cover">
            </button>
        </template>
    </div>
</div>

If you want to customize the gallery (e.g. different zoom behavior or a different thumbnail layout), you typically override the gallery template in your own theme folder (chapter 10), rather than rebuilding the entire product page.

Custom options: form fields with x-model

Products with custom options (text, select, file upload) use exactly the x-model pattern from chapter 14 for live price calculation: every option change instantly updates the displayed total price, with no page reload.

<div x-data="{ selectedOption: null, basePrice: 49.90 }">
    <select x-model="selectedOption">
        <option value="">Please choose</option>
        <option value="engraving">With engraving (+€9.90)</option>
    </select>
    <p>Total price: <span x-text="(basePrice + (selectedOption === 'engraving' ? 9.9 : 0)).toFixed(2)"></span> €</p>
</div>

Tipp: For more complex price calculations with many options, a named Alpine.data() component is worth it - just like the accordion example from chapter 16 - instead of a growing inline object.

What shouldn't be touched

For all the customization freedom: core functions like cart validation, price calculation with taxes and discounts, or stock checks stay server-side Magento logic (backend validation when adding to cart). The client-side Alpine price preview is only a display optimization for better UX - it doesn't replace server-side validation.

Achtung: If you implement custom price calculation purely client-side in Alpine and rely on it, you're opening a security hole: an attacker can manipulate the final price via browser dev tools before the request reaches the server. The server-side price calculation in Magento's custom options processing remains the authoritative source - Alpine only shows a preview.