Twig-Overrides
Guppy verfolgt einen komponentenbasierten Templating-Ansatz: dynamische, modulare Templates, die per theme.json gesteuert werden und sich saubere von Shopwares Defaults trennen lassen. Ziel ist Updatefähigkeit ohne Patches im Kerncode.
Warum dieser Ansatz?
- Maximale Anpassbarkeit: Konfigurations-Werte statt Code-Änderungen.
- Modularität: Komponenten (Footer, Header, Widgets) als eigenständige Templates.
- Upgrade-Kompatibilität: Override via
sw_extends, kein Eingriff im Kern. - Barrierefreiheit: modulare Templates erlauben gezielte A11y-Anpassungen.
Theme-Integration via theme.json
Felder aus theme.json werden in Twig per theme_config('feld-name') gelesen. Beispiel: Footer-Variante.
{# src/Resources/views/storefront/base.html.twig #}
{% if theme_config('guppy-footer') != "default" %}
{% set configFooter = theme_config('guppy-footer') %}
{% endif %}
{% block base_footer %}
<footer class="footer-main{% if configFooter %} footer-main-{{ configFooter }} pt-md-3 pt-4{% endif %}">
{% block base_footer_inner %}
{% if configFooter %}
{% sw_include '@Storefront/storefront/layout/footer/footer-' ~ configFooter ~ '.html.twig' %}
{% else %}
{{ parent() }}
{% endif %}
{% endblock %}
</footer>
{% endblock %}Passendes theme.json-Feld:
{
"guppy-footer": {
"label": { "en-GB": "Footer variant", "de-DE": "Footer Variante" },
"type": "text",
"value": "guppy-default",
"custom": {
"componentName": "sw-single-select",
"options": [
{ "value": "default", "label": { "de-DE": "Shopware Standard" } },
{ "value": "guppy-default", "label": { "de-DE": "Guppy Standard" } }
]
},
"editable": true,
"tab": "footer",
"block": "layoutFooter",
"order": 100
}
}Zugehöriges Variant-Template:
{# src/Resources/views/storefront/layout/footer/footer-guppy-default.html.twig #}
{% sw_extends '@Storefront/storefront/layout/footer/footer.html.twig' %}
{# Hier Inhalt #}Template-Varianten-System
Guppy implementiert das Varianten-Pattern durchgängig. Beispiele:
Header
{% if theme_config('guppy-header') != "default" %}
{% set configHeader = theme_config('guppy-header') %}
{% endif %}
{% block base_header %}
<header class="header-main{% if configHeader %} header-variant-{{ configHeader }}{% endif %}">
{% block base_header_inner %}
{% if configHeader %}
{% sw_include '@Storefront/storefront/layout/header/header-' ~ configHeader ~ '.html.twig' %}
{% else %}
{{ parent() }}
{% endif %}
{% endblock %}
</header>
{% endblock %}Verfügbare Varianten: default, compact (Guppy Standard), extended (mit Top-Bar), simple.
Navigation
{% if theme_config('guppy-navigation') != "content" %}
{% set navigationStyle = theme_config('guppy-navigation') %}
{% endif %}
{% block base_navigation %}
<nav class="nav-main{% if navigationStyle %} nav-{{ navigationStyle }}{% endif %}">
{% sw_include '@Storefront/storefront/layout/navbar/' ~ (navigationStyle ?: 'content') ~ '.html.twig' %}
</nav>
{% endblock %}Verfügbare Varianten: content, content-compact.
Productcards
{% if theme_config('guppy-productcard-config') %}
{% set boxDesign = theme_config('guppy-productcard-config') %}
{% endif %}
<article class="card product-box box-{{ layout }}{% if boxDesign %} box-{{ boxDesign }}{% endif %}">
<!-- Produktkarten-Inhalt -->
</article>Verfügbare Varianten: default, guppy-default (barrierefrei). Zusätzliche Felder:
"guppy-productcard-protective-frame": true,
"guppy-productcard-image-object-fit": "contain"Login & Register / Checkout
{% if theme_config('guppy-login-register') == "compact" %}
{% set loginStyle = "compact" %}
{% endif %}
{% if theme_config('guppy-checkout') == "compact" %}
{% set checkoutStyle = "compact" %}
{% endif %}Template-Struktur
Ausgewählte Pfade unter src/Resources/views/storefront/:
storefront/
├── base.html.twig # Skip-Links + Layout
├── layout/
│ ├── header.html.twig
│ ├── header/
│ │ ├── header-compact.html.twig
│ │ ├── header-extended.html.twig
│ │ ├── header-simple.html.twig
│ │ ├── logo.html.twig
│ │ ├── search.html.twig
│ │ └── top-bar-extended.html.twig
│ ├── footer.html.twig
│ ├── footer/
│ │ └── footer-guppy-default.html.twig
│ ├── navbar/
│ │ ├── navbar.html.twig
│ │ ├── content.html.twig
│ │ └── content-compact.html.twig
│ ├── breadcrumb.html.twig
│ └── sidebar/
│ └── category-navigation-onelevel.html.twig
├── component/
│ ├── product/
│ ├── buy-widget/
│ ├── account/
│ ├── checkout/
│ ├── line-item/
│ ├── address/
│ ├── listing/
│ └── delivery-information.html.twig
├── element/ # CMS-Elemente
├── page/
│ ├── account/
│ ├── checkout/
│ ├── content/
│ └── product-detail/
├── section/
│ └── cms-section-sidebar.html.twig
├── block/
└── utilities/
├── alert.html.twig
├── icon.html.twig
├── offcanvas.html.twig
└── thumbnail.html.twigVollständige Liste der überschriebenen Templates: Twig-Blöcke.
Custom Twig-Extensions
categoryOneLevel($navigationTree, $currentCategoryId)
Erlaubt einstufige Kategorie-Navigationen:
{% set navigationData = categoryOneLevel(navigationTree, currentCategoryId) %}
{% for category in navigationData %}
<a href="{{ category.url }}" class="nav-link">
{{ category.name }}
</a>
{% endfor %}Implementierung:
// src/Twig/CategoryOneLevelExtension.php
class CategoryOneLevelExtension extends AbstractExtension
{
public function getFunctions(): array
{
return [
new TwigFunction('categoryOneLevel', [$this, 'categoryOneLevel'])
];
}
public function categoryOneLevel($navigationTree, $currentCategoryId)
{
// einstufige Kategorienavigation
}
}Barrierefreiheit
Skip-Links
Konfigurierbar per theme_config:
{# base.html.twig #}
{% block base_body_inner %}
{% if theme_config('guppy-header-skip-to-main-content') %}
<a href="#content-main" class="skip-link">
{{ 'skipLink.toMainContent'|trans|sw_sanitize }}
</a>
{% endif %}
{% if theme_config('guppy-header-skip-to-main-nav') %}
<a href="#main-navigation" class="skip-link">
{{ 'skipLink.toMainNavigation'|trans|sw_sanitize }}
</a>
{% endif %}
{% if theme_config('guppy-header-skip-to-search') %}
<a href="#search" class="skip-link">
{{ 'skipLink.toSearch'|trans|sw_sanitize }}
</a>
{% endif %}
{{ parent() }}
{% endblock %}Verfügbare Skip-Link-Felder:
| Feld | Wirkung |
|---|---|
guppy-header-skip-to-main-content | Sprung zum Hauptinhalt |
guppy-header-skip-to-main-nav | Sprung zur Hauptnavigation |
guppy-header-skip-to-search | Sprung zur Suche |
USP-Banner
{# layout/_includes/usp-banner.html.twig #}
{% if theme_config('guppy-usp-active') %}
<div class="usp-banner" data-usp-banner-plugin="true">
{% if theme_config('guppy-usp-layout') == 'benefits' %}
{% for i in 1..4 %}
{% set benefit = theme_config('guppy-usp-layout-benefit' ~ i) %}
{% if benefit %}
<div class="usp-item">{{ benefit }}</div>
{% endif %}
{% endfor %}
{% else %}
<div class="usp-item">
<a href="{{ theme_config('guppy-usp-layout-link') }}"
{% if theme_config('guppy-usp-layout-newTab') %}target="_blank"{% endif %}>
{{ theme_config('guppy-usp-layout-text') }}
</a>
</div>
{% endif %}
</div>
{% endif %}Praktische Implementierung
Neuen Header-Typ
- Template anlegen:
{# src/Resources/views/storefront/layout/header/header-custom.html.twig #}
{% sw_extends '@Storefront/storefront/layout/header/header.html.twig' %}
{% block layout_header_inner %}
<div class="header-custom">
{# Custom Header-Inhalt #}
</div>
{% endblock %}- Theme-Konfiguration erweitern:
"guppy-header": {
"custom": {
"options": [
{ "value": "custom", "label": { "de-DE": "Custom Header" } }
]
}
}- SCSS-Styles ergänzen:
.header-variant-custom {
.header-custom {
// Custom Styles
}
}Neue Productcard-Variante
{# src/Resources/views/storefront/component/product/card/box-custom.html.twig #}
{% sw_extends '@Storefront/storefront/component/product/card/box-standard.html.twig' %}
{% block component_product_box %}
<div class="product-box box-custom">
{# Custom Inhalt #}
</div>
{% endblock %}"guppy-productcard-config": {
"custom": {
"options": [
{ "value": "custom", "label": { "de-DE": "Custom Produktkarte" } }
]
}
}Alert-Varianten konfigurieren
{# utilities/alert.html.twig #}
{% set alertClass = 'alert-' ~ type %}
{% if theme_config('guppy-alert-alert-outline-active') %}
{% set alertClass = alertClass ~ ' alert-outline' %}
{% endif %}
<div class="alert {{ alertClass }}" role="alert">
{{ message }}
</div>"guppy-alert-color-success": "#D3F2E7",
"guppy-alert-color-success-text": "dark",
"guppy-alert-alert-outline-active": trueBest Practices
Vererbung sauber halten
{# Immer von der entsprechenden Shopware-Vorlage erben #}
{% sw_extends '@Storefront/storefront/layout/header/header.html.twig' %}
{# Spezifische Blöcke überschreiben #}
{% block layout_header_logo %}
<div class="header-logo-custom">
{{ parent() }}
</div>
{% endblock %}Theme-Konfig konsumieren
{# Variante setzen #}
{% if theme_config('guppy-component-variant') %}
{% set variant = theme_config('guppy-component-variant') %}
{% endif %}
{# Fallback auf Default #}
{% if variant %}
{# Custom Logik #}
{% else %}
{{ parent() }}
{% endif %}Semantik und A11y
<nav role="navigation" aria-label="{{ 'navigation.main'|trans }}">
{# Navigation-Inhalt #}
</nav>
<a href="#main-content" class="skip-link">
{{ 'skipLink.toMainContent'|trans }}
</a>Verwandt
- Architektur: Plugin-Struktur, theme.json-Modell.
- Variablen & Tokens: SCSS-Pendants zu Twig-Konfigurationen.
- Storefront-JS: JS-Plugins, die mit den Templates interagieren.
- Twig-Blöcke: vollständige Override-Liste.