Skip to content

CSS Optimization

Gaurav Tiwari edited this page Aug 27, 2026 · 1 revision

CSS Optimization

Removes selectors a page does not use, then delivers what remains in one of three shapes. All of it runs on your own server — no external service ever sees your CSS. Off by default.

How pruning decides

For each page, the optimizer parses every collected stylesheet into an AST, converts each selector to XPath with Symfony's CSS selector component, and queries the rendered DOM. A selector that matches nothing is dropped.

Several things are kept regardless of whether they match:

Kept Why
Blocks declaring custom properties (--foo) A variable may be consumed by a descendant, a pseudo-element, a later state, or markup injected after load. Dropping the block would leave surviving rules with unresolved var().
:root, html, body, * Structural, always relevant
@keyframes Never matched by a DOM query
@font-face Same
Anything containing :: Pseudo-elements have no DOM node to match
Selectors your safelist matches Explicit escape hatch
Selectors that fail to convert to XPath Kept rather than guessed at

Interaction states are matched against the base element. .button:hover is tested as .button, because the captured DOM never shows a hover state. The full list of stripped states covers :hover, :focus, :focus-visible, :focus-within, :active, :visited, :target, :disabled, :enabled, :required, :optional, :valid, :invalid, :user-valid, :user-invalid, :checked, :indeterminate, :read-only, :read-write, :placeholder-shown, :autofill, :open, :closed, :popover-open. Certain [aria-*] and [data-*] state attributes are stripped the same way.

Releases before 1.0.1 mangled :focus-visible and :focus-within into a fragment that matched nothing, so keyboard focus styles were silently pruned out. Fixed in 1.0.1.

Delivery modes

Mode What the page gets
Generated file (file) One immutable, fingerprinted stylesheet
Inline all used CSS (inline) Everything inlined in <head>; no stylesheet request
Critical inline + remaining file (hybrid) Above-the-fold CSS inline, the rest as a file

Hybrid respects a critical budget (default 14336 bytes, roughly the first TCP window). If the critical segment would exceed it, hybrid falls back to a generated file rather than shipping an oversized inline block.

Training Mode

Static analysis cannot see selectors that only appear after JavaScript runs. Training Mode closes that gap.

While an administrator browses the site with it on, a small script records element IDs and class names only — no text, no field values, no cookies, no customer data — and posts them to a bounded observation store. Candidates then appear under CSS Reports, where you review them and publish the ones you want added to the safelist.

Nothing observed affects generated CSS until you publish it.

Staged rollout

rollout_percent controls what share of visitors receive optimized CSS. Set it to 10, watch, then raise it. Bucketing is deterministic per cache key, so a given page is consistently either optimized or not — you are not flipping the same URL back and forth between two versions.

Previewing before you commit

Append ?gtperf_css_preview=<nonce> as a logged-in administrator to render a page through the full optimization pipeline without storing anything in the page cache or a shared cache. The preview query parameter is on the bypass list, so it can never produce a cached artifact.

Exclusions and safelists

  • Excluded stylesheets — skip a whole stylesheet by URL fragment. Useful for a page builder that generates CSS per request.
  • Safelist — selector fragments that survive pruning unconditionally.
  • Both are also filterable: gt_performance_css_safelist and gt_performance_css_stylesheet_exclusions.

Compatibility detection adds exclusions automatically for known plugins. See Integrations.

Artifacts and regeneration

Generated CSS lives in wp-content/cache/gt-performance/assets/ and is registered in a database table with a fingerprint, mode and last-used timestamp. CSS Reports lists every generated artifact, its size, the saving against the original, and lets you regenerate a single URL.

Regeneration happens through the queue, not inline in a visitor's request.

Related

JavaScript Optimization · Diagnostics · Settings Reference

Clone this wiki locally