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

Fix CLS Issues on Textpattern (Layout Shift)

Stabilize Textpattern layouts by sizing images via txp:image tags, preloading theme fonts, and constraining plugin-generated output sections.

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. Textpattern generates CLS from images rendered without dimensions via Txp tags, plugin-injected content (comment forms, search widgets, related articles), theme font loading, and article listings with variable-height content.

Textpattern-Specific CLS Causes

  • Txp image tags without dimensions -- <txp:article_image /> and <txp:image /> can output images without explicit width/height if not configured
  • Plugin output injection -- plugins like smd_related_articles, com_connect (forms), and search plugins inject content that shifts surrounding elements
  • Variable article card heights -- article listings with optional images and variable excerpt lengths create inconsistent card sizes
  • Theme font loading -- custom fonts loaded via CSS @import in Textpattern themes cause FOUT
  • Comment section rendering -- the <txp:comments /> tag renders variable-height comment blocks that shift footer content

Fixes

1. Size Images in Templates

Use Textpattern's image info tags to include dimensions:

<!-- Article image with dimensions from the database -->
<txp:if_article_image>
  <div class="article-hero" style="aspect-ratio: 16/9; overflow: hidden; background: #f0f0f0;">
    <txp:images limit="1">
      <img
        src="<txp:image_url />"
        width="<txp:image_info type="w" />"
        height="<txp:image_info type="h" />"
        alt="<txp:image_info type="alt" />"
        style="width: 100%; height: 100%; object-fit: cover;"
      >
    </txp:images>
  </div>
</txp:if_article_image>

For content body images:

/* Fix unsized images in article bodies */
.article-body img {
  max-width: 100%;
  height: auto;
}

.article-body img:not([width]) {
  aspect-ratio: 16 / 9;
  width: 100%;
  object-fit: cover;
}

2. Constrain Plugin Output

/* Related articles plugin */
.smd_related {
  min-height: 200px;
  contain: layout;
}

/* Comment section */
.comments-container {
  min-height: 100px;
  contain: layout;
}

/* Search results */
.search-results {
  min-height: 300px;
  contain: layout;
}

/* Contact form plugin */
.com_connect-form {
  min-height: 400px;
  contain: layout;
}

3. Stabilize Article Listings

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

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

/* Placeholder for articles without images */
.article-card .image-placeholder {
  aspect-ratio: 16 / 9;
  background: #f0f0f0;
}
<!-- In your article list form -->
<div class="article-card">
  <txp:if_article_image>
    <div style="aspect-ratio: 16/9; overflow: hidden;">
      <txp:article_image />
    </div>
  <txp:else />
    <div class="image-placeholder"></div>
  </txp:if_article_image>
  <h3><txp:permlink><txp:title /></txp:permlink></h3>
  <p class="excerpt"><txp:excerpt /></p>
</div>

4. Fix Font Loading

<!-- In your page template <head> -->
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link rel="preload" href="<txp:site_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%;
}

body { font-family: 'CustomFont', system-ui, sans-serif; }

5. Handle Embedded Content

/* Iframes in article content */
.article-body iframe {
  aspect-ratio: 16 / 9;
  width: 100%;
  height: auto;
  border: 0;
}

/* Generic embed wrapper */
.embed-container {
  position: relative;
  aspect-ratio: 16 / 9;
}

.embed-container iframe {
  position: absolute;
  top: 0; left: 0;
  width: 100%; height: 100%;
}

Measuring CLS on Textpattern

  1. Chrome DevTools Performance tab -- record page load and filter for layout-shift entries
  2. Test article pages with/without images -- optional images in article listings cause the most CLS variability
  3. Test with plugins disabled -- deactivate plugins via Admin > Plugins one by one to isolate CLS sources
  4. Mobile testing -- Textpattern themes often have custom responsive CSS that introduces additional shifts

Analytics Script Impact

  • Textpattern has no built-in analytics -- all tracking is manually added to page templates
  • Ensure analytics scripts use async/defer and do not inject visible DOM elements
  • Cookie consent plugins should use position: fixed overlay, not push content
  • Avoid heatmap tools that inject feedback buttons without fixed positioning