⌘K

Font-end Assets Registry

Search, the gallery lightbox, shortcode styling, and collapsible/tabbed sections each load their own CSS/JS only on pages that actually use that feature — a plain content page loads none of them. This replaced a single always-loaded assets/js/main.js bundling everything into every page load regardless of need.

There's no core-provided page-transitions file at all anymore, unconditional or otherwise — most catalogue themes never actually used its fade-in/pagination-fade behavior (no .fade-in CSS rule defined, or pagination markup under a different class than .pagination), so it was pure dead weight on every theme that didn't. The themes that do use it now carry that same handful of lines directly in their own script.js.

Two mechanisms are involved, and they detect different things:

Detection: rendered HTML classes, not raw content

sl_content_needs_shortcode_assets() and sl_content_needs_gallery_assets() (core/render/asset-registry.php) both take the fully-rendered HTML string and look for CSS class markers, not raw Markdown/shortcode source:

function sl_content_needs_shortcode_assets(string $renderedHtml): bool
{
    return $renderedHtml !== '' && stripos($renderedHtml, 'class="sc-') !== false;
}

function sl_content_needs_gallery_assets(string $renderedHtml): bool
{
    foreach (['class="c-col', 'class="tab-group', 'class="inline-gallery',
              'class="gallery-', 'class="carousel-', 'class="masonry-item',
              'class="justified-gallery'] as $marker) {
        if (stripos($renderedHtml, $marker) !== false) return true;
    }
    return false;
}

This matters because collapsible/tabbed sections are not a shortcode — they're an HTML block the editor inserts directly (<div class="c-col">…</div>, <div class="tab-group">…</div>), never present as [collapsible]/[tabs] tag text anywhere. An earlier version of this detection scanned raw Markdown for a hand-maintained list of shortcode tag names and consequently missed collapsible-only content entirely (a page with a .c-col block and no [gallery] shortcode got collapsibles.js correctly but not the CSS that styles .c-col, and rendered unstyled). Checking the rendered HTML's actual classes — the same technique render_hljs_scripts() already used for code-block detection — doesn't have that gap, and also doesn't false-positive on a shortcode like [contact_form] that produces no sc--prefixed markup.

Both functions read global $pageContent — the page body, already rendered by processContent() and stored before header.php is ever included, so this works even though CSS/JS decisions happen in <head>, before the body markup would otherwise be available.

gallery-layout.css's name is legacy and misleading: it holds gallery grid styling and the collapsible/tab styling (.c-col, .tab-group, .c-toggle-bar) — a page can load it purely for a collapsible section, with no gallery on the page at all.

assets/css/shortcodes.css and assets/css/gallery-layout.css are added as plain <link> tags directly inside render_header_scripts() (core/render/page/assets.php), right after the unconditional synaptikCSS.php — you never call enqueue_css() for these two yourself, they're already wired to the detection above.

enqueue_js() / enqueue_css()

Everything else conditional goes through these two functions (core/render/asset-registry.php):

enqueue_js(string $handle, string $path, bool $defer = true): void
enqueue_css(string $handle, string $path): void

Call either from anywhere that runs before render_header_scripts() finishes (a hook, a page-templates/*.php file, a plugin's early_request/after_routing handler). $handle is a unique key — calling enqueue_js() twice with the same handle is a no-op the second time, so it's safe to call from a function that might run more than once per request. Whatever is enqueued is printed by render_enqueued_assets(), called at the very end of render_header_scripts() — after the theme's own js/script.js, so anything you enqueue always lands last in <head>.

What's already wired up

FileLoads whenEnqueued from
assets/js/features/search.jsAlways — every pageUnconditional, in render_header_scripts()'s own list, not via enqueue_js()
assets/css/shortcodes.cssThe rendered page contains any class="sc-…" markupInline <link> in render_header_scripts(), gated by sl_content_needs_shortcode_assets()
assets/css/gallery-layout.cssThe rendered page contains a gallery or a collapsible/tab blockInline <link> in render_header_scripts(), gated by sl_content_needs_gallery_assets()
assets/css/lightbox.css + assets/js/features/lightbox.jsThe page has a gallery (any layout)enqueue_css()/enqueue_js(), called from index.php at the point gallery layouts are resolved for the request
assets/js/features/collapsibles.jsThe rendered page content contains a .c-col/.tab-group blockenqueue_js(), called from render_collapsibles_script() (core/render/page/assets.php)

Search stays unconditional because it's meant to be available on every page regardless of content — there's no cheap way to predict "will this page's visitor want to search" the way there is for "does this page have a gallery."

Writing your own conditional asset

A plugin shipping a widget that only some pages use is the typical case:

if ($page_actually_has_my_widget) {
    enqueue_js('my-plugin-widget', 'plugins/my-plugin/assets/widget.js');
}

That's the entire integration — no manual <script> tag, no risk of double-inclusion if your hook fires more than once, and the file simply never downloads on a page that doesn't need it. If your detection needs the rendered page HTML rather than the raw item content, read global $pageContent the same way the two core detection functions do.