Implementing Focus Reading Mode for WordPress Posts Without Layout Shift (CLS)

Focus Mode (Focus Reading Mode) is a very useful feature for long-form articles and technical documentation. However, if implemented incorrectly, hiding the sidebar after the page has already rendered can cause layout shifts, increasing the Cumulative Layout Shift (CLS) metric.

Implementing Focus Reading Mode for WordPress Posts Without Layout Shift (CLS)

This article presents a practical Focus Mode implementation based directly on real code, with the key point being the use of the wp_head hook to determine layout behavior from the very beginning.

Common Issues When Implementing Focus Mode

Most Focus Mode implementations are built by waiting for DOMContentLoaded before hiding the sidebar. This approach causes the sidebar to render first and then disappear, forcing the content column to reflow and resulting in a visible layout shift.

To avoid CLS, the sidebar must be hidden before the browser’s first paint. In WordPress, this can only be done safely through the wp_head hook.

Initializing Focus Mode State Early with wp_head

The following code runs very early inside wp_head. Its sole responsibility is to check whether Focus Mode has been previously stored in localStorage, and if so, apply a temporary class to the html element.

// ===== Focus Mode: singular only, early execution =====
function init_html_early_focus_mode_inline_script() {
    if ( ! is_singular() ) {
        return;
    }
?>
<script>
(function () {
    try {
        if (localStorage.getItem('init_focus_mode') === '1') {
            document.documentElement.classList.add('init-focus-pending');
        }
    } catch (e) {}
})();
</script>
<?php
}
add_action('wp_head', 'init_html_early_focus_mode_inline_script', 1);

The important detail here is that this script does not interact with body, does not query the DOM, and does not cause any layout changes. It only marks an early state so CSS can react immediately.

Early-rendered CSS to Prevent CLS

JavaScript alone is not enough to prevent layout shift. The sidebar will only be excluded from rendering when the corresponding CSS exists before the first paint. For this reason, the anti-CLS CSS must also be inlined inside wp_head.

// Focus mode EARLY anti-CLS
add_action('wp_head', function () {
    if ( ! is_singular() ) {
        return;
    }
?>
<style>
html.init-focus-pending body.single-post #id-sidebar {
    display: none !important;
}
html.init-focus-pending body.single-post #id-content {
    margin-inline: auto;
}
</style>
<?php
}, 1);

With this CSS in place, when Focus Mode is enabled, the sidebar is never rendered at all. As a result, the layout remains completely stable and no CLS is generated.

CSS for Focus Mode After Page Load

Once the page has fully rendered, Focus Mode is controlled by a class applied to the body element. At this point, the CSS no longer affects the initial layout.

/* Focus Mode */

body.is-focus-mode #id-sidebar {
    display: none !important;
}

body.is-focus-mode #id-content {
    margin-inline: auto;
}

State Synchronization and Toggle Button Rendering

When DOMContentLoaded fires, the temporary state on the html element is synchronized to the body. At the same time, a toggle button is rendered to allow user interaction.

// FOCUS
document.addEventListener('DOMContentLoaded', () => {
    if (!document.body.classList.contains('single-post')) {
        return;
    }

    const STORAGE_KEY = 'init_focus_mode';
    const root = document.documentElement;

    let isEnabled = root.classList.contains('init-focus-pending');

    // Sync early state → body
    if (isEnabled) {
        document.body.classList.add('is-focus-mode');
        root.classList.remove('init-focus-pending');
    }

    const btn = document.createElement('button');
    btn.className = [
        'uk-button',
        'uk-button-secondary',
        'uk-border-pill',
        'uk-position-fixed',
        'uk-position-small',
        'uk-position-bottom-right',
        'uk-box-shadow-medium',
        'uk-visible@l',
        'init-focus-toggle'
    ].join(' ');

    btn.style.zIndex = 9999;
    document.body.appendChild(btn);

    const render = (enabled) => {
        btn.innerHTML = enabled
            ? `<span uk-icon="icon: close"></span> ${initHTMLData.exit || 'Exit'}`
            : `<span uk-icon="icon: file-text"></span> ${initHTMLData.focus || 'Focus'}`;
    };

    render(isEnabled);

    btn.addEventListener('click', () => {
        isEnabled = !isEnabled;

        document.body.classList.toggle('is-focus-mode', isEnabled);
        localStorage.setItem(STORAGE_KEY, isEnabled ? '1' : '0');
        render(isEnabled);
    });
});

From this point onward, enabling or disabling Focus Mode only toggles a class on the body element, without affecting the initial layout or introducing CLS.

Conclusion

The key to a proper Focus Mode implementation is not the UI or the toggle button, but deciding the layout early enough. By using wp_head to initialize state and applying anti-CLS CSS before the browser paints, Focus Mode can work smoothly, consistently, and without harming Core Web Vitals.

This approach is especially suitable for technical blogs and documentation systems, where reading experience and performance are top priorities.

Comments


  • No comments yet.

Init Toolbox

Press Ctrl + \ on desktop, or swipe left anywhere on mobile.

Login