diff --git a/src/wp-includes/block-supports/background.php b/src/wp-includes/block-supports/background.php
index cc4d451e1d8bd..bece55d80fca9 100644
--- a/src/wp-includes/block-supports/background.php
+++ b/src/wp-includes/block-supports/background.php
@@ -43,6 +43,7 @@ function wp_register_background_support( $block_type ) {
* @since 6.5.0 Added support for `backgroundPosition` and `backgroundRepeat` output.
* @since 6.6.0 Removed requirement for `backgroundImage.source`. A file/url is the default.
* @since 6.7.0 Added support for `backgroundAttachment` output.
+ * @since 6.8.0 Added support for rendering background images as elements.
*
* @access private
*
@@ -79,6 +80,84 @@ function wp_render_background_support( $block_content, $block ) {
}
}
+ /*
+ * Use an
element for media library images with cover/contain sizing and no tiling,
+ * so the browser can benefit from srcset, sizes, loading="lazy", and decoding="async".
+ * 'no-repeat' is treated the same as unset — both are compatible with object-fit.
+ * Only explicit tiling values (repeat, repeat-x, repeat-y) require CSS background-image.
+ */
+ $attachment_id = is_array( $background_styles['backgroundImage'] )
+ ? (int) ( $background_styles['backgroundImage']['id'] ?? 0 )
+ : 0;
+
+ $use_img_element = (
+ $attachment_id > 0 &&
+ in_array( $background_styles['backgroundSize'], array( 'cover', 'contain' ), true ) &&
+ ( empty( $background_styles['backgroundRepeat'] ) || 'no-repeat' === $background_styles['backgroundRepeat'] ) &&
+ empty( $background_styles['backgroundAttachment'] )
+ );
+
+ if ( $use_img_element ) {
+ $object_fit = $background_styles['backgroundSize'];
+ $object_position = $background_styles['backgroundPosition'] ?? null;
+
+ $img_style = 'position:absolute;top:0;left:0;right:0;bottom:0;margin:0;padding:0;width:100%;height:100%;max-width:none;max-height:none;pointer-events:none;object-fit:' . esc_attr( $object_fit ) . ';';
+ if ( $object_position ) {
+ $img_style .= 'object-position:' . esc_attr( $object_position ) . ';';
+ }
+
+ $img_attrs = array(
+ 'class' => 'wp-block__background-image',
+ 'style' => $img_style,
+ 'alt' => '',
+ 'aria-hidden' => 'true',
+ 'data-object-fit' => $object_fit,
+ 'loading' => 'lazy',
+ 'decoding' => 'async',
+ );
+ if ( $object_position ) {
+ $img_attrs['data-object-position'] = $object_position;
+ }
+
+ $img_html = wp_get_attachment_image( $attachment_id, 'full', false, $img_attrs );
+
+ if ( $img_html ) {
+ $tags = new WP_HTML_Tag_Processor( $block_content );
+ if ( $tags->next_tag() ) {
+ $tag_name = strtolower( $tags->get_tag() );
+ $existing_style = $tags->get_attribute( 'style' );
+
+ if ( is_string( $existing_style ) && '' !== $existing_style ) {
+ $separator = str_ends_with( $existing_style, ';' ) ? '' : ';';
+ // Only add position:relative when there is no existing position rule.
+ if ( ! preg_match( '/(?:^|;)\s*position\s*:/', $existing_style ) ) {
+ $wrapper_style = $existing_style . $separator . 'position:relative;';
+ } else {
+ $wrapper_style = rtrim( $existing_style, ';' ) . ';';
+ }
+ } else {
+ $wrapper_style = 'position:relative;';
+ }
+
+ $tags->set_attribute( 'style', $wrapper_style );
+ $tags->add_class( 'has-background' );
+ }
+ $modified_content = $tags->get_updated_html();
+
+ // Insert the img as the first child of the wrapper element.
+ // Find the end of the opening tag by locating the first '>'. The
+ // WP_HTML_Tag_Processor guarantees attribute values are properly
+ // escaped, so the first raw '>' is always the closing bracket of
+ // the opening wrapper tag.
+ $close_bracket = strpos( $modified_content, '>' );
+ if ( false !== $close_bracket ) {
+ $modified_content = substr( $modified_content, 0, $close_bracket + 1 ) . $img_html . substr( $modified_content, $close_bracket + 1 );
+ }
+
+ return $modified_content;
+ }
+ }
+
$styles = wp_style_engine_get_styles( array( 'background' => $background_styles ) );
if ( ! empty( $styles['css'] ) ) {
diff --git a/tests/phpunit/tests/block-supports/wpRenderBackgroundSupport.php b/tests/phpunit/tests/block-supports/wpRenderBackgroundSupport.php
index 5a4faf46085c0..98a08bccb59d4 100644
--- a/tests/phpunit/tests/block-supports/wpRenderBackgroundSupport.php
+++ b/tests/phpunit/tests/block-supports/wpRenderBackgroundSupport.php
@@ -213,6 +213,160 @@ public function data_background_block_support() {
'expected_wrapper' => '