Fix CLS Issues on Forkcms (Layout Shift) | OpsBlu Docs

Fix CLS Issues on Forkcms (Layout Shift)

Stabilize Fork CMS layouts by sizing images in Twig theme templates, preloading web fonts, and reserving widget position containers.

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. Fork CMS generates CLS from unsized media library images, dynamically-rendered module widgets (blog, FAQ, search), and theme font loading.

Fork CMS-Specific CLS Causes

  • Media library images without dimensions -- Fork CMS outputs images from the media manager without explicit width/height attributes
  • Module widget positions -- modules placed in widget positions (sidebar, footer) load their content dynamically and shift surrounding elements
  • Blog module listings -- variable-length post excerpts and optional featured images create inconsistent card heights
  • FAQ accordion rendering -- FAQ module items animate open/closed, shifting content below
  • Theme font loading -- Fork CMS themes load web fonts via CSS files without font-display: swap

Fixes

1. Add Image Dimensions in Templates

Override Fork's image rendering in your theme:

{# In your theme's Twig templates #}
{% if post.image %}
  <div class="post-image" style="aspect-ratio: 16/9; overflow: hidden; background: #f0f0f0;">
    <img
      src="{{ FRONTEND_FILES_URL }}/{{ post.image }}"
      width="800" height="450"
      alt="{{ post.title }}"
      loading="lazy"
      style="width: 100%; height: 100%; object-fit: cover;"
    >
  </div>
{% else %}
  <div class="post-image-placeholder" style="aspect-ratio: 16/9; background: #f0f0f0;"></div>
{% endif %}

2. Reserve Space for Widget Positions

Fork CMS uses named positions for module widgets. Set minimum heights:

/* Widget position containment */
.position-sidebar { min-height: 300px; contain: layout; }
.position-footer { min-height: 200px; contain: layout; }

/* Individual widget modules */
.widget-blog-recent { min-height: 250px; contain: layout; }
.widget-blog-categories { min-height: 150px; contain: layout; }
.widget-search { min-height: 60px; contain: layout; }
.widget-faq { min-height: 200px; contain: layout; }

3. Stabilize Blog Listings

/* Consistent blog post card heights */
.blog-list .post-item {
  min-height: 200px;
  contain: layout;
}

/* Fixed-height excerpts prevent variable card sizes */
.post-excerpt {
  max-height: 4.5em;
  overflow: hidden;
  display: -webkit-box;
  -webkit-line-clamp: 3;
  -webkit-box-orient: vertical;
}

4. Fix FAQ Accordion CLS

/* Prevent FAQ items from shifting content below */
.faq-container {
  contain: layout;
}

/* Use max-height transition instead of height:auto */
.faq-answer {
  max-height: 0;
  overflow: hidden;
  transition: max-height 0.3s ease;
}

.faq-answer.open {
  max-height: 500px; /* Large enough for any answer */
}

5. Preload Theme Fonts

{# In base layout <head> #}
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link rel="preload" href="{{ THEME_URL }}/fonts/custom.woff2" as="font" type="font/woff2" crossorigin>
@font-face {
  font-family: 'CustomFont';
  src: url('fonts/custom.woff2') format('woff2');
  font-display: swap;
  size-adjust: 103%;
}

Measuring CLS on Fork CMS

  1. Chrome DevTools Performance tab -- record and filter for layout shifts
  2. Test widget-heavy pages -- pages with sidebar widgets and footer modules have the most CLS risk
  3. Test blog listings vs. single posts -- different CLS profiles
  4. Mobile testing -- Fork CMS responsive layouts often reflow differently on mobile

Analytics Script Impact

  • Fork CMS's analytics module is lightweight and server-side -- minimal CLS impact
  • Cookie consent implementations should use position: fixed overlays
  • If adding third-party analytics via custom code, use async/defer