Title: wp_hoist_late_printed_styles
Published: February 24, 2026
Last modified: August 20, 2026

---

# wp_hoist_late_printed_styles()

## In this article

 * [Description](https://developer.wordpress.org/reference/functions/wp_hoist_late_printed_styles/?output_format=md#description)
    - [See also](https://developer.wordpress.org/reference/functions/wp_hoist_late_printed_styles/?output_format=md#see-also)
 * [Source](https://developer.wordpress.org/reference/functions/wp_hoist_late_printed_styles/?output_format=md#source)
 * [Related](https://developer.wordpress.org/reference/functions/wp_hoist_late_printed_styles/?output_format=md#related)
 * [Changelog](https://developer.wordpress.org/reference/functions/wp_hoist_late_printed_styles/?output_format=md#changelog)

[ Back to top](https://developer.wordpress.org/reference/functions/wp_hoist_late_printed_styles/?output_format=md#wp--skip-link--target)

Adds the hooks needed for CSS output to be delayed until after the content of the
page has been established.

## 󠀁[Description](https://developer.wordpress.org/reference/functions/wp_hoist_late_printed_styles/?output_format=md#description)󠁿

### 󠀁[See also](https://developer.wordpress.org/reference/functions/wp_hoist_late_printed_styles/?output_format=md#see-also)󠁿

 * [wp_load_classic_theme_block_styles_on_demand()](https://developer.wordpress.org/reference/functions/wp_load_classic_theme_block_styles_on_demand/)
 * [_wp_footer_scripts()](https://developer.wordpress.org/reference/functions/_wp_footer_scripts/)

## 󠀁[Source](https://developer.wordpress.org/reference/functions/wp_hoist_late_printed_styles/?output_format=md#source)󠁿

    ```php
    function wp_hoist_late_printed_styles(): void {
    	// Skip the embed template on-demand styles aren't relevant, and there is no wp_head action.
    	if ( is_embed() ) {
    		return;
    	}

    	/*
    	 * Add a placeholder comment into the inline styles for wp-block-library, after which the late block styles
    	 * can be hoisted from the footer to be printed in the header by means of a filter below on the template enhancement
    	 * output buffer.
    	 *
    	 * Note that wp_maybe_inline_styles() prepends the inlined style to the extra 'after' array, which happens after
    	 * this code runs. This ensures that the placeholder appears right after any inlined wp-block-library styles,
    	 * which would be common.css.
    	 */
    	$placeholder = sprintf( '/*%s*/', uniqid( 'wp_block_styles_on_demand_placeholder:' ) );
    	$dependency  = wp_styles()->query( 'wp-block-library', 'registered' );
    	if ( $dependency ) {
    		if ( ! isset( $dependency->extra['after'] ) ) {
    			wp_add_inline_style( 'wp-block-library', $placeholder );
    		} else {
    			array_unshift( $dependency->extra['after'], $placeholder );
    		}
    	}

    	/*
    	 * Create a substitute for `print_late_styles()` which is aware of block styles. This substitute does not print
    	 * the styles, but it captures what would be printed for block styles and non-block styles so that they can be
    	 * later hoisted to the HEAD in the template enhancement output buffer. This will run at `wp_print_footer_scripts`
    	 * before `print_footer_scripts()` is called.
    	 */
    	$printed_core_block_styles  = '';
    	$printed_other_block_styles = '';
    	$printed_global_styles      = '';
    	$printed_late_styles        = '';

    	$capture_late_styles = static function () use ( &$printed_core_block_styles, &$printed_other_block_styles, &$printed_global_styles, &$printed_late_styles ) {
    		// Gather the styles related to on-demand block enqueues.
    		$all_core_block_style_handles  = array();
    		$all_other_block_style_handles = array();
    		foreach ( WP_Block_Type_Registry::get_instance()->get_all_registered() as $block_type ) {
    			if ( str_starts_with( $block_type->name, 'core/' ) ) {
    				foreach ( $block_type->style_handles as $style_handle ) {
    					$all_core_block_style_handles[] = $style_handle;
    				}
    			} else {
    				foreach ( $block_type->style_handles as $style_handle ) {
    					$all_other_block_style_handles[] = $style_handle;
    				}
    			}
    		}

    		/*
    		 * First print all styles related to core blocks which should be inserted right after the wp-block-library stylesheet
    		 * to preserve the CSS cascade. The logic in this `if` statement is derived from `wp_print_styles()`.
    		 */
    		$enqueued_core_block_styles = array_values( array_intersect( $all_core_block_style_handles, wp_styles()->queue ) );
    		if ( count( $enqueued_core_block_styles ) > 0 ) {
    			ob_start();
    			wp_styles()->do_items( $enqueued_core_block_styles );
    			$printed_core_block_styles = (string) ob_get_clean();
    		}

    		// Capture non-core block styles so they can get printed at the point where wp_enqueue_registered_block_scripts_and_styles() runs.
    		$enqueued_other_block_styles = array_values( array_intersect( $all_other_block_style_handles, wp_styles()->queue ) );
    		if ( count( $enqueued_other_block_styles ) > 0 ) {
    			ob_start();
    			wp_styles()->do_items( $enqueued_other_block_styles );
    			$printed_other_block_styles = (string) ob_get_clean();
    		}

    		// Capture the global-styles so that it can be printed at the point where wp_enqueue_global_styles() runs.
    		if ( wp_style_is( 'global-styles' ) ) {
    			ob_start();
    			wp_styles()->do_items( array( 'global-styles' ) );
    			$printed_global_styles = (string) ob_get_clean();
    		}

    		/*
    		 * Print all remaining styles not related to blocks. This contains a subset of the logic from
    		 * `print_late_styles()`, without admin-specific logic and the `print_late_styles` filter to control whether
    		 * late styles are printed (since they are being hoisted anyway).
    		 */
    		ob_start();
    		wp_styles()->do_footer_items();
    		$printed_late_styles = (string) ob_get_clean();
    	};

    	/*
    	 * If `_wp_footer_scripts()` was unhooked from the `wp_print_footer_scripts` action, or if `wp_print_footer_scripts()`
    	 * was unhooked from running at the `wp_footer` action, then only add a callback to `wp_footer` which will capture the
    	 * late-printed styles.
    	 *
    	 * Otherwise, in the normal case where `_wp_footer_scripts()` will run at the `wp_print_footer_scripts` action, then
    	 * swap out `_wp_footer_scripts()` with an alternative which captures the printed styles (for hoisting to HEAD) before
    	 * proceeding with printing the footer scripts.
    	 */
    	$wp_print_footer_scripts_priority = has_action( 'wp_print_footer_scripts', '_wp_footer_scripts' );
    	if ( false === $wp_print_footer_scripts_priority || false === has_action( 'wp_footer', 'wp_print_footer_scripts' ) ) {
    		// The normal priority for wp_print_footer_scripts() is to run at 20.
    		add_action( 'wp_footer', $capture_late_styles, 20 );
    	} else {
    		remove_action( 'wp_print_footer_scripts', '_wp_footer_scripts', $wp_print_footer_scripts_priority );
    		add_action(
    			'wp_print_footer_scripts',
    			static function () use ( $capture_late_styles ) {
    				$capture_late_styles();
    				print_footer_scripts();
    			},
    			$wp_print_footer_scripts_priority
    		);
    	}

    	// Replace placeholder with the captured late styles.
    	add_filter(
    		'wp_template_enhancement_output_buffer',
    		static function ( $buffer ) use ( $placeholder, &$printed_core_block_styles, &$printed_other_block_styles, &$printed_global_styles, &$printed_late_styles ) {

    			// Anonymous subclass of WP_HTML_Tag_Processor which exposes underlying bookmark spans.
    			$processor = new class( $buffer ) extends WP_HTML_Tag_Processor {
    				/**
    				 * Gets the span for the current token.
    				 *
    				 * @return WP_HTML_Span Current token span.
    				 */
    				private function get_span(): WP_HTML_Span {
    					// Note: This call will never fail according to the usage of this class, given it is always called after ::next_tag() is true.
    					$this->set_bookmark( 'here' );
    					return $this->bookmarks['here'];
    				}

    				/**
    				 * Inserts text before the current token.
    				 *
    				 * @param string $text Text to insert.
    				 */
    				public function insert_before( string $text ): void {
    					$this->lexical_updates[] = new WP_HTML_Text_Replacement( $this->get_span()->start, 0, $text );
    				}

    				/**
    				 * Inserts text after the current token.
    				 *
    				 * @param string $text Text to insert.
    				 */
    				public function insert_after( string $text ): void {
    					$span = $this->get_span();

    					$this->lexical_updates[] = new WP_HTML_Text_Replacement( $span->start + $span->length, 0, $text );
    				}

    				/**
    				 * Removes the current token.
    				 */
    				public function remove(): void {
    					$span = $this->get_span();

    					$this->lexical_updates[] = new WP_HTML_Text_Replacement( $span->start, $span->length, '' );
    				}

    				/**
    				 * Replaces the current token.
    				 *
    				 * @param string $text Text to replace with.
    				 */
    				public function replace( string $text ): void {
    					$span = $this->get_span();

    					$this->lexical_updates[] = new WP_HTML_Text_Replacement( $span->start, $span->length, $text );
    				}
    			};

    			// Locate the insertion points in the HEAD.
    			while ( $processor->next_tag( array( 'tag_closers' => 'visit' ) ) ) {
    				if (
    					'STYLE' === $processor->get_tag() &&
    					'wp-global-styles-placeholder-inline-css' === $processor->get_attribute( 'id' )
    				) {
    					/** This is added in wp_enqueue_global_styles() */
    					$processor->set_bookmark( 'wp_global_styles_placeholder' );
    				} elseif (
    					'STYLE' === $processor->get_tag() &&
    					'wp-block-styles-placeholder-inline-css' === $processor->get_attribute( 'id' )
    				) {
    					/** This is added in wp_enqueue_registered_block_scripts_and_styles() */
    					$processor->set_bookmark( 'wp_block_styles_placeholder' );
    				} elseif (
    					'STYLE' === $processor->get_tag() &&
    					'wp-block-library-inline-css' === $processor->get_attribute( 'id' )
    				) {
    					/** This is added here in wp_hoist_late_printed_styles() */
    					$processor->set_bookmark( 'wp_block_library' );
    				} elseif ( 'HEAD' === $processor->get_tag() && $processor->is_tag_closer() ) {
    					$processor->set_bookmark( 'head_end' );
    					break;
    				}
    			}

    			/**
    			 * Replace the placeholder for global styles enqueued during wp_enqueue_global_styles(). This is done
    			 * even if $printed_global_styles is empty.
    			 */
    			if ( $processor->has_bookmark( 'wp_global_styles_placeholder' ) ) {
    				$processor->seek( 'wp_global_styles_placeholder' );
    				$processor->replace( $printed_global_styles );
    				$printed_global_styles = '';
    			}

    			/*
    			 * Insert block styles right after wp-block-library (if it is present). The placeholder CSS comment will
    			 * always be added to the wp-block-library inline style since it gets printed at `wp_head` before the blocks
    			 * are rendered. This means that there may not actually be any block styles to hoist from the footer to
    			 * insert after this inline style. The placeholder CSS comment needs to be added so that the inline style
    			 * gets printed, but if the resulting inline style is empty after the placeholder is removed, then the
    			 * inline style is removed.
    			 */
    			if ( $processor->has_bookmark( 'wp_block_library' ) ) {
    				$processor->seek( 'wp_block_library' );

    				$css_text = $processor->get_modifiable_text();

    				/*
    				 * Split the block library inline style by the placeholder to identify the original inlined CSS, which
    				 * likely would be common.css, followed by any inline styles which had been added by the theme or
    				 * plugins via `wp_add_inline_style( 'wp-block-library', '...' )`. The separate block styles loaded on
    				 * demand will get inserted after the inlined common.css and before the extra inline styles added by the
    				 * user.
    				 */
    				$css_text_around_placeholder = explode( $placeholder, $css_text, 2 );
    				$extra_inline_styles         = '';
    				if ( count( $css_text_around_placeholder ) === 2 ) {
    					$css_text = $css_text_around_placeholder[0];
    					if ( '' !== trim( $css_text ) ) {
    						$inlined_src = wp_styles()->get_data( 'wp-block-library', 'inlined_src' );
    						if ( $inlined_src ) {
    							$css_text .= sprintf(
    								"\n/*# sourceURL=%s */\n",
    								esc_url_raw( $inlined_src )
    							);
    						}
    					}
    					$extra_inline_styles = $css_text_around_placeholder[1];
    				}

    				/*
    				 * The placeholder CSS comment was added to the inline style in order to force an inline STYLE tag to
    				 * be printed. Now that the inline style has been located and the placeholder comment has been removed, if
    				 * there is no CSS left in the STYLE tag after removal, then remove the STYLE tag entirely.
    				 */
    				if ( '' === trim( $css_text ) ) {
    					$processor->remove();
    				} else {
    					$processor->set_modifiable_text( $css_text );
    				}

    				$inserted_after            = $printed_core_block_styles;
    				$printed_core_block_styles = '';

    				/*
    				 * Add a new inline style for any user styles added via wp_add_inline_style( 'wp-block-library', '...' ).
    				 * This must be added here after $printed_core_block_styles to preserve the original CSS cascade when
    				 * the combined block library stylesheet was used. The pattern here is checking to see if it is not just
    				 * a sourceURL comment after the placeholder above is removed.
    				 */
    				if ( ! preg_match( ':^\s*(/\*# sourceURL=\S+? \*/\s*)?$:s', $extra_inline_styles ) ) {
    					$style_processor = new WP_HTML_Tag_Processor( '<style></style>' );
    					$style_processor->next_tag();
    					$style_processor->set_attribute( 'id', 'wp-block-library-inline-css-extra' );
    					$style_processor->set_modifiable_text( $extra_inline_styles );
    					$inserted_after .= "{$style_processor->get_updated_html()}\n";
    				}

    				if ( '' !== $inserted_after ) {
    					$processor->insert_after( "\n" . $inserted_after );
    				}
    			}

    			// Insert block styles at the point where wp_enqueue_registered_block_scripts_and_styles() normally enqueues styles.
    			if ( $processor->has_bookmark( 'wp_block_styles_placeholder' ) ) {
    				$processor->seek( 'wp_block_styles_placeholder' );
    				if ( '' !== $printed_other_block_styles ) {
    					$processor->replace( "\n" . $printed_other_block_styles );
    				} else {
    					$processor->remove();
    				}
    				$printed_other_block_styles = '';
    			}

    			// Print all remaining styles.
    			$remaining_styles = $printed_core_block_styles . $printed_other_block_styles . $printed_global_styles . $printed_late_styles;
    			if ( $remaining_styles && $processor->has_bookmark( 'head_end' ) ) {
    				$processor->seek( 'head_end' );
    				$processor->insert_before( $remaining_styles . "\n" );
    			}
    			return $processor->get_updated_html();
    		}
    	);
    }
    ```

[View all references](https://developer.wordpress.org/reference/files/wp-includes/script-loader.php/)
[View on Trac](https://core.trac.wordpress.org/browser/tags/7.1/src/wp-includes/script-loader.php#L3743)
[View on GitHub](https://github.com/WordPress/wordpress-develop/blob/7.1/src/wp-includes/script-loader.php#L3743-L4040)

## 󠀁[Related](https://developer.wordpress.org/reference/functions/wp_hoist_late_printed_styles/?output_format=md#related)󠁿

| Uses | Description | 
| [WP_HTML_Tag_Processor::__construct()](https://developer.wordpress.org/reference/classes/wp_html_tag_processor/__construct/)`wp-includes/html-api/class-wp-html-tag-processor.php` |

Constructor.

  | 
| [WP_Block_Type_Registry::get_instance()](https://developer.wordpress.org/reference/classes/wp_block_type_registry/get_instance/)`wp-includes/class-wp-block-type-registry.php` |

Utility method to retrieve the main instance of the class.

  | 
| [is_embed()](https://developer.wordpress.org/reference/functions/is_embed/)`wp-includes/query.php` |

Is the query for an embedded post?

  | 
| [wp_styles()](https://developer.wordpress.org/reference/functions/wp_styles/)`wp-includes/functions.wp-styles.php` |

Initializes $wp_styles if it has not been set.

  | 
| [WP_Styles::do_footer_items()](https://developer.wordpress.org/reference/classes/wp_styles/do_footer_items/)`wp-includes/class-wp-styles.php` |

Processes items and dependencies for the footer group.

  | 
| [esc_url_raw()](https://developer.wordpress.org/reference/functions/esc_url_raw/)`wp-includes/formatting.php` |

Sanitizes a URL for database or redirect usage.

  | 
| [wp_add_inline_style()](https://developer.wordpress.org/reference/functions/wp_add_inline_style/)`wp-includes/functions.wp-styles.php` |

Adds extra CSS styles to a registered stylesheet.

  | 
| [wp_style_is()](https://developer.wordpress.org/reference/functions/wp_style_is/)`wp-includes/functions.wp-styles.php` |

Checks whether a CSS stylesheet has been added to the queue.

  | 
| [has_action()](https://developer.wordpress.org/reference/functions/has_action/)`wp-includes/plugin.php` |

Checks if any action has been registered for a hook.

  | 
| [remove_action()](https://developer.wordpress.org/reference/functions/remove_action/)`wp-includes/plugin.php` |

Removes a callback function from an action hook.

  | 
| [print_footer_scripts()](https://developer.wordpress.org/reference/functions/print_footer_scripts/)`wp-includes/script-loader.php` |

Prints the scripts that were queued for the footer or too late for the HTML head.

  | 
| [add_action()](https://developer.wordpress.org/reference/functions/add_action/)`wp-includes/plugin.php` |

Adds a callback function to an action hook.

  | 
| [add_filter()](https://developer.wordpress.org/reference/functions/add_filter/)`wp-includes/plugin.php` |

Adds a callback function to a filter hook.

  |

[Show 8 more](https://developer.wordpress.org/reference/functions/wp_hoist_late_printed_styles/?output_format=md#)
[Show less](https://developer.wordpress.org/reference/functions/wp_hoist_late_printed_styles/?output_format=md#)

## 󠀁[Changelog](https://developer.wordpress.org/reference/functions/wp_hoist_late_printed_styles/?output_format=md#changelog)󠁿

| Version | Description | 
| [6.9.0](https://developer.wordpress.org/reference/since/6.9.0/) | Introduced. |

## User Contributed Notes

You must [log in](https://login.wordpress.org/?redirect_to=https%3A%2F%2Fdeveloper.wordpress.org%2Freference%2Ffunctions%2Fwp_hoist_late_printed_styles%2F)
before being able to contribute a note or feedback.