Add class whitespace doc#37
Conversation
Note that the `class_name` argument to `WP_HTML_Tag_Processor::add_class()` and `WP_HTML_Processor::add_class()` is inserted verbatim into the `class` attribute, which is a whitespace-separated token list. Whitespace in the value therefore adds multiple classes; a single class whose name contains whitespace cannot be expressed.
Drop the inline enumeration of HTML ASCII whitespace code points and shorten the explanation; keep the worked single- and multi-class examples.
Identify the term and list the characters (tab, line feed, form feed, carriage return, space) so readers know exactly what splits a value into multiple class names.
Replace the paraphrased whitespace list with a blockquote of the Infra Standard definition and add a `@see` link to the source.
Adopt the simpler note on the Tag Processor and mirror it on the HTML Processor so both `add_class()` docblocks read the same.
Rewrite the lead sentence so it parses on one read, and restore the explicit "not one class" cue on the multi-class example so the docblock directly contradicts the natural misreading.
State directly that the value is added verbatim and may yield multiple class names in the element's class list — "may" because trailing or leading whitespace alone still produces a single class.
The body prose already names ASCII whitespace and explains the multi-class effect, so the `@see` link and expanded `@param` description are redundant. Restore the original `@param` text.
There was a problem hiding this comment.
Code Review
This pull request introduces support for responsive breakpoint states (such as mobile and tablet media queries) within the WP_Theme_JSON class, updating style compilation, sanitization, and variation handling accordingly. It also updates global variable PHPDoc annotations in the Twenty Eleven theme and clarifies class name handling in the HTML API processors. Feedback on these changes focuses on adding robust array type guards (using is_array) when accessing breakpoint nodes, nested elements, spacing, and pseudo-selectors to prevent PHP warnings or type errors on malformed theme.json structures.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
I am having trouble creating individual review comments. Click here to see my feedback.
src/wp-includes/class-wp-theme-json.php (2949-2950)
Ensure that the breakpoint node is an array before processing it, to prevent potential type errors or warnings if the theme.json structure is malformed.
foreach ( array_keys( static::RESPONSIVE_BREAKPOINTS ) as $breakpoint ) {
if ( isset( $theme_json['styles']['blocks'][ $name ][ $breakpoint ] ) && is_array( $theme_json['styles']['blocks'][ $name ][ $breakpoint ] ) ) {src/wp-includes/class-wp-theme-json.php (2969-2974)
Safely access the breakpoint node as an array to avoid warnings when checking for pseudo-selectors on malformed or non-array breakpoint nodes.
foreach ( array_keys( static::RESPONSIVE_BREAKPOINTS ) as $breakpoint ) {
$bp_node = $theme_json['styles']['blocks'][ $name ][ $breakpoint ] ?? null;
if ( is_array( $bp_node ) && isset( $bp_node[ $pseudo_selector ] ) ) {
$has_responsive_pseudo = true;
break;
}
}src/wp-includes/class-wp-theme-json.php (3014-3027)
Use a safe array guard for the breakpoint node before checking for pseudo-selectors to prevent PHP warnings on malformed inputs.
foreach ( array_keys( static::RESPONSIVE_BREAKPOINTS ) as $breakpoint ) {
$bp_node = $theme_json['styles']['blocks'][ $name ][ $breakpoint ] ?? null;
if ( is_array( $bp_node ) && isset( $bp_node[ $pseudo_selector ] ) ) {
$nodes[] = array(
'name' => $name,
'path' => array( 'styles', 'blocks', $name, $breakpoint, $pseudo_selector ),
'media_query' => static::RESPONSIVE_BREAKPOINTS[ $breakpoint ],
'selector' => static::append_to_selector( $selector, $pseudo_selector ),
'selectors' => $pseudo_feature_selectors,
'elements' => $selectors[ $name ]['elements'] ?? array(),
'variations' => $variation_selectors,
'css' => static::append_to_selector( $selector, $pseudo_selector ),
);
}
}src/wp-includes/class-wp-theme-json.php (3050-3058)
Guard against non-array breakpoint nodes when checking for nested elements to prevent PHP warnings.
foreach ( array_keys( static::RESPONSIVE_BREAKPOINTS ) as $breakpoint ) {
$bp_node = $theme_json['styles']['blocks'][ $name ][ $breakpoint ] ?? null;
if ( is_array( $bp_node ) && isset( $bp_node['elements'][ $element ] ) ) {
$nodes[] = array(
'path' => array( 'styles', 'blocks', $name, $breakpoint, 'elements', $element ),
'selector' => $element_selector,
'media_query' => static::RESPONSIVE_BREAKPOINTS[ $breakpoint ],
);
}
}src/wp-includes/class-wp-theme-json.php (3065-3072)
Ensure the breakpoint node is an array before checking for nested element pseudo-selectors.
if ( ! $has_element_pseudo ) {
foreach ( array_keys( static::RESPONSIVE_BREAKPOINTS ) as $bp ) {
$bp_node = $theme_json['styles']['blocks'][ $name ][ $bp ] ?? null;
if ( is_array( $bp_node ) && isset( $bp_node['elements'][ $element ][ $pseudo_selector ] ) ) {
$has_element_pseudo = true;
break;
}
}
}src/wp-includes/class-wp-theme-json.php (3091-3099)
Safely check nested element pseudo-selectors on breakpoint nodes by ensuring they are arrays.
foreach ( array_keys( static::RESPONSIVE_BREAKPOINTS ) as $breakpoint ) {
$bp_node = $theme_json['styles']['blocks'][ $name ][ $breakpoint ] ?? null;
if ( is_array( $bp_node ) && isset( $bp_node['elements'][ $element ][ $pseudo_selector ] ) ) {
$nodes[] = array(
'path' => array( 'styles', 'blocks', $name, $breakpoint, 'elements', $element ),
'selector' => static::append_to_selector( $element_selector, $pseudo_selector ),
'media_query' => static::RESPONSIVE_BREAKPOINTS[ $breakpoint ],
);
}
}src/wp-includes/class-wp-theme-json.php (3211-3214)
Verify that the style variation breakpoint node is an array before proceeding to process its styles.
foreach ( array_keys( static::RESPONSIVE_BREAKPOINTS ) as $breakpoint ) {
if ( ! isset( $style_variation_node[ $breakpoint ] ) || ! is_array( $style_variation_node[ $breakpoint ] ) ) {
continue;
}src/wp-includes/class-wp-theme-json.php (3271-3281)
Ensure spacing is an array before checking for blockGap to avoid PHP warnings on malformed variation nodes.
if ( isset( $breakpoint_node['spacing'] ) && is_array( $breakpoint_node['spacing'] ) && isset( $breakpoint_node['spacing']['blockGap'] ) ) {
$variation_layout_metadata = $style_variation;
$variation_layout_metadata['selector'] = $style_variation['selector'] . $block_metadata['css'];
$variation_responsive_css .= $this->get_layout_styles(
$variation_layout_metadata,
array(
'node' => $breakpoint_node,
'media_query' => $breakpoint_media,
)
);
}src/wp-includes/class-wp-theme-json.php (3284-3288)
Ensure elements is an array and each element_node is an array before processing nested element styles.
if ( isset( $breakpoint_node['elements'] ) && is_array( $breakpoint_node['elements'] ) && ! empty( $block_elements ) ) {
foreach ( $breakpoint_node['elements'] as $element_name => $element_node ) {
if ( ! isset( $block_elements[ $element_name ] ) || ! is_array( $element_node ) ) {
continue;
}src/wp-includes/class-wp-theme-json.php (3312-3321)
Ensure that the pseudo-selector style definition is an array before passing it to compute_style_properties.
if ( isset( static::VALID_ELEMENT_PSEUDO_SELECTORS[ $element_name ] ) ) {
foreach ( static::VALID_ELEMENT_PSEUDO_SELECTORS[ $element_name ] as $pseudo_selector ) {
if ( ! isset( $element_node[ $pseudo_selector ] ) || ! is_array( $element_node[ $pseudo_selector ] ) ) {
continue;
}
$pseudo_declarations = static::compute_style_properties( $element_node[ $pseudo_selector ], $settings, null, $this->theme_json );
if ( empty( $pseudo_declarations ) ) {
continue;
}src/wp-includes/class-wp-theme-json.php (4060-4070)
Ensure the breakpoint node and its nested elements and blocks keys are arrays before calling sanitization methods.
foreach ( array_keys( static::RESPONSIVE_BREAKPOINTS ) as $breakpoint ) {
if ( isset( $input[ $breakpoint ] ) && is_array( $input[ $breakpoint ] ) ) {
$output[ $breakpoint ] = static::remove_insecure_styles( $input[ $breakpoint ] );
if ( isset( $input[ $breakpoint ]['elements'] ) && is_array( $input[ $breakpoint ]['elements'] ) ) {
$output[ $breakpoint ]['elements'] = static::remove_insecure_element_styles( $input[ $breakpoint ]['elements'] );
}
if ( isset( $input[ $breakpoint ]['blocks'] ) && is_array( $input[ $breakpoint ]['blocks'] ) ) {
$output[ $breakpoint ]['blocks'] = static::remove_insecure_inner_block_styles( $input[ $breakpoint ]['blocks'] );
}src/wp-includes/class-wp-theme-json.php (4109-4119)
Verify that the variation breakpoint node and its sub-keys are arrays before sanitizing them.
foreach ( array_keys( static::RESPONSIVE_BREAKPOINTS ) as $breakpoint ) {
if ( isset( $variation_input[ $breakpoint ] ) && is_array( $variation_input[ $breakpoint ] ) ) {
$variation_output[ $breakpoint ] = static::remove_insecure_styles( $variation_input[ $breakpoint ] );
if ( isset( $variation_input[ $breakpoint ]['elements'] ) && is_array( $variation_input[ $breakpoint ]['elements'] ) ) {
$variation_output[ $breakpoint ]['elements'] = static::remove_insecure_element_styles( $variation_input[ $breakpoint ]['elements'] );
}
if ( isset( $variation_input[ $breakpoint ]['blocks'] ) && is_array( $variation_input[ $breakpoint ]['blocks'] ) ) {
$variation_output[ $breakpoint ]['blocks'] = static::remove_insecure_inner_block_styles( $variation_input[ $breakpoint ]['blocks'] );
}src/wp-includes/class-wp-theme-json.php (4197-4209)
Ensure the element breakpoint node and its pseudo-selectors are arrays before processing them.
foreach ( array_keys( static::RESPONSIVE_BREAKPOINTS ) as $breakpoint ) {
if ( isset( $element_input[ $breakpoint ] ) && is_array( $element_input[ $breakpoint ] ) ) {
$element_output[ $breakpoint ] = static::remove_insecure_styles( $element_input[ $breakpoint ] );
if ( isset( static::VALID_ELEMENT_PSEUDO_SELECTORS[ $element_name ] ) ) {
foreach ( static::VALID_ELEMENT_PSEUDO_SELECTORS[ $element_name ] as $pseudo_selector ) {
if ( isset( $element_input[ $breakpoint ][ $pseudo_selector ] ) && is_array( $element_input[ $breakpoint ][ $pseudo_selector ] ) ) {
$element_output[ $breakpoint ][ $pseudo_selector ] = static::remove_insecure_styles( $element_input[ $breakpoint ][ $pseudo_selector ] );
}
}
}
}
}src/wp-includes/class-wp-theme-json.php (4235-4247)
Ensure the inner block breakpoint node and its pseudo-selectors are arrays before processing them.
foreach ( array_keys( static::RESPONSIVE_BREAKPOINTS ) as $breakpoint ) {
if ( isset( $block_input[ $breakpoint ] ) && is_array( $block_input[ $breakpoint ] ) ) {
$block_output[ $breakpoint ] = static::remove_insecure_styles( $block_input[ $breakpoint ] );
if ( isset( static::VALID_BLOCK_PSEUDO_SELECTORS[ $block_type ] ) ) {
foreach ( static::VALID_BLOCK_PSEUDO_SELECTORS[ $block_type ] as $pseudo_selector ) {
if ( isset( $block_input[ $breakpoint ][ $pseudo_selector ] ) && is_array( $block_input[ $breakpoint ][ $pseudo_selector ] ) ) {
$block_output[ $breakpoint ][ $pseudo_selector ] = static::remove_insecure_styles( $block_input[ $breakpoint ][ $pseudo_selector ] );
}
}
}
}
}…e value. The class name itself is HTML-escaped when written into the attribute; only the whitespace passes through unchanged, which is what splits the value into multiple class names.
…ace.
A class name cannot contain whitespace; the HTML class attribute is a
whitespace-separated token list. Pass-through whitespace in the
`$wanted_class` / `$class_name` argument can therefore never match a
real token, so reject it up front and return false. Document the new
behavior in both the Tag Processor and HTML Processor docblocks.
Behavior note: previously, `add_class('a b')` followed by
`remove_class('a b')` was an accidental no-op because the REMOVE
overwrote the ADD in the keyed updates map. With the short-circuit the
REMOVE is rejected and the ADD survives, so the element gets classes
"a" and "b" as the lone `add_class()` call would have produced.
The short-circuit is purely a performance refinement of an already guaranteed result (no token can contain whitespace), not a meaningful behavior change worth versioning. Keep the inline docblock note.
Add a `_doing_it_wrong()` call on the whitespace short-circuit in `has_class()` and `remove_class()` so developers passing a value that can never identify a class token see an actionable warning rather than a silent false.
… docblocks. The `_doing_it_wrong()` warning now communicates the constraint at the point of misuse, so the docblock note is redundant.
Trac ticket:
Use of AI Tools
This Pull Request is for code review only. Please keep all other discussion in the Trac ticket. Do not merge this Pull Request. See GitHub Pull Requests for Code Review in the Core Handbook for more details.