Hallo TYPO3 Freunde und Liebhaber. Ich habe mir Gedanken gemacht, warum ein eigenes Fluid-Theme der bessere Weg ist und wie man ein Seitenlayout Template ggf. zukunftssicher für TYPO3 13 entwickeln könnte. Dann habe ich mich auf den Weg gemacht und das Ergebnis stimmt mich freudig.
Der Weg jenseits des Bootstrap Package
Als langjähriger TYPO3 Freund und Webdesigner, habe ich die Evolution des Bootstrap_Package von Benjamin Kott miterlebt. Keine Frage: Seine Arbeit hat die TYPO3-Community enorm vorangebracht und vielen den Einstieg erleichtert. Die Backend-Konstanten in Version 6 und 7 LTS waren fantastisch – schnelle Anpassungen von Farben, Schriften, Header und Footer ohne tiefes SCSS-Wissen. Als Webdesigner habe ich das geliebt.
Doch mit jedem LTS-Wechsel wurde es komplexer:
Die Herausforderungen des Bootstrap Package
- Upgrade-Hölle: Jeder Wechsel von LTS 11 → 12 → 13 erfordert ein neues Bootstrap Sitepackage
- Theme.scss Overrides: Alle bisherigen Styles müssen mühsam in Theme.scss überschrieben werden
- Vendor Lock-in: Abhängigkeit von Benjamin Kotts Release-Zyklen
- Komplexität: Was früher über Konstanten ging, erfordert jetzt SCSS-Kenntnisse
- Wartungsaufwand: Bei jedem TYPO3-Update potenzielle Breaking Changes
- Crop und verzerrte Hintergrundbilder im Slider – Mobil nicht Responsive (Beispiel)
Mein Ansatz: Reines Fluid Sitepackage
Nach mehreren frustrierenden Upgrades habe ich mich entschieden, einen anderen Weg zu gehen:
- Volle Kontrolle über jeden Aspekt des Themes
- Unabhängig von externen Package-Releases
- Einfache Upgrades zwischen LTS-Versionen
- Klare Struktur mit Fluid, TypoScript und eigenem CSS
- Zukunftssicher für TYPO3 13 und darüber hinaus
In diesem Tutorial zeige ich dir, wie du einen professionellen Hero Slider als Custom Content Element erstellst – ohne Bootstrap Package, aber mit allen modernen Features.
Was wir bauen
Ein vollwertiger Hero Slider mit folgenden Features:
Responsive Hintergrundbilder ohne Crop (background-size: cover)
Schicke H2 Überschriften mit Text-Shadow für bessere Lesbarkeit
Elegante Teaser-Texte
Touch-fähige Navigation (Swiper.js)
Smooth Fade-Effekte zwischen Slides
Mobile-optimiert mit responsiven Höhen
Backend-freundlich mit Metadaten pro Bild
Voraussetzungen
- TYPO3 12.4 LTS Installation
- Eigenes Fluid Sitepackage (z.B.
EXT:mein_sitepackage) - Grundkenntnisse in Fluid, TypoScript und PHP
- Zugriff auf das Backend (Admin-Rechte)
Wichtig: Wir nutzen Bootstrap NUR für das Grid-System via CDN – kein Bootstrap Package!
Vergleich: Bootstrap Package vs. Eigenes Fluid Theme
┌──────────────────────────────────────────────────────────┐
│ Bootstrap Package vs. Fluid Theme │
├──────────────────────────────────────────────────────────┤
│ Theme.scss Overrides → Eigenes CSS │
│ Komplexe Dependencies → Minimale Abhängigkeiten │
│ Upgrade-Hölle bei LTS → Smooth Updates │
│ Vendor Lock-in → Volle Kontrolle │
│ SCSS-Kenntnisse nötig → Einfaches CSS │
│ Undurchsichtige Struktur → Klare Ordnerstruktur │
└──────────────────────────────────────────────────────────┘
Schritt 1: Swiper.js einbinden
Für unseren Slider nutzen wir Swiper.js – eine moderne, leichtgewichtige Slider-Bibliothek.
Configuration/TypoScript/setup.typoscript:
# Swiper CSS & JS einbinden
page.includeCSS {
swiper = https://cdn.jsdelivr.net/npm/swiper@11/swiper-bundle.min.css
swiper.external = 1
}
page.includeJSFooter {
swiper = https://cdn.jsdelivr.net/npm/swiper@11/swiper-bundle.min.js
swiper.external = 1
}
💡 Tipp: Via CDN bleiben wir flexibel und müssen Swiper nicht in unserer Extension mitliefern.
Schritt 2: Custom Content Element registrieren
Wir erstellen ein neues Content Element „Hero Slider“ für das Backend.
Configuration/TCA/Overrides/tt_content_heroslider.php:
<?php
defined('TYPO3') or die();
// Neues Content Element "Hero Slider" registrieren
\TYPO3\CMS\Core\Utility\ExtensionManagementUtility::addTcaSelectItem(
'tt_content',
'CType',
[
'label' => 'Hero Slider',
'value' => 'heroslider',
'icon' => 'content-image',
'group' => 'default',
]
);
// Felder für Hero Slider definieren
$GLOBALS['TCA']['tt_content']['types']['heroslider'] = [
'showitem' => '
--div--;LLL:EXT:core/Resources/Private/Language/Form/locallang_tabs.xlf:general,
--palette--;;general,
header;Überschrift (optional),
bodytext;Beschreibung (optional),
--div--;Slides,
assets;Hintergrundbilder (je Bild = 1 Slide),
--div--;LLL:EXT:core/Resources/Private/Language/Form/locallang_tabs.xlf:appearance,
--palette--;;frames,
',
'columnsOverrides' => [
'bodytext' => [
'config' => [
'enableRichtext' => true,
]
],
'assets' => [
'config' => [
'maxitems' => 10,
],
],
],
];
Was passiert hier?
- Wir registrieren ein neues Content-Element mit dem Typ
heroslider - Das Element erscheint im Backend unter „Inhalt hinzufügen“
- Wir definieren, welche Felder angezeigt werden (Header, Bodytext, Assets)
- Maximal 10 Bilder können hochgeladen werden
Schritt 3: Datenbank-Felder für Bild-Metadaten
Damit jedes Slider-Bild eigene Texte haben kann, erweitern wir sys_file_reference.
Configuration/TCA/Overrides/sys_file_reference.php:
<?php
defined('TYPO3') or die();
// Felder für Hero Slider Bilder
$tempColumns = [
'tx_heroslider_title' => [
'label' => 'Slide Überschrift',
'config' => [
'type' => 'input',
'size' => 50,
'max' => 255,
],
],
'tx_heroslider_teaser' => [
'label' => 'Slide Teaser',
'config' => [
'type' => 'text',
'rows' => 3,
'cols' => 40,
],
],
'tx_heroslider_link' => [
'label' => 'Button Link',
'config' => [
'type' => 'input',
'renderType' => 'inputLink',
'size' => 50,
'max' => 1024,
'eval' => 'trim',
],
],
];
\TYPO3\CMS\Core\Utility\ExtensionManagementUtility::addTCAcolumns('sys_file_reference', $tempColumns);
Datenbank-Schema anlegen:
ext_tables.sql (im Root deiner Extension):
#
# Table structure for table 'sys_file_reference'
#
CREATE TABLE sys_file_reference (
tx_heroslider_title varchar(255) DEFAULT '' NOT NULL,
tx_heroslider_teaser text,
tx_heroslider_link varchar(1024) DEFAULT '' NOT NULL
);
⚠️ Wichtig: Nach dem Erstellen der Datei:
Admin Tools → Maintenance → Analyze Database Structure → Execute
Häufiger Fehler: „Unknown column ‚tx_heroslider_link'“
Dieser Fehler tritt auf, wenn die Datenbank-Felder noch nicht angelegt wurden. Lösung:
- Prüfe ob
ext_tables.sqlim Extension-Root liegt - Führe Database Analyzer aus
- Cache leeren
💡 NICHT SQL-Code in
ext_tables.phpeinfügen – das sind zwei verschiedene Dateien!
Schritt 4: TypoScript Rendering
Jetzt definieren wir, wie der Slider gerendert wird.
Configuration/TypoScript/setup.typoscript:
# Hero Slider Content Element
tt_content.heroslider = FLUIDTEMPLATE
tt_content.heroslider {
templateName = HeroSlider
templateRootPaths {
10 = EXT:mein_sitepackage/Resources/Private/Templates/ContentElements/
}
partialRootPaths {
10 = EXT:mein_sitepackage/Resources/Private/Partials/
}
dataProcessing {
# Bilder verarbeiten
10 = TYPO3\CMS\Frontend\DataProcessing\FilesProcessor
10 {
references.fieldName = assets
as = slides
}
# Content-Daten durchreichen
20 = TYPO3\CMS\Frontend\DataProcessing\DatabaseQueryProcessor
20 {
table = tt_content
pidInList.data = field:uid
as = contentData
}
}
}
Was macht der FilesProcessor?
- Lädt alle hochgeladenen Bilder aus dem
assets-Feld - Stellt sie als Array
{slides}im Fluid-Template zur Verfügung - Inkl. aller Metadaten (Titel, Teaser, Link)
Schritt 5: Fluid Template
Das Herzstück unseres Sliders.
Resources/Private/Templates/ContentElements/HeroSlider.html:
<html xmlns:f="http://typo3.org/ns/TYPO3/CMS/Fluid/ViewHelpers"
data-namespace-typo3-fluid="true">
<!-- Hero Slider Wrapper -->
<div class="hero-slider-wrapper">
<div class="swiper heroSwiper">
<div class="swiper-wrapper">
<!-- Loop durch alle Slides -->
<f:for each="{slides}" as="slide" iteration="iteration">
<div class="swiper-slide">
<!-- Responsive Background Image -->
<div class="hero-slide-bg"
style="background-image: url('{f:uri.image(image: slide, maxWidth: 1920)}');">
<!-- Overlay für bessere Lesbarkeit -->
<div class="hero-overlay"></div>
<!-- Content Container -->
<div class="hero-content container">
<div class="hero-text-wrapper">
<!-- Slide-spezifische Überschrift -->
<f:if condition="{slide.properties.tx_heroslider_title}">
<h2 class="hero-title" data-aos="fade-up">
{slide.properties.tx_heroslider_title}
</h2>
</f:if>
<!-- Fallback: Globale Überschrift -->
<f:if condition="{data.header}">
<f:then>
<f:if condition="{slide.properties.tx_heroslider_title}">
<f:else>
<h2 class="hero-title" data-aos="fade-up">
{data.header}
</h2>
</f:else>
</f:if>
</f:then>
</f:if>
<!-- Slide-spezifischer Teaser -->
<f:if condition="{slide.properties.tx_heroslider_teaser}">
<p class="hero-teaser" data-aos="fade-up" data-aos-delay="100">
{slide.properties.tx_heroslider_teaser}
</p>
</f:if>
<!-- Fallback: Globaler Text -->
<f:if condition="{data.bodytext}">
<f:if condition="{slide.properties.tx_heroslider_teaser}">
<f:else>
<div class="hero-teaser" data-aos="fade-up" data-aos-delay="100">
<f:format.html>{data.bodytext}</f:format.html>
</div>
</f:else>
</f:if>
</f:if>
<!-- Call to Action Button -->
<f:if condition="{slide.properties.tx_heroslider_link}">
<div class="hero-cta" data-aos="fade-up" data-aos-delay="200">
<a href="{slide.properties.tx_heroslider_link}"
class="btn btn-primary btn-lg hero-btn">
Mehr erfahren
</a>
</div>
</f:if>
</div>
</div>
</div>
</div>
</f:for>
</div>
<!-- Navigation Arrows -->
<div class="swiper-button-next"></div>
<div class="swiper-button-prev"></div>
<!-- Pagination Dots -->
<div class="swiper-pagination"></div>
</div>
</div>
<!-- Swiper Initialisierung -->
<script>
document.addEventListener('DOMContentLoaded', function() {
const heroSwiper = new Swiper('.heroSwiper', {
loop: true,
speed: 800,
effect: 'fade',
fadeEffect: {
crossFade: true
},
autoplay: {
delay: 5000,
disableOnInteraction: false,
},
pagination: {
el: '.swiper-pagination',
clickable: true,
},
navigation: {
nextEl: '.swiper-button-next',
prevEl: '.swiper-button-prev',
},
});
});
</script>
</html>
Besonderheiten:
- Fallback-Logik: Erst slide-spezifische Texte, dann globale Content-Texte
- Responsive Images:
f:uri.imagemit maxWidth 1920px - Fade-Effekt: Sanfte Übergänge statt hartem Slide
- Autoplay: Alle 5 Sekunden automatisch weiter
Schritt 6: CSS Styling
Jetzt machen wir den Slider schön – responsive und ohne Bootstrap Package!
Resources/Public/Css/hero-slider.css:
/* ================================
HERO SLIDER STYLES
================================ */
.hero-slider-wrapper {
width: 100%;
position: relative;
overflow: hidden;
}
.heroSwiper {
width: 100%;
height: 80vh; /* 80% der Bildschirmhöhe */
min-height: 600px; /* Niemals kleiner als 600px */
max-height: 900px; /* Auf großen Monitoren begrenzen */
}
/* Slide Background - RESPONSIVE ohne Crop */
.hero-slide-bg {
width: 100%;
height: 100%;
background-size: cover;
background-position: center center;
background-repeat: no-repeat;
position: relative;
display: flex;
align-items: center;
justify-content: center;
}
/* Dunkler Overlay für bessere Lesbarkeit */
.hero-overlay {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: linear-gradient(
to bottom,
rgba(0, 0, 0, 0.3) 0%,
rgba(0, 0, 0, 0.5) 100%
);
z-index: 1;
}
/* Content Container */
.hero-content {
position: relative;
z-index: 2;
color: white;
text-align: center;
padding: 2rem;
}
.hero-text-wrapper {
max-width: 800px;
margin: 0 auto;
}
/* Schicke H2 Überschrift */
.hero-title {
font-size: 3.5rem;
font-weight: 700;
line-height: 1.2;
margin-bottom: 1.5rem;
color: white;
text-shadow: 2px 2px 8px rgba(0, 0, 0, 0.5);
letter-spacing: -0.02em;
}
/* Teaser Text */
.hero-teaser {
font-size: 1.25rem;
line-height: 1.6;
margin-bottom: 2rem;
color: rgba(255, 255, 255, 0.95);
text-shadow: 1px 1px 4px rgba(0, 0, 0, 0.5);
}
/* Call to Action Button */
.hero-cta {
margin-top: 2rem;
}
.hero-btn {
padding: 1rem 2.5rem;
font-size: 1.125rem;
font-weight: 600;
border-radius: 50px;
text-transform: uppercase;
letter-spacing: 0.05em;
transition: all 0.3s ease;
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.2);
}
.hero-btn:hover {
transform: translateY(-2px);
box-shadow: 0 6px 20px rgba(0, 0, 0, 0.3);
}
/* Swiper Navigation Buttons */
.swiper-button-next,
.swiper-button-prev {
color: white;
background: rgba(255, 255, 255, 0.2);
width: 50px;
height: 50px;
border-radius: 50%;
backdrop-filter: blur(10px);
transition: all 0.3s ease;
}
.swiper-button-next:after,
.swiper-button-prev:after {
font-size: 1.25rem;
}
.swiper-button-next:hover,
.swiper-button-prev:hover {
background: rgba(255, 255, 255, 0.3);
transform: scale(1.1);
}
/* Pagination Dots */
.swiper-pagination-bullet {
width: 12px;
height: 12px;
background: white;
opacity: 0.5;
transition: all 0.3s ease;
}
.swiper-pagination-bullet-active {
opacity: 1;
width: 30px;
border-radius: 6px;
}
/* ================================
RESPONSIVE ANPASSUNGEN
================================ */
/* Tablet */
@media (max-width: 991px) {
.heroSwiper {
height: 70vh;
min-height: 500px;
}
.hero-title {
font-size: 2.5rem;
}
.hero-teaser {
font-size: 1.125rem;
}
.hero-btn {
padding: 0.875rem 2rem;
font-size: 1rem;
}
}
/* Mobile */
@media (max-width: 767px) {
.heroSwiper {
height: 60vh;
min-height: 400px;
}
.hero-title {
font-size: 2rem;
margin-bottom: 1rem;
}
.hero-teaser {
font-size: 1rem;
margin-bottom: 1.5rem;
}
.hero-btn {
padding: 0.75rem 1.5rem;
font-size: 0.95rem;
}
.swiper-button-next,
.swiper-button-prev {
width: 40px;
height: 40px;
}
.swiper-button-next:after,
.swiper-button-prev:after {
font-size: 1rem;
}
}
/* Extra Small Mobile */
@media (max-width: 575px) {
.heroSwiper {
height: 60vh;
min-height: 350px;
}
.hero-title {
font-size: 1.75rem;
}
.hero-content {
padding: 1rem;
}
/* Navigation Buttons auf kleinen Screens ausblenden */
.swiper-button-next,
.swiper-button-prev {
display: none;
}
}
Wichtige CSS-Konzepte:
background-size: cover: Bild füllt Container ohne Cropbackground-position: center: Bildzentrum bleibt sichtbar- Viewport-basierte Höhen:
80vh= 80% Bildschirmhöhe - Mobile-First: Basis-Styles + Media Queries für größere Screens
- Keine SCSS-Overrides: Klares, wartbares CSS
Schritt 7: CSS einbinden
Configuration/TypoScript/setup.typoscript:
page.includeCSS {
heroSlider = EXT:mein_sitepackage/Resources/Public/Css/hero-slider.css
}
Schritt 8: Im Backend verwenden
Content Element anlegen:
- Seite öffnen (z.B. Startseite)
- Inhalt erstellen im Hero-Bereich (colPos 4)
- Typ wählen: „Hero Slider“
- Bilder hochladen (Empfohlen: 1920x600px oder größer)
- Pro Bild Metadaten eintragen:
- Slide Überschrift
- Slide Teaser
- Button Link
- Speichern & Frontend prüfen
![Backend Screenshot Platzhalter: Hero Slider Element mit Bildern]
Tipps für Redakteure:
- Bildgröße: Mindestens 1920px Breite für Desktop
- Bildformat: JPG (komprimiert) oder WebP
- Textlänge: Überschrift max. 60 Zeichen, Teaser max. 150 Zeichen
- Kontrast: Dunkle Bilder = weißer Text, helle Bilder = Overlay verstärken
Troubleshooting: Häufige Fehler
1. „Unknown column ‚tx_heroslider_link'“
Problem: Datenbank-Felder nicht angelegt
Lösung:
# 1. Prüfe ob ext_tables.sql existiert
# 2. Backend: Admin Tools → Maintenance → Analyze Database Structure
# 3. Execute selected statements
# 4. Cache leeren
2. „Template could not be loaded“
Problem: Falscher Template-Pfad
Lösung:
# Prüfe templateRootPaths in setup.typoscript
templateRootPaths {
10 = EXT:dein_sitepackage/Resources/Private/Templates/ContentElements/
}
3. Slider zeigt nur schmalen Streifen
Problem: Keine Höhe definiert
Lösung:
.heroSwiper {
min-height: 600px; /* Mindesthöhe setzen */
height: 80vh;
}
4. Bilder werden gecroppt/verzerrt
Problem: Falsches background-size
Lösung:
.hero-slide-bg {
background-size: cover; /* NICHT contain! */
background-position: center center;
}
5. Swiper funktioniert nicht
Problem: JavaScript nicht geladen
Lösung:
# Prüfe ob Swiper.js eingebunden ist
page.includeJSFooter {
swiper = https://cdn.jsdelivr.net/npm/swiper@11/swiper-bundle.min.js
swiper.external = 1
}
Der Upgrade-Vorteil: Bereit für TYPO3 13
Was funktioniert in TYPO3 13?
Fluid-Syntax: Bleibt größtenteils stabil
TypoScript: Kompatibel (mit kleinen Anpassungen)
TCA-Struktur: Weitgehend unverändert
DataProcessing: FilesProcessor funktioniert weiter
Custom Content Elements: Bewährtes Konzept
Was zu prüfen ist:
⚠️ Breaking Changes: Release Notes lesen
⚠️ Deprecated Features: TYPO3 Scanner nutzen
⚠️ Composer Dependencies: Versionsnummern anpassen
Migration vom Bootstrap Package
Wenn du vom Bootstrap Package kommst:
- Erstelle neues Sitepackage (ohne Bootstrap Package Dependency)
- Migriere Content Elements schrittweise
- CSS neu schreiben (keine SCSS-Overrides mehr)
- Teste auf Staging vor dem Go-Live
- Dokumentiere Änderungen für dein Team
Zeit-Investition: 2-4 Wochen, aber danach:
- Volle Kontrolle
- Einfache Updates
- Keine Vendor-Abhängigkeiten
Best Practices für zukunftssichere Sitepackages
1. Klare Ordnerstruktur
EXT:mein_sitepackage/
├── Configuration/
│ ├── TCA/Overrides/
│ │ ├── tt_content_*.php
│ │ └── sys_file_reference.php
│ └── TypoScript/
│ ├── setup.typoscript
│ └── constants.typoscript
├── Resources/
│ ├── Private/
│ │ ├── Layouts/
│ │ ├── Partials/
│ │ │ ├── Header.html
│ │ │ └── Footer.html
│ │ └── Templates/
│ │ ├── Page/
│ │ └── ContentElements/
│ └── Public/
│ ├── Css/
│ ├── JavaScript/
│ └── Images/
├── ext_emconf.php
├── ext_tables.sql
└── ext_localconf.php
2. Partials nutzen
Lagere wiederkehrende Komponenten aus:
<!-- Templates/Page/Default.html -->
<f:render partial="/Header" />
<f:render partial="/Footer" />
Vorteile:
- DRY-Prinzip (Don’t Repeat Yourself)
- Einfache Wartung
- Wiederverwendbarkeit
3. TypoScript modular halten
# Statt alles in setup.typoscript:
<INCLUDE_TYPOSCRIPT: source="FILE:EXT:mein_sitepackage/Configuration/TypoScript/ContentElements/HeroSlider.typoscript">
<INCLUDE_TYPOSCRIPT: source="FILE:EXT:mein_sitepackage/Configuration/TypoScript/Navigation.typoscript">
4. CSS-Variablen statt SCSS
/* Keine SCSS-Overrides mehr! */
:root {
--primary-color: #007bff;
--hero-title-size: 3.5rem;
--hero-overlay-opacity: 0.5;
}
.hero-title {
font-size: var(--hero-title-size);
}
Vorteile:
- Keine Build-Pipeline nötig
- Browser-nativ
- Einfache Anpassungen
5. Eigene Content-Elemente
Statt Bootstrap-Components zu überschreiben, erstelle eigene:
// Custom Content Elements für:
- Hero Slider
- Card Grid
-
Das Ergebnis seht Ihr auf einer Musterseite in der Version TYP3 12.4.38 Die Upgrade Erfahren auf 13 LTS werde ich dann in einem weiteren Beitrag beschreiben.
