: str_replace(): Passing null to parameter #2 ($replace) of type array|string is deprecated in
} else if (getValueFromObjectPath(blockStyles, pathToValue, false)) {
const cssProperty = key.startsWith('--') ? key : use_global_styles_output_kebabCase(key);
declarations.push(`${cssProperty}: ${compileStyleValue(getValueFromObjectPath(blockStyles, pathToValue))}`);
// The goal is to move everything to server side generated engine styles
// This is temporary as we absorb more and more styles into the engine.
const extraRules = (0,external_wp_styleEngine_namespaceObject.getCSSRules)(blockStyles);
extraRules.forEach(rule => {
// Don't output padding properties if padding variables are set or if we're not editing a full template.
if (isRoot && (useRootPaddingAlign || disableRootPadding) && rule.key.startsWith('padding')) {
const cssProperty = rule.key.startsWith('--') ? rule.key : use_global_styles_output_kebabCase(rule.key);
let ruleValue = rule.value;
if (typeof ruleValue !== 'string' && ruleValue?.ref) {
const refPath = ruleValue.ref.split('.');
ruleValue = compileStyleValue(getValueFromObjectPath(tree, refPath));
// Presence of another ref indicates a reference to another dynamic value.
// Pointing to another dynamic value is not supported.
if (!ruleValue || ruleValue?.ref) {
// Calculate fluid typography rules where available.
if (cssProperty === 'font-size') {
* getTypographyFontSizeValue() will check
* if fluid typography has been activated and also
* whether the incoming value can be converted to a fluid value.
* Values that already have a "clamp()" function will not pass the test,
* and therefore the original $value will be returned.
ruleValue = getTypographyFontSizeValue({
// For aspect ratio to work, other dimensions rules (and Cover block defaults) must be unset.
// This ensures that a fixed height does not override the aspect ratio.
if (cssProperty === 'aspect-ratio') {
output.push('min-height: unset');
output.push(`${cssProperty}: ${ruleValue}`);
* Get generated CSS for layout styles by looking up layout definitions provided
* in theme.json, and outputting common layout styles, and specific blockGap values.
* @param {Object} props.layoutDefinitions Layout definitions, keyed by layout type.
* @param {Object} props.style A style object containing spacing values.
* @param {string} props.selector Selector used to group together layout styling rules.
* @param {boolean} props.hasBlockGapSupport Whether or not the theme opts-in to blockGap support.
* @param {boolean} props.hasFallbackGapSupport Whether or not the theme allows fallback gap styles.
* @param {?string} props.fallbackGapValue An optional fallback gap value if no real gap value is available.
* @return {string} Generated CSS rules for the layout styles.
function getLayoutStyles({
layoutDefinitions = LAYOUT_DEFINITIONS,
let gapValue = hasBlockGapSupport ? getGapCSSValue(style?.spacing?.blockGap) : '';
// Ensure a fallback gap value for the root layout definitions,
// and use a fallback value if one is provided for the current block.
if (hasFallbackGapSupport) {
if (selector === ROOT_BLOCK_SELECTOR) {
gapValue = !gapValue ? '0.5em' : gapValue;
} else if (!hasBlockGapSupport && fallbackGapValue) {
gapValue = fallbackGapValue;
if (gapValue && layoutDefinitions) {
Object.values(layoutDefinitions).forEach(({
// Allow outputting fallback gap styles for flex layout type when block gap support isn't available.
if (!hasBlockGapSupport && 'flex' !== name && 'grid' !== name) {
if (spacingStyles?.length) {
spacingStyles.forEach(spacingStyle => {
if (spacingStyle.rules) {
Object.entries(spacingStyle.rules).forEach(([cssProperty, cssValue]) => {
declarations.push(`${cssProperty}: ${cssValue ? cssValue : gapValue}`);
if (declarations.length) {
let combinedSelector = '';
if (!hasBlockGapSupport) {
// For fallback gap styles, use lower specificity, to ensure styles do not unintentionally override theme styles.
combinedSelector = selector === ROOT_BLOCK_SELECTOR ? `:where(.${className}${spacingStyle?.selector || ''})` : `:where(${selector}.${className}${spacingStyle?.selector || ''})`;
combinedSelector = selector === ROOT_BLOCK_SELECTOR ? `:root :where(.${className})${spacingStyle?.selector || ''}` : `:root :where(${selector}-${className})${spacingStyle?.selector || ''}`;
ruleset += `${combinedSelector} { ${declarations.join('; ')}; }`;
// For backwards compatibility, ensure the legacy block gap CSS variable is still available.
if (selector === ROOT_BLOCK_SELECTOR && hasBlockGapSupport) {
ruleset += `${ROOT_CSS_PROPERTIES_SELECTOR} { --wp--style--block-gap: ${gapValue}; }`;
if (selector === ROOT_BLOCK_SELECTOR && layoutDefinitions) {
const validDisplayModes = ['block', 'flex', 'grid'];
Object.values(layoutDefinitions).forEach(({
if (displayMode && validDisplayModes.includes(displayMode)) {
ruleset += `${selector} .${className} { display:${displayMode}; }`;
if (baseStyles?.length) {
baseStyles.forEach(baseStyle => {
Object.entries(baseStyle.rules).forEach(([cssProperty, cssValue]) => {
declarations.push(`${cssProperty}: ${cssValue}`);
if (declarations.length) {
const combinedSelector = `.${className}${baseStyle?.selector || ''}`;
ruleset += `${combinedSelector} { ${declarations.join('; ')}; }`;
const STYLE_KEYS = ['border', 'color', 'dimensions', 'spacing', 'typography', 'filter', 'outline', 'shadow', 'background'];
function pickStyleKeys(treeToPickFrom) {
const entries = Object.entries(treeToPickFrom);
const pickedEntries = entries.filter(([key]) => STYLE_KEYS.includes(key));
// clone the style objects so that `getFeatureDeclarations` can remove consumed keys from it
const clonedEntries = pickedEntries.map(([key, style]) => [key, JSON.parse(JSON.stringify(style))]);
return Object.fromEntries(clonedEntries);
const getNodesWithStyles = (tree, blockSelectors) => {
const styles = pickStyleKeys(tree.styles);
selector: ROOT_BLOCK_SELECTOR,
// Root selector (body) styles should not be wrapped in `:root where()` to keep
// specificity at (0,0,1) and maintain backwards compatibility.
skipSelectorWrapper: true
Object.entries(external_wp_blocks_namespaceObject.__EXPERIMENTAL_ELEMENTS).forEach(([name, selector]) => {
if (tree.styles?.elements?.[name]) {
styles: tree.styles?.elements?.[name],
// Top level elements that don't use a class name should not receive the
// `:root :where()` wrapper to maintain backwards compatibility.
skipSelectorWrapper: !ELEMENT_CLASS_NAMES[name]
// Iterate over blocks: they can have styles & elements.
Object.entries((_tree$styles$blocks = tree.styles?.blocks) !== null && _tree$styles$blocks !== void 0 ? _tree$styles$blocks : {}).forEach(([blockName, node]) => {
const blockStyles = pickStyleKeys(node);
Object.entries(node.variations).forEach(([variationName, variation]) => {
var _variation$elements, _variation$blocks;
variations[variationName] = pickStyleKeys(variation);
variations[variationName].css = variation.css;
const variationSelector = blockSelectors[blockName]?.styleVariationSelectors?.[variationName];
// Process the variation's inner element styles.
// This comes before the inner block styles so the
// element styles within the block type styles take
// precedence over these.
Object.entries((_variation$elements = variation?.elements) !== null && _variation$elements !== void 0 ? _variation$elements : {}).forEach(([element, elementStyles]) => {
if (elementStyles && external_wp_blocks_namespaceObject.__EXPERIMENTAL_ELEMENTS[element]) {
selector: scopeSelector(variationSelector, external_wp_blocks_namespaceObject.__EXPERIMENTAL_ELEMENTS[element])
// Process the variations inner block type styles.
Object.entries((_variation$blocks = variation?.blocks) !== null && _variation$blocks !== void 0 ? _variation$blocks : {}).forEach(([variationBlockName, variationBlockStyles]) => {
var _variationBlockStyles;
const variationBlockSelector = scopeSelector(variationSelector, blockSelectors[variationBlockName]?.selector);
const variationDuotoneSelector = scopeSelector(variationSelector, blockSelectors[variationBlockName]?.duotoneSelector);
const variationFeatureSelectors = scopeFeatureSelectors(variationSelector, blockSelectors[variationBlockName]?.featureSelectors);
const variationBlockStyleNodes = pickStyleKeys(variationBlockStyles);
if (variationBlockStyles?.css) {
variationBlockStyleNodes.css = variationBlockStyles.css;
selector: variationBlockSelector,
duotoneSelector: variationDuotoneSelector,
featureSelectors: variationFeatureSelectors,
fallbackGapValue: blockSelectors[variationBlockName]?.fallbackGapValue,
hasLayoutSupport: blockSelectors[variationBlockName]?.hasLayoutSupport,
styles: variationBlockStyleNodes
// Process element styles for the inner blocks
Object.entries((_variationBlockStyles = variationBlockStyles.elements) !== null && _variationBlockStyles !== void 0 ? _variationBlockStyles : {}).forEach(([variationBlockElement, variationBlockElementStyles]) => {
if (variationBlockElementStyles && external_wp_blocks_namespaceObject.__EXPERIMENTAL_ELEMENTS[variationBlockElement]) {
styles: variationBlockElementStyles,
selector: scopeSelector(variationBlockSelector, external_wp_blocks_namespaceObject.__EXPERIMENTAL_ELEMENTS[variationBlockElement])
blockStyles.variations = variations;
if (blockSelectors?.[blockName]?.selector) {
duotoneSelector: blockSelectors[blockName].duotoneSelector,
fallbackGapValue: blockSelectors[blockName].fallbackGapValue,
hasLayoutSupport: blockSelectors[blockName].hasLayoutSupport,
selector: blockSelectors[blockName].selector,
featureSelectors: blockSelectors[blockName].featureSelectors,
styleVariationSelectors: blockSelectors[blockName].styleVariationSelectors
Object.entries((_node$elements = node?.elements) !== null && _node$elements !== void 0 ? _node$elements : {}).forEach(([elementName, value]) => {
if (value && blockSelectors?.[blockName] && external_wp_blocks_namespaceObject.__EXPERIMENTAL_ELEMENTS[elementName]) {
selector: blockSelectors[blockName]?.selector.split(',').map(sel => {
const elementSelectors = external_wp_blocks_namespaceObject.__EXPERIMENTAL_ELEMENTS[elementName].split(',');
return elementSelectors.map(elementSelector => sel + ' ' + elementSelector);
const getNodesWithSettings = (tree, blockSelectors) => {
var _tree$settings$blocks;
const pickPresets = treeToPickFrom => {
PRESET_METADATA.forEach(({
const value = getValueFromObjectPath(treeToPickFrom, path, false);
presets = setImmutably(presets, path, value);
const presets = pickPresets(tree.settings);
const custom = tree.settings?.custom;
if (Object.keys(presets).length > 0 || custom) {
selector: ROOT_CSS_PROPERTIES_SELECTOR
Object.entries((_tree$settings$blocks = tree.settings?.blocks) !== null && _tree$settings$blocks !== void 0 ? _tree$settings$blocks : {}).forEach(([blockName, node]) => {
const blockPresets = pickPresets(node);
const blockCustom = node.custom;
if (Object.keys(blockPresets).length > 0 || blockCustom) {
selector: blockSelectors[blockName]?.selector
const toCustomProperties = (tree, blockSelectors) => {
const settings = getNodesWithSettings(tree, blockSelectors);
const declarations = getPresetsDeclarations(presets, tree?.settings);
const customProps = flattenTree(custom, '--wp--custom--', '--');
if (customProps.length > 0) {
declarations.push(...customProps);
if (declarations.length > 0) {
ruleset += `${selector}{${declarations.join(';')};}`;
const toStyles = (tree, blockSelectors, hasBlockGapSupport, hasFallbackGapSupport, disableLayoutStyles = false, disableRootPadding = false, styleOptions = undefined) => {
// These allow opting out of certain sets of styles.
const nodesWithStyles = getNodesWithStyles(tree, blockSelectors);
const nodesWithSettings = getNodesWithSettings(tree, blockSelectors);
const useRootPaddingAlign = tree?.settings?.useRootPaddingAwareAlignments;
} = tree?.settings?.layout || {};
const hasBodyStyles = options.marginReset || options.rootPadding || options.layoutStyles;
if (options.presets && (contentSize || wideSize)) {
ruleset += `${ROOT_CSS_PROPERTIES_SELECTOR} {`;
ruleset = contentSize ? ruleset + ` --wp--style--global--content-size: ${contentSize};` : ruleset;
ruleset = wideSize ? ruleset + ` --wp--style--global--wide-size: ${wideSize};` : ruleset;
* Reset default browser margin on the body element.
* This is set on the body selector **before** generating the ruleset
* from the `theme.json`. This is to ensure that if the `theme.json` declares
* `margin` in its `spacing` declaration for the `body` element then these
* user-generated values take precedence in the CSS cascade.
* @link https://github.com/WordPress/gutenberg/issues/36147.
ruleset += ':where(body) {margin: 0;';
// Root padding styles should be output for full templates, patterns and template parts.
if (options.rootPadding && useRootPaddingAlign) {
* These rules reproduce the ones from https://github.com/WordPress/gutenberg/blob/79103f124925d1f457f627e154f52a56228ed5ad/lib/class-wp-theme-json-gutenberg.php#L2508
* almost exactly, but for the selectors that target block wrappers in the front end. This code only runs in the editor, so it doesn't need those selectors.
ruleset += `padding-right: 0; padding-left: 0; padding-top: var(--wp--style--root--padding-top); padding-bottom: var(--wp--style--root--padding-bottom) }
.has-global-padding { padding-right: var(--wp--style--root--padding-right); padding-left: var(--wp--style--root--padding-left); }
.has-global-padding > .alignfull { margin-right: calc(var(--wp--style--root--padding-right) * -1); margin-left: calc(var(--wp--style--root--padding-left) * -1); }
.has-global-padding :where(:not(.alignfull.is-layout-flow) > .has-global-padding:not(.wp-block-block, .alignfull)) { padding-right: 0; padding-left: 0; }
.has-global-padding :where(:not(.alignfull.is-layout-flow) > .has-global-padding:not(.wp-block-block, .alignfull)) > .alignfull { margin-left: 0; margin-right: 0;
if (options.blockStyles) {
nodesWithStyles.forEach(({
// Process styles for block support features with custom feature level
const featureDeclarations = getFeatureDeclarations(featureSelectors, styles);
Object.entries(featureDeclarations).forEach(([cssSelector, declarations]) => {
if (declarations.length) {
const rules = declarations.join(';');
ruleset += `:root :where(${cssSelector}){${rules};}`;
// Process duotone styles.
const duotoneStyles = {};
duotoneStyles.filter = styles.filter;
const duotoneDeclarations = getStylesDeclarations(duotoneStyles);
if (duotoneDeclarations.length) {
ruleset += `${duotoneSelector}{${duotoneDeclarations.join(';')};}`;
// Process blockGap and layout styles.
if (!disableLayoutStyles && (ROOT_BLOCK_SELECTOR === selector || hasLayoutSupport)) {
ruleset += getLayoutStyles({
// Process the remaining block styles (they use either normal block class or __experimentalSelector).
const styleDeclarations = getStylesDeclarations(styles, selector, useRootPaddingAlign, tree, disableRootPadding);
if (styleDeclarations?.length) {
const generalSelector = skipSelectorWrapper ? selector : `:root :where(${selector})`;
ruleset += `${generalSelector}{${styleDeclarations.join(';')};}`;
ruleset += processCSSNesting(styles.css, `:root :where(${selector})`);
if (options.variationStyles && styleVariationSelectors) {
Object.entries(styleVariationSelectors).forEach(([styleVariationName, styleVariationSelector]) => {
const styleVariations = styles?.variations?.[styleVariationName];
// If the block uses any custom selectors for block support, add those first.
const featureDeclarations = getFeatureDeclarations(featureSelectors, styleVariations);
Object.entries(featureDeclarations).forEach(([baseSelector, declarations]) => {
if (declarations.length) {
const cssSelector = concatFeatureVariationSelectorString(baseSelector, styleVariationSelector);
const rules = declarations.join(';');
ruleset += `:root :where(${cssSelector}){${rules};}`;
// Otherwise add regular selectors.
const styleVariationDeclarations = getStylesDeclarations(styleVariations, styleVariationSelector, useRootPaddingAlign, tree);
if (styleVariationDeclarations.length) {
ruleset += `:root :where(${styleVariationSelector}){${styleVariationDeclarations.join(';')};}`;
if (styleVariations?.css) {
ruleset += processCSSNesting(styleVariations.css, `:root :where(${styleVariationSelector})`);
// Check for pseudo selector in `styles` and handle separately.
const pseudoSelectorStyles = Object.entries(styles).filter(([key]) => key.startsWith(':'));
if (pseudoSelectorStyles?.length) {
pseudoSelectorStyles.forEach(([pseudoKey, pseudoStyle]) => {
const pseudoDeclarations = getStylesDeclarations(pseudoStyle);
if (!pseudoDeclarations?.length) {
// `selector` may be provided in a form
// where block level selectors have sub element
// selectors appended to them as a comma separated
// e.g. `h1 a,h2 a,h3 a,h4 a,h5 a,h6 a`;
// Split and append pseudo selector to create
// the proper rules to target the elements.
const _selector = selector.split(',').map(sel => sel + pseudoKey).join(',');
// As pseudo classes such as :hover, :focus etc. have class-level
// specificity, they must use the `:root :where()` wrapper. This.
// caps the specificity at `0-1-0` to allow proper nesting of variations
// and block type element styles.
const pseudoRule = `:root :where(${_selector}){${pseudoDeclarations.join(';')};}`;