General Guide: See Global CLS Guide for universal concepts and fixes.
What is CLS?
Cumulative Layout Shift measures visual stability. Google recommends CLS under 0.1. Contao generates CLS primarily from its image processing pipeline, dynamic insert tags, front-end module rendering, and cookie consent bar injection.
Contao-Specific CLS Causes
- Image variants loading at different sizes -- Contao's responsive image system can output images at sizes different from the placeholder until the correct variant loads
- Insert tags resolving client-side -- some insert tags (
{{iflng::*}},{{date::*}}) resolve dynamically, changing content and layout - Front-end module height variability -- news lists, event calendars, and search results render at different heights depending on data
- Contao cookie bar -- the built-in cookie consent bar pushes page content down when it appears
- Custom element animations -- Contao themes with scroll-triggered animations shift content during viewport entry
Fixes
1. Set Image Dimensions in Content Element Templates
Override Contao's image content element to always include dimensions:
<!-- templates/ce_image.html5 -->
<?php if ($this->addImage): ?>
<figure class="image_container"
style="aspect-ratio: <?= $this->imgSize[0] ?> / <?= $this->imgSize[1] ?>;">
<img
src="<?= $this->src ?>"
width="<?= $this->imgSize[0] ?>"
height="<?= $this->imgSize[1] ?>"
alt="<?= $this->alt ?>"
loading="<?= $this->isFirst ? 'eager' : 'lazy' ?>"
style="width: 100%; height: auto;"
>
</figure>
<?php endif; ?>
For gallery content elements:
/* Consistent gallery grid dimensions */
.ce_gallery .image_container {
aspect-ratio: 1 / 1;
overflow: hidden;
}
.ce_gallery .image_container img {
width: 100%;
height: 100%;
object-fit: cover;
}
2. Reserve Space for Front-End Modules
Wrap dynamic modules in containers with minimum heights:
<!-- templates/mod_newslist.html5 -->
<div class="<?= $this->class ?>" style="min-height: 400px; contain: layout;">
<?= $this->parent() ?>
</div>
/* News list module */
.mod_newslist { min-height: 400px; contain: layout; }
/* Event calendar */
.mod_eventlist { min-height: 350px; contain: layout; }
/* Search results */
.mod_search { min-height: 300px; contain: layout; }
/* Individual news items */
.layout_short { min-height: 150px; }
3. Fix Cookie Consent Bar CLS
Contao's built-in cookie bar or third-party consent extensions push content:
/* Make Contao's cookie bar a fixed overlay */
.contao-cookiebar,
.cc-window {
position: fixed !important;
bottom: 0;
left: 0;
right: 0;
z-index: 9999;
}
/* Do NOT use these -- they push content: */
/* position: relative; */
/* position: static; */
4. Preload Theme Fonts
Configure font preloading in your page layout:
<!-- In your fe_page.html5 template <head> -->
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link rel="preload" href="/files/themes/yourtheme/fonts/custom.woff2" as="font" type="font/woff2" crossorigin>
@font-face {
font-family: 'CustomFont';
src: url('/files/themes/yourtheme/fonts/custom.woff2') format('woff2');
font-display: swap;
size-adjust: 102%;
}
5. Handle Responsive Content Elements
Contao's content elements can have different content on different devices. Prevent CLS during resize:
/* Contain layout shifts within articles */
.mod_article {
contain: layout style;
}
/* Prevent content element collapse during load */
.ce_text, .ce_image, .ce_hyperlink {
min-height: 0;
contain: layout;
}
/* Embed containers */
.ce_html iframe,
.ce_youtube iframe {
aspect-ratio: 16 / 9;
width: 100%;
height: auto;
}
Measuring CLS on Contao
- Chrome DevTools Performance tab -- record page load and look for layout-shift entries
- Test module-heavy pages -- news listing, event calendar, and search result pages have the most dynamic content
- Test with/without cookie bar -- toggle cookie consent to measure its CLS impact
- Symfony Profiler (dev mode) -- helps identify which content elements and modules render late
Analytics Script Impact
- Contao's analytics is typically added via page layout "Additional CSS/JS" fields or the
contao-google-analyticsextension - Ensure scripts use
async/deferattributes - Consent manager extensions (klaro, onetrust) should use fixed-position overlays, not push-down banners