{ 'none' !== attributes.iconType ? (
diff --git a/src/blocks/blocks/font-awesome-icons/edit.js b/src/blocks/blocks/font-awesome-icons/edit.js
index 5fe413a5e..25b35cdfc 100644
--- a/src/blocks/blocks/font-awesome-icons/edit.js
+++ b/src/blocks/blocks/font-awesome-icons/edit.js
@@ -22,6 +22,7 @@ import Inspector from './inspector.js';
import themeIsleIcons from './../../helpers/themeisle-icons';
import { blockInit, getDefaultValueByField } from '../../helpers/block-utility.js';
import { _cssBlock, boxValues } from '../../helpers/helper-functions';
+import { useColorResolver } from '../../helpers/utility-hooks.js';
const { attributes: defaultAttributes } = metadata;
@@ -58,12 +59,15 @@ const Edit = ({
return () => unsubscribe( attributes.id );
}, [ attributes.id ]);
+ // Get color resolver to handle theme color slugs
+ const resolveColor = useColorResolver();
+
const Icon = themeIsleIcons.icons[ attributes.icon ];
const getValue = field => getDefaultValueByField({ name, field, defaultAttributes, attributes });
const inlineStyles = {
- '--border-color': attributes.borderColor,
+ '--border-color': resolveColor( attributes.borderColor ),
'--border-size': attributes.borderSize !== undefined && `${attributes.borderSize }px`,
'--border-radius': attributes.borderRadius !== undefined && `${ attributes.borderRadius }%`,
'--margin': ! isObjectLike( getValue( 'margin' ) ) ? getValue( 'margin' ) + 'px' : boxValues( getValue( 'margin' ), { top: '5px', right: '5px', bottom: '5px', left: '5px' }),
@@ -97,30 +101,30 @@ const Edit = ({
diff --git a/src/blocks/blocks/posts/edit.js b/src/blocks/blocks/posts/edit.js
index 73c0d5c5b..12155b196 100644
--- a/src/blocks/blocks/posts/edit.js
+++ b/src/blocks/blocks/posts/edit.js
@@ -54,7 +54,8 @@ import {
} from '../../helpers/helper-functions.js';
import {
useDarkBackground,
- useResponsiveAttributes
+ useResponsiveAttributes,
+ useColorResolver
} from '../../helpers/utility-hooks.js';
import '../../components/store/index.js';
import FeaturedPost from './components/layout/featured.js';
@@ -240,6 +241,9 @@ const Edit = ({
useDarkBackground( attributes.backgroundColor, attributes, setAttributes );
+ // Get color resolver to handle theme color slugs
+ const resolveColor = useColorResolver();
+
const getValue = field => getDefaultValueByField({ name, field, defaultAttributes, attributes });
const imageBoxShadow = getValue( 'imageBoxShadow' );
@@ -258,10 +262,10 @@ const Edit = ({
'--box-shadow': boxShadow.active && `${ boxShadow.horizontal }px ${ boxShadow.vertical }px ${ boxShadow.blur }px ${ boxShadow.spread }px ${ hex2rgba( boxShadow.color, boxShadow.colorOpacity ) }`,
'--vert-align': _align( attributes.verticalAlign ),
'--text-align': attributes.textAlign,
- '--text-color': attributes.textColor,
- '--background-color': attributes.backgroundColor,
+ '--text-color': resolveColor( attributes.textColor ),
+ '--background-color': resolveColor( attributes.backgroundColor ),
'--background-overlay': attributes.backgroundOverlay || '#0000005e',
- '--border-color': attributes.borderColor,
+ '--border-color': resolveColor( attributes.borderColor ),
'--content-gap': attributes.contentGap,
'--img-width': responsiveGetAttributes([ _px( attributes.imageWidth ), attributes.imageWidthTablet, attributes.imageWidthMobile ]),
'--img-width-tablet': attributes.imageWidthTablet,
diff --git a/src/blocks/blocks/progress-bar/edit.js b/src/blocks/blocks/progress-bar/edit.js
index 325bcedba..390ba6d5c 100644
--- a/src/blocks/blocks/progress-bar/edit.js
+++ b/src/blocks/blocks/progress-bar/edit.js
@@ -26,6 +26,7 @@ import {
import metadata from './block.json';
import { blockInit } from '../../helpers/block-utility.js';
import Inspector from './inspector.js';
+import { useColorResolver } from '../../helpers/utility-hooks.js';
const { attributes: defaultAttributes } = metadata;
@@ -46,6 +47,9 @@ const ProgressBar = ({
return () => unsubscribe( attributes.id );
}, [ attributes.id ]);
+ // Get color resolver to handle theme color slugs
+ const resolveColor = useColorResolver();
+
const blockRef = useRef( null );
const [ showPercentage, setShowPercentage ] = useState( false );
@@ -86,15 +90,15 @@ const ProgressBar = ({
}, [ attributes.percentage, attributes.duration ]);
const inlineStyles = {
- '--title-color': attributes.titleColor,
- '--percentage-color': attributes.percentageColor,
- '--percentage-color-outer': attributes.percentageColor,
- '--percentage-color-tooltip': attributes.percentageColor,
- '--percentage-color-append': attributes.percentageColor,
- '--background-color': attributes.backgroundColor,
+ '--title-color': resolveColor( attributes.titleColor ),
+ '--percentage-color': resolveColor( attributes.percentageColor ),
+ '--percentage-color-outer': resolveColor( attributes.percentageColor ),
+ '--percentage-color-tooltip': resolveColor( attributes.percentageColor ),
+ '--percentage-color-append': resolveColor( attributes.percentageColor ),
+ '--background-color': resolveColor( attributes.backgroundColor ),
'--border-radius': attributes.borderRadius !== undefined && ( attributes.borderRadius + 'px' ),
'--height': attributes.height !== undefined && ( attributes.height + 'px' ),
- '--bar-background': attributes.barBackgroundColor,
+ '--bar-background': resolveColor( attributes.barBackgroundColor ),
'--title-font-size': attributes.titleFontSize
};
diff --git a/src/blocks/blocks/review/edit.js b/src/blocks/blocks/review/edit.js
index c0cdca204..3ab458661 100644
--- a/src/blocks/blocks/review/edit.js
+++ b/src/blocks/blocks/review/edit.js
@@ -47,7 +47,7 @@ import {
getDefaultValueByField
} from '../../helpers/block-utility.js';
-import { useDarkBackground } from '../../helpers/utility-hooks.js';
+import { useDarkBackground, useColorResolver } from '../../helpers/utility-hooks.js';
import { _px } from '../../helpers/helper-functions';
const { attributes: defaultAttributes } = metadata;
@@ -116,6 +116,9 @@ const Edit = ({
const getValue = field => getDefaultValueByField({ name, field, defaultAttributes, attributes });
+ // Get color resolver to handle theme color slugs
+ const resolveColor = useColorResolver();
+
useDarkBackground( getValue( 'backgroundColor' ), attributes, setAttributes );
const overallRatings = ( attributes.features.reduce( ( accumulator, feature ) => accumulator + feature.rating, 0 ) / attributes.features.length ).toFixed( 1 );
@@ -153,14 +156,14 @@ const Edit = ({
const boxShadow = getValue( 'boxShadow' );
const inlineStyles = {
- '--background-color': getValue( 'backgroundColor' ),
- '--primary-color': getValue( 'primaryColor' ),
- '--text-color': getValue( 'textColor' ),
- '--button-text-color': getValue( 'buttonTextColor' ),
- '--border-color': getValue( 'borderColor' ),
- '--stars-color': getValue( 'starsColor' ),
- '--pros-color': getValue( 'prosColor' ),
- '--cons-color': getValue( 'consColor' ),
+ '--background-color': resolveColor( getValue( 'backgroundColor' ) ),
+ '--primary-color': resolveColor( getValue( 'primaryColor' ) ),
+ '--text-color': resolveColor( getValue( 'textColor' ) ),
+ '--button-text-color': resolveColor( getValue( 'buttonTextColor' ) ),
+ '--border-color': resolveColor( getValue( 'borderColor' ) ),
+ '--stars-color': resolveColor( getValue( 'starsColor' ) ),
+ '--pros-color': resolveColor( getValue( 'prosColor' ) ),
+ '--cons-color': resolveColor( getValue( 'consColor' ) ),
'--content-font-size': getValue( 'contentFontSize' ),
...( attributes?.padding?.top && { '--padding-desktop-top': attributes.padding.top }),
...( attributes?.padding?.bottom && { '--padding-desktop-bottom': attributes.padding.bottom }),
diff --git a/src/blocks/blocks/section/column/edit.js b/src/blocks/blocks/section/column/edit.js
index 295a705f5..03c62322e 100644
--- a/src/blocks/blocks/section/column/edit.js
+++ b/src/blocks/blocks/section/column/edit.js
@@ -38,7 +38,7 @@ import {
getDefaultValueByField
} from '../../../helpers/block-utility.js';
import { _cssBlock } from '../../../helpers/helper-functions';
-import { useDarkBackground } from '../../../helpers/utility-hooks.js';
+import { useDarkBackground, useColorResolver } from '../../../helpers/utility-hooks.js';
const { attributes: defaultAttributes } = metadata;
@@ -104,6 +104,9 @@ const Edit = ({
return () => unsubscribe( attributes.id );
}, [ attributes.id ]);
+ // Get color resolver to handle theme color slugs
+ const resolveColor = useColorResolver();
+
useEffect( () => {
if ( 1 < parentBlock.innerBlocks.length ) {
if ( ! adjacentBlockClientId ) {
@@ -223,7 +226,7 @@ const Edit = ({
if ( 'color' === attributes.backgroundType ) {
background = {
- '--background': attributes.backgroundColor
+ '--background': resolveColor( attributes.backgroundColor )
};
}
@@ -250,7 +253,7 @@ const Edit = ({
borderBottomWidth: attributes.border.bottom,
borderLeftWidth: attributes.border.left,
borderStyle: 'solid',
- borderColor: attributes.borderColor
+ borderColor: resolveColor( attributes.borderColor )
};
}
@@ -264,8 +267,9 @@ const Edit = ({
}
if ( true === attributes.boxShadow ) {
+ const resolvedBoxShadowColor = resolveColor( attributes.boxShadowColor );
boxShadowStyle = {
- boxShadow: `${ attributes.boxShadowHorizontal }px ${ attributes.boxShadowVertical }px ${ attributes.boxShadowBlur }px ${ attributes.boxShadowSpread }px ${ attributes.boxShadowColor.includes( 'var(' ) && ( attributes.boxShadowColorOpacity === undefined || 100 === attributes.boxShadowColorOpacity ) ? attributes.boxShadowColor : hexToRgba( ( attributes.boxShadowColor ? attributes.boxShadowColor : '#000000' ), attributes.boxShadowColorOpacity ) }`
+ boxShadow: `${ attributes.boxShadowHorizontal }px ${ attributes.boxShadowVertical }px ${ attributes.boxShadowBlur }px ${ attributes.boxShadowSpread }px ${ resolvedBoxShadowColor?.includes( 'var(' ) && ( attributes.boxShadowColorOpacity === undefined || 100 === attributes.boxShadowColorOpacity ) ? resolvedBoxShadowColor : hexToRgba( ( resolvedBoxShadowColor || '#000000' ), attributes.boxShadowColorOpacity ) }`
};
}
@@ -281,8 +285,8 @@ const Edit = ({
...borderStyle,
...borderRadiusStyle,
...boxShadowStyle,
- '--link-color': attributes.linkColor,
- '--background-color-hover': attributes.backgroundColorHover
+ '--link-color': resolveColor( attributes.linkColor ),
+ '--background-color-hover': resolveColor( attributes.backgroundColorHover )
};
if ( attributes.verticalAlign ) {
@@ -291,7 +295,7 @@ const Edit = ({
if ( 'color' === attributes.backgroundOverlayType ) {
overlayBackground = {
- background: attributes.backgroundOverlayColor,
+ background: resolveColor( attributes.backgroundOverlayColor ),
opacity: attributes.backgroundOverlayOpacity / 100
};
}
@@ -333,12 +337,12 @@ const Edit = ({
diff --git a/src/blocks/blocks/section/columns/edit.js b/src/blocks/blocks/section/columns/edit.js
index 8c2aaeb2d..78e0a9dbd 100644
--- a/src/blocks/blocks/section/columns/edit.js
+++ b/src/blocks/blocks/section/columns/edit.js
@@ -54,7 +54,7 @@ import {
} from '../../../helpers/block-utility.js';
import { columnsIcon as icon } from '../../../helpers/icons.js';
import { _cssBlock, _px } from '../../../helpers/helper-functions';
-import { useDarkBackground } from '../../../helpers/utility-hooks.js';
+import { useDarkBackground, useColorResolver } from '../../../helpers/utility-hooks.js';
const { attributes: defaultAttributes } = metadata;
@@ -74,6 +74,9 @@ const Edit = ({
return () => unsubscribe( attributes.id );
}, [ attributes.id ]);
+ // Get color resolver to handle theme color slugs
+ const resolveColor = useColorResolver();
+
const { updateBlockAttributes, replaceInnerBlocks } = useDispatch( 'core/block-editor' );
const {
@@ -266,7 +269,7 @@ const Edit = ({
if ( 'color' === attributes.backgroundType ) {
background = {
- backgroundColor: attributes.backgroundColor
+ backgroundColor: resolveColor( attributes.backgroundColor )
};
}
@@ -293,7 +296,7 @@ const Edit = ({
borderBottomWidth: attributes.border.bottom,
borderLeftWidth: attributes.border.left,
borderStyle: 'solid',
- borderColor: attributes.borderColor
+ borderColor: resolveColor( attributes.borderColor )
};
}
@@ -307,8 +310,9 @@ const Edit = ({
}
if ( true === attributes.boxShadow ) {
+ const resolvedBoxShadowColor = resolveColor( attributes.boxShadowColor );
boxShadowStyle = {
- boxShadow: `${ attributes.boxShadowHorizontal }px ${ attributes.boxShadowVertical }px ${ attributes.boxShadowBlur }px ${ attributes.boxShadowSpread }px ${ attributes.boxShadowColor.includes( 'var(' ) && ( attributes.boxShadowColorOpacity === undefined || 100 === attributes.boxShadowColorOpacity ) ? attributes.boxShadowColor : hexToRgba( ( attributes.boxShadowColor ? attributes.boxShadowColor : '#000000' ), attributes.boxShadowColorOpacity ) }`
+ boxShadow: `${ attributes.boxShadowHorizontal }px ${ attributes.boxShadowVertical }px ${ attributes.boxShadowBlur }px ${ attributes.boxShadowSpread }px ${ resolvedBoxShadowColor?.includes( 'var(' ) && ( attributes.boxShadowColorOpacity === undefined || 100 === attributes.boxShadowColorOpacity ) ? resolvedBoxShadowColor : hexToRgba( ( resolvedBoxShadowColor || '#000000' ), attributes.boxShadowColorOpacity ) }`
};
}
@@ -318,12 +322,12 @@ const Edit = ({
...borderStyle,
...borderRadiusStyle,
...boxShadowStyle,
- '--link-color': attributes.linkColor
+ '--link-color': resolveColor( attributes.linkColor )
};
if ( 'color' === attributes.backgroundOverlayType ) {
overlayBackground = {
- background: attributes.backgroundOverlayColor,
+ background: resolveColor( attributes.backgroundOverlayColor ),
opacity: attributes.backgroundOverlayOpacity / 100
};
}
@@ -420,12 +424,12 @@ const Edit = ({
From 013c0dabf4f7d639aaf07ab63ff3731811021976 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Thu, 19 Feb 2026 10:49:37 +0000
Subject: [PATCH 09/25] Use CSS variables to preserve color slug connection to
theme.json
Co-authored-by: selul <3330746+selul@users.noreply.github.com>
---
docs/color-slug-resolution.md | 96 ++++++++++++++++++++------
inc/class-base-css.php | 69 ++++++++----------
src/blocks/helpers/helper-functions.js | 53 +++++++-------
3 files changed, 134 insertions(+), 84 deletions(-)
diff --git a/docs/color-slug-resolution.md b/docs/color-slug-resolution.md
index 5c6fe0c52..262d863a0 100644
--- a/docs/color-slug-resolution.md
+++ b/docs/color-slug-resolution.md
@@ -2,13 +2,33 @@
## Problem
-WordPress themes define color palettes in `theme.json` using color slugs (e.g., "primary", "base", "contrast"). Core WordPress blocks can reference these colors by slug, and they are automatically resolved to the actual color values.
+WordPress themes define color palettes in `theme.json` using color slugs (e.g., "primary", "base", "contrast"). Core WordPress blocks can reference these colors by slug, and they automatically update when theme colors change.
-However, Otter Blocks were not resolving these color slugs, causing them to revert to defaults when a slug was used instead of a hex/rgb value.
+Otter Blocks were not properly handling color slugs - they were converting slugs to hex values at render time, which broke the connection to theme.json. When theme colors changed, blocks didn't update.
## Solution
-We've added color slug resolution utilities that work on both the JavaScript (editor) and PHP (frontend) sides.
+We've implemented the same approach as WordPress core blocks: **using CSS variables to preserve the connection to theme.json**.
+
+### How It Works
+
+WordPress automatically generates CSS variables from `theme.json` color palette:
+```css
+/* WordPress generates these automatically */
+:root {
+ --wp--preset--color--primary: #0073aa;
+ --wp--preset--color--base: #000000;
+ --wp--preset--color--contrast: #ffffff;
+}
+```
+
+When you use a color slug in Otter blocks, it's converted to a CSS variable reference:
+```javascript
+// Input: slug "primary"
+// Output: "var(--wp--preset--color--primary)"
+```
+
+**Key Benefit:** When theme.json changes, the CSS variable value updates automatically, and all blocks using that slug instantly reflect the new color - no block re-save needed!
### JavaScript Solution
@@ -21,7 +41,9 @@ const Edit = ({ attributes, setAttributes }) => {
// Get the color resolver function
const resolveColor = useColorResolver();
- // Use it to resolve any color attribute
+ // Converts slugs to CSS variables
+ // "primary" → "var(--wp--preset--color--primary)"
+ // "#ff0000" → "#ff0000" (hex values passed through)
const resolvedBackgroundColor = resolveColor(attributes.backgroundColor);
const resolvedTextColor = resolveColor(attributes.textColor);
@@ -35,20 +57,23 @@ const Edit = ({ attributes, setAttributes }) => {
};
```
-#### Option 2: Use `resolveColorValue` Directly
+#### Option 2: Use `resolveColorValue` or `getColorCSSVariable` Directly
```javascript
-import { useSetting } from '@wordpress/block-editor';
-import { resolveColorValue } from '../../helpers/helper-functions';
+import { resolveColorValue, getColorCSSVariable } from '../../helpers/helper-functions';
const Edit = ({ attributes, setAttributes }) => {
- // Get the color palette
- const colorPalette = useSetting('color.palette') || [];
+ // Both functions do the same thing - convert slug to CSS variable
+ const colorVar1 = resolveColorValue(attributes.backgroundColor);
+ const colorVar2 = getColorCSSVariable(attributes.textColor);
- // Resolve colors
- const resolvedColor = resolveColorValue(attributes.backgroundColor, colorPalette);
+ // "primary" → "var(--wp--preset--color--primary)"
+ // "#ff0000" → "#ff0000"
- return
...
;
+ return
...
;
};
```
@@ -79,21 +104,50 @@ $css->add_item(
);
```
+**Result:**
+- Input slug: `"primary"`
+- Output CSS: `background-color: var(--wp--preset--color--primary);`
+- Input hex: `"#ff0000"`
+- Output CSS: `background-color: #ff0000;`
+
## How It Works
### Color Resolution Logic
-The resolver:
+The resolver converts color slugs to CSS variables:
1. Checks if the value is already a color (starts with `#`, `rgb`, `hsl`, or `var(`)
-2. If not, looks up the slug in the theme color palette
-3. Returns the resolved color value, or the original value if not found
+2. If it's a hex/rgb/hsl value, returns it unchanged
+3. If it's a slug (anything else), converts to CSS variable: `var(--wp--preset--color--{slug})`
+
+**No palette lookup needed!** WordPress handles the CSS variable definitions automatically.
-### Theme Palette Sources
+### WordPress CSS Variable Generation
+
+WordPress reads `theme.json` and automatically generates CSS variables:
+
+```json
+// theme.json
+{
+ "settings": {
+ "color": {
+ "palette": [
+ { "slug": "primary", "color": "#0073aa", "name": "Primary" },
+ { "slug": "base", "color": "#000000", "name": "Base" }
+ ]
+ }
+ }
+}
+```
+
+WordPress outputs:
+```css
+:root {
+ --wp--preset--color--primary: #0073aa;
+ --wp--preset--color--base: #000000;
+}
+```
-The resolver checks colors from:
-- Theme palette (`theme.json` colors)
-- Default WordPress colors
-- Custom colors added by the user
+When you change colors in `theme.json`, WordPress updates the CSS variables, and all blocks automatically reflect the change!
## Example: Advanced Heading Block
@@ -101,7 +155,7 @@ The advanced-heading block has been updated as a reference implementation. See:
- JavaScript: `/src/blocks/blocks/advanced-heading/edit.js`
- PHP: `/inc/css/blocks/class-advanced-heading-css.php`
-## Blocks That Need This Fix
+## Blocks Updated
Based on analysis, these blocks have color attributes and should be updated:
diff --git a/inc/class-base-css.php b/inc/class-base-css.php
index dea361347..614f5d02c 100644
--- a/inc/class-base-css.php
+++ b/inc/class-base-css.php
@@ -203,56 +203,47 @@ public static function hex2rgba( $color, $opacity = false ) {
}
/**
- * Resolve a color value which may be a slug from the theme color palette.
- * If the value is a slug (doesn't start with # or rgb/hsl/var), attempts to resolve it from the palette.
- * Otherwise, returns the value as-is.
+ * Convert a color slug to a CSS variable reference.
+ * WordPress generates CSS variables in the format: --wp--preset--color--{slug}
*
- * @param string|null $value The color value or slug.
- * @return string|null The resolved color value.
+ * @param string|null $slug The color slug.
+ * @return string|null The CSS variable reference.
* @since 3.1.5
* @access public
*/
- public static function resolve_color_value( $value ) {
- if ( empty( $value ) ) {
- return $value;
+ public static function get_color_css_variable( $slug ) {
+ if ( empty( $slug ) ) {
+ return $slug;
}
- // If it's already a color value (hex, rgb, hsl, var), return as-is.
+ // If it's already a color value or CSS variable, return as-is.
if (
- strpos( $value, '#' ) === 0 ||
- strpos( $value, 'rgb' ) === 0 ||
- strpos( $value, 'hsl' ) === 0 ||
- strpos( $value, 'var(' ) === 0
+ strpos( $slug, '#' ) === 0 ||
+ strpos( $slug, 'rgb' ) === 0 ||
+ strpos( $slug, 'hsl' ) === 0 ||
+ strpos( $slug, 'var(' ) === 0
) {
- return $value;
+ return $slug;
}
- // Try to get the color palette from theme.json.
- if ( function_exists( 'wp_get_global_settings' ) ) {
- $global_settings = wp_get_global_settings();
-
- // Get colors from different sources (theme, default, custom).
- $palettes = array();
- if ( isset( $global_settings['color']['palette']['theme'] ) ) {
- $palettes = array_merge( $palettes, $global_settings['color']['palette']['theme'] );
- }
- if ( isset( $global_settings['color']['palette']['default'] ) ) {
- $palettes = array_merge( $palettes, $global_settings['color']['palette']['default'] );
- }
- if ( isset( $global_settings['color']['palette']['custom'] ) ) {
- $palettes = array_merge( $palettes, $global_settings['color']['palette']['custom'] );
- }
-
- // Try to find the color by slug.
- foreach ( $palettes as $color_obj ) {
- if ( isset( $color_obj['slug'] ) && $color_obj['slug'] === $value ) {
- return $color_obj['color'];
- }
- }
- }
+ // Convert slug to CSS variable.
+ return 'var(--wp--preset--color--' . $slug . ')';
+ }
- // If we couldn't resolve it, return the original value.
- return $value;
+ /**
+ * Resolve a color value which may be a slug from the theme color palette.
+ * This function converts slugs to CSS variables to preserve the connection to theme.json.
+ * If the value is a slug, it returns a CSS variable reference.
+ * Otherwise, returns the value as-is (for hex, rgb, hsl values).
+ *
+ * @param string|null $value The color value or slug.
+ * @return string|null The CSS variable or color value.
+ * @since 3.1.5
+ * @access public
+ */
+ public static function resolve_color_value( $value ) {
+ // Use CSS variable conversion for slugs.
+ return self::get_color_css_variable( $value );
}
/**
diff --git a/src/blocks/helpers/helper-functions.js b/src/blocks/helpers/helper-functions.js
index 24a0e5619..c2a524e48 100644
--- a/src/blocks/helpers/helper-functions.js
+++ b/src/blocks/helpers/helper-functions.js
@@ -361,39 +361,44 @@ export const lightnessFromColor = color => {
};
/**
- * Resolve a color value which may be a slug from the color palette.
- * If the value is a slug (doesn't start with # or rgb/hsl/var), attempts to resolve it from the palette.
- * Otherwise, returns the value as-is.
+ * Convert a color slug to a CSS variable reference.
+ * WordPress generates CSS variables in the format: --wp--preset--color--{slug}
*
- * @param {string|undefined} value The color value or slug
- * @param {Array} palette Optional color palette array from useSetting('color.palette')
- * @return {string|undefined} The resolved color value
+ * @param {string|undefined} slug The color slug
+ * @return {string|undefined} The CSS variable reference
*/
-export const resolveColorValue = ( value, palette = null ) => {
- if ( ! value ) {
- return value;
+export const getColorCSSVariable = ( slug ) => {
+ if ( ! slug ) {
+ return slug;
}
- // If it's already a color value (hex, rgb, hsl, var), return as-is
+ // If it's already a color value or CSS variable, return as-is
if (
- value.startsWith( '#' ) ||
- value.startsWith( 'rgb' ) ||
- value.startsWith( 'hsl' ) ||
- value.startsWith( 'var(' )
+ slug.startsWith( '#' ) ||
+ slug.startsWith( 'rgb' ) ||
+ slug.startsWith( 'hsl' ) ||
+ slug.startsWith( 'var(' )
) {
- return value;
+ return slug;
}
- // If no palette provided, we can't resolve, so return the value
- if ( ! palette || ! Array.isArray( palette ) ) {
- return value;
- }
-
- // Try to find the color in the palette by slug
- const colorObject = palette.find( color => color.slug === value );
+ // Convert slug to CSS variable
+ return `var(--wp--preset--color--${ slug })`;
+};
- // Return the color value if found, otherwise return original value
- return colorObject?.color || value;
+/**
+ * Resolve a color value which may be a slug from the color palette.
+ * This function converts slugs to CSS variables to preserve the connection to theme.json.
+ * If the value is a slug, it returns a CSS variable reference.
+ * Otherwise, returns the value as-is (for hex, rgb, hsl values).
+ *
+ * @param {string|undefined} value The color value or slug
+ * @param {Array} palette Optional color palette array (for backwards compatibility, not used)
+ * @return {string|undefined} The CSS variable or color value
+ */
+export const resolveColorValue = ( value, palette = null ) => {
+ // Use CSS variable conversion for slugs
+ return getColorCSSVariable( value );
};
/**
From c4f3f7028697863a01d385c7168c3846c1a1bd38 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Thu, 19 Feb 2026 10:51:32 +0000
Subject: [PATCH 10/25] Add slug sanitization and improve documentation clarity
Co-authored-by: selul <3330746+selul@users.noreply.github.com>
---
docs/color-slug-resolution.md | 16 +++++++---------
inc/class-base-css.php | 6 +++++-
src/blocks/helpers/helper-functions.js | 9 +++++++--
3 files changed, 19 insertions(+), 12 deletions(-)
diff --git a/docs/color-slug-resolution.md b/docs/color-slug-resolution.md
index 262d863a0..70596a63f 100644
--- a/docs/color-slug-resolution.md
+++ b/docs/color-slug-resolution.md
@@ -57,26 +57,24 @@ const Edit = ({ attributes, setAttributes }) => {
};
```
-#### Option 2: Use `resolveColorValue` or `getColorCSSVariable` Directly
+#### Option 2: Use `getColorCSSVariable` Directly (Recommended for New Code)
```javascript
-import { resolveColorValue, getColorCSSVariable } from '../../helpers/helper-functions';
+import { getColorCSSVariable } from '../../helpers/helper-functions';
const Edit = ({ attributes, setAttributes }) => {
- // Both functions do the same thing - convert slug to CSS variable
- const colorVar1 = resolveColorValue(attributes.backgroundColor);
- const colorVar2 = getColorCSSVariable(attributes.textColor);
+ // Direct conversion of slug to CSS variable
+ const colorVar = getColorCSSVariable(attributes.backgroundColor);
// "primary" → "var(--wp--preset--color--primary)"
// "#ff0000" → "#ff0000"
- return
...
;
+ return
...
;
};
```
+**Note:** `resolveColorValue()` is also available as a wrapper for backward compatibility, but `getColorCSSVariable()` is recommended for new code as it's more explicit about its purpose.
+
### PHP Solution
In your block's CSS class (e.g., `class-my-block-css.php`), use the `Base_CSS::resolve_color_value()` method:
diff --git a/inc/class-base-css.php b/inc/class-base-css.php
index 614f5d02c..9200278ce 100644
--- a/inc/class-base-css.php
+++ b/inc/class-base-css.php
@@ -226,8 +226,12 @@ public static function get_color_css_variable( $slug ) {
return $slug;
}
+ // Sanitize slug: WordPress slugs should only contain lowercase alphanumeric and hyphens.
+ // This prevents potential CSS injection if slug comes from untrusted sources.
+ $sanitized_slug = strtolower( preg_replace( '/[^a-z0-9-]/', '', $slug ) );
+
// Convert slug to CSS variable.
- return 'var(--wp--preset--color--' . $slug . ')';
+ return 'var(--wp--preset--color--' . $sanitized_slug . ')';
}
/**
diff --git a/src/blocks/helpers/helper-functions.js b/src/blocks/helpers/helper-functions.js
index c2a524e48..ecd714434 100644
--- a/src/blocks/helpers/helper-functions.js
+++ b/src/blocks/helpers/helper-functions.js
@@ -382,8 +382,12 @@ export const getColorCSSVariable = ( slug ) => {
return slug;
}
+ // Sanitize slug: WordPress slugs should only contain lowercase alphanumeric and hyphens
+ // This prevents potential CSS injection if slug comes from untrusted sources
+ const sanitizedSlug = slug.toLowerCase().replace( /[^a-z0-9-]/g, '' );
+
// Convert slug to CSS variable
- return `var(--wp--preset--color--${ slug })`;
+ return `var(--wp--preset--color--${ sanitizedSlug })`;
};
/**
@@ -393,8 +397,9 @@ export const getColorCSSVariable = ( slug ) => {
* Otherwise, returns the value as-is (for hex, rgb, hsl values).
*
* @param {string|undefined} value The color value or slug
- * @param {Array} palette Optional color palette array (for backwards compatibility, not used)
+ * @param {Array} palette Optional color palette array (deprecated, no longer used)
* @return {string|undefined} The CSS variable or color value
+ * @deprecated The palette parameter is deprecated and no longer used. Use getColorCSSVariable() directly.
*/
export const resolveColorValue = ( value, palette = null ) => {
// Use CSS variable conversion for slugs
From 75d0ccb6d41bb7dbe20869686e18bdd92d0c8481 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Thu, 19 Feb 2026 10:53:52 +0000
Subject: [PATCH 11/25] Add comprehensive implementation summary documentation
Co-authored-by: selul <3330746+selul@users.noreply.github.com>
---
docs/IMPLEMENTATION_SUMMARY.md | 288 +++++++++++++++++++++++++++++++++
1 file changed, 288 insertions(+)
create mode 100644 docs/IMPLEMENTATION_SUMMARY.md
diff --git a/docs/IMPLEMENTATION_SUMMARY.md b/docs/IMPLEMENTATION_SUMMARY.md
new file mode 100644
index 000000000..f74c057ce
--- /dev/null
+++ b/docs/IMPLEMENTATION_SUMMARY.md
@@ -0,0 +1,288 @@
+# Color Slug Implementation Summary
+
+## Overview
+
+This document summarizes the implementation of CSS variable-based color slug handling in Otter Blocks, matching the approach used by WordPress core blocks.
+
+## Problem
+
+**Previous Implementation:**
+- Color slugs (e.g., `"primary"`, `"base"`) were resolved to actual hex values at render time
+- Example: `"primary"` → `"#0073aa"`
+- **Issue:** When theme.json colors changed, blocks didn't update because they were using hardcoded hex values
+- Connection to theme.json was lost after initial render
+
+**User Impact:**
+- Changing theme colors required re-saving all blocks
+- Blocks didn't automatically adapt to theme changes
+- Inconsistent with WordPress core block behavior
+
+## Solution
+
+**New Implementation:**
+- Color slugs are converted to CSS variables that reference the theme palette
+- Example: `"primary"` → `"var(--wp--preset--color--primary)"`
+- **Benefit:** WordPress automatically updates CSS variable values when theme.json changes
+- All blocks instantly reflect new colors without re-saving
+
+## Technical Details
+
+### JavaScript Implementation
+
+**File:** `src/blocks/helpers/helper-functions.js`
+
+**New Function:**
+```javascript
+export const getColorCSSVariable = ( slug ) => {
+ if ( ! slug ) return slug;
+
+ // Pass through existing color values
+ if (slug.startsWith('#') || slug.startsWith('rgb') ||
+ slug.startsWith('hsl') || slug.startsWith('var(')) {
+ return slug;
+ }
+
+ // Sanitize and convert slug to CSS variable
+ const sanitizedSlug = slug.toLowerCase().replace(/[^a-z0-9-]/g, '');
+ return `var(--wp--preset--color--${sanitizedSlug})`;
+};
+```
+
+**Updated Function:**
+```javascript
+export const resolveColorValue = ( value, palette = null ) => {
+ // Now uses CSS variable conversion
+ return getColorCSSVariable( value );
+};
+```
+
+### PHP Implementation
+
+**File:** `inc/class-base-css.php`
+
+**New Method:**
+```php
+public static function get_color_css_variable( $slug ) {
+ if ( empty( $slug ) ) return $slug;
+
+ // Pass through existing color values
+ if (strpos($slug, '#') === 0 || strpos($slug, 'rgb') === 0 ||
+ strpos($slug, 'hsl') === 0 || strpos($slug, 'var(') === 0) {
+ return $slug;
+ }
+
+ // Sanitize and convert slug to CSS variable
+ $sanitized_slug = strtolower(preg_replace('/[^a-z0-9-]/', '', $slug));
+ return 'var(--wp--preset--color--' . $sanitized_slug . ')';
+}
+```
+
+**Updated Method:**
+```php
+public static function resolve_color_value( $value ) {
+ // Now uses CSS variable conversion
+ return self::get_color_css_variable( $value );
+}
+```
+
+## Security Considerations
+
+### Slug Sanitization
+
+Both JavaScript and PHP implementations sanitize slugs to prevent CSS injection:
+
+**Rules:**
+- Convert to lowercase
+- Allow only: `a-z`, `0-9`, `-` (hyphen)
+- Remove all other characters
+
+**Examples:**
+- `"Primary-Color_123"` → `"primary-color123"`
+- `"test@color!"` → `"testcolor"`
+- `"base"` → `"base"` (unchanged)
+
+**Why:** Prevents potential CSS injection if slug values come from untrusted sources.
+
+## How WordPress Generates CSS Variables
+
+When you define colors in `theme.json`:
+
+```json
+{
+ "settings": {
+ "color": {
+ "palette": [
+ { "slug": "primary", "color": "#0073aa", "name": "Primary" },
+ { "slug": "base", "color": "#000000", "name": "Base" }
+ ]
+ }
+ }
+}
+```
+
+WordPress automatically generates CSS in the document ``:
+
+```css
+:root {
+ --wp--preset--color--primary: #0073aa;
+ --wp--preset--color--base: #000000;
+}
+```
+
+## Usage Examples
+
+### JavaScript (Block Edit Component)
+
+```javascript
+import { useColorResolver } from '../../helpers/utility-hooks.js';
+
+const Edit = ({ attributes }) => {
+ const resolveColor = useColorResolver();
+
+ // Converts slugs to CSS variables
+ const style = {
+ color: resolveColor(attributes.headingColor),
+ background: resolveColor(attributes.backgroundColor)
+ };
+
+ return
...
;
+};
+```
+
+**Input/Output:**
+- `attributes.headingColor = "primary"` → `style.color = "var(--wp--preset--color--primary)"`
+- `attributes.backgroundColor = "#ff0000"` → `style.background = "#ff0000"`
+
+### PHP (CSS Generation)
+
+```php
+$css->add_item(
+ array(
+ 'properties' => array(
+ array(
+ 'property' => 'color',
+ 'value' => 'headingColor',
+ 'format' => function ( $value ) {
+ return Base_CSS::resolve_color_value( $value );
+ },
+ ),
+ ),
+ )
+);
+```
+
+**Generated CSS:**
+- Input: `headingColor = "primary"` → Output: `color: var(--wp--preset--color--primary);`
+- Input: `headingColor = "#ff0000"` → Output: `color: #ff0000;`
+
+## Backward Compatibility
+
+The implementation is fully backward compatible:
+
+| Input Type | Example | Output | Notes |
+|------------|---------|--------|-------|
+| Color Slug | `"primary"` | `"var(--wp--preset--color--primary)"` | New behavior |
+| Hex Value | `"#ff0000"` | `"#ff0000"` | Unchanged |
+| RGB Value | `"rgb(255,0,0)"` | `"rgb(255,0,0)"` | Unchanged |
+| HSL Value | `"hsl(0,100%,50%)"` | `"hsl(0,100%,50%)"` | Unchanged |
+| CSS Variable | `"var(--custom)"` | `"var(--custom)"` | Unchanged |
+
+## Affected Blocks
+
+All 15 Otter blocks with color attributes automatically benefit from this change:
+
+1. advanced-heading
+2. button-group/button
+3. section/column
+4. section/columns
+5. font-awesome-icons
+6. posts
+7. review
+8. progress-bar
+9. circle-counter
+10. countdown
+11. sharing-icons
+12. popup
+13. flip
+14. lottie
+15. modal
+
+## Testing
+
+### Automated Tests
+
+```javascript
+// Test cases verified:
+✓ "primary" → "var(--wp--preset--color--primary)"
+✓ "base" → "var(--wp--preset--color--base)"
+✓ "#ff0000" → "#ff0000"
+✓ "rgb(255, 0, 0)" → "rgb(255, 0, 0)"
+✓ "var(--custom-color)" → "var(--custom-color)"
+✓ "Primary-Color_123" → "var(--wp--preset--color--primary-color123)"
+✓ "test@color!" → "var(--wp--preset--color--testcolor)"
+```
+
+### Manual Testing Steps
+
+1. **Setup:**
+ - Create/edit a theme with custom colors in `theme.json`
+ - Add Otter blocks using color slugs
+
+2. **Test Color Slug:**
+ ```javascript
+ wp.data.dispatch('core/block-editor').insertBlocks(
+ wp.blocks.createBlock('themeisle-blocks/advanced-heading', {
+ headingColor: "primary",
+ content: "Test Heading"
+ })
+ );
+ ```
+
+3. **Verify:**
+ - Check that block displays correctly in editor
+ - Inspect CSS to see `var(--wp--preset--color--primary)`
+
+4. **Test Theme Change:**
+ - Change the `primary` color value in `theme.json`
+ - Refresh page
+ - Verify block color updates automatically (no re-save needed)
+
+5. **Test Backward Compatibility:**
+ - Use hex value: `headingColor: "#ff0000"`
+ - Verify it still works correctly
+
+## Benefits
+
+1. **Auto-updates:** Theme color changes apply instantly to all blocks
+2. **No re-save needed:** Blocks maintain connection to theme palette
+3. **Core consistency:** Matches WordPress core block behavior
+4. **Maintainability:** Easier theme customization and testing
+5. **Performance:** No palette lookup needed at runtime
+6. **Security:** Slug sanitization prevents CSS injection
+
+## Migration Notes
+
+**For Existing Blocks:**
+- No migration needed! Existing hex/rgb values continue to work
+- New blocks using slugs will automatically use CSS variables
+- Old blocks can be updated by changing to slug references
+
+**For Developers:**
+- Use `getColorCSSVariable()` for new code (explicit)
+- Use `useColorResolver()` hook in React components (convenient)
+- `resolveColorValue()` maintained for backward compatibility
+
+## Related Files
+
+- `src/blocks/helpers/helper-functions.js` - Core conversion logic
+- `src/blocks/helpers/utility-hooks.js` - React hook wrapper
+- `inc/class-base-css.php` - PHP conversion logic
+- `docs/color-slug-resolution.md` - User documentation
+- All block `edit.js` files - Using the utilities
+- All block CSS PHP files - Using the utilities
+
+## References
+
+- [WordPress Block Editor Handbook - Global Settings](https://developer.wordpress.org/block-editor/how-to-guides/themes/global-settings-and-styles/)
+- [WordPress Block Supports](https://developer.wordpress.org/block-editor/reference-guides/block-api/block-supports/)
+- [theme.json Reference](https://developer.wordpress.org/block-editor/reference-guides/theme-json-reference/theme-json-living/)
From ad981f16c6c527cdacaa03667b09af7e62062cac Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Thu, 19 Feb 2026 10:55:49 +0000
Subject: [PATCH 12/25] Remove extra documentation and add unit tests for color
CSS variable functions
Co-authored-by: selul <3330746+selul@users.noreply.github.com>
---
docs/IMPLEMENTATION_SUMMARY.md | 288 ------------------
src/blocks/test/unit/helper-functions.test.ts | 79 ++++-
2 files changed, 78 insertions(+), 289 deletions(-)
delete mode 100644 docs/IMPLEMENTATION_SUMMARY.md
diff --git a/docs/IMPLEMENTATION_SUMMARY.md b/docs/IMPLEMENTATION_SUMMARY.md
deleted file mode 100644
index f74c057ce..000000000
--- a/docs/IMPLEMENTATION_SUMMARY.md
+++ /dev/null
@@ -1,288 +0,0 @@
-# Color Slug Implementation Summary
-
-## Overview
-
-This document summarizes the implementation of CSS variable-based color slug handling in Otter Blocks, matching the approach used by WordPress core blocks.
-
-## Problem
-
-**Previous Implementation:**
-- Color slugs (e.g., `"primary"`, `"base"`) were resolved to actual hex values at render time
-- Example: `"primary"` → `"#0073aa"`
-- **Issue:** When theme.json colors changed, blocks didn't update because they were using hardcoded hex values
-- Connection to theme.json was lost after initial render
-
-**User Impact:**
-- Changing theme colors required re-saving all blocks
-- Blocks didn't automatically adapt to theme changes
-- Inconsistent with WordPress core block behavior
-
-## Solution
-
-**New Implementation:**
-- Color slugs are converted to CSS variables that reference the theme palette
-- Example: `"primary"` → `"var(--wp--preset--color--primary)"`
-- **Benefit:** WordPress automatically updates CSS variable values when theme.json changes
-- All blocks instantly reflect new colors without re-saving
-
-## Technical Details
-
-### JavaScript Implementation
-
-**File:** `src/blocks/helpers/helper-functions.js`
-
-**New Function:**
-```javascript
-export const getColorCSSVariable = ( slug ) => {
- if ( ! slug ) return slug;
-
- // Pass through existing color values
- if (slug.startsWith('#') || slug.startsWith('rgb') ||
- slug.startsWith('hsl') || slug.startsWith('var(')) {
- return slug;
- }
-
- // Sanitize and convert slug to CSS variable
- const sanitizedSlug = slug.toLowerCase().replace(/[^a-z0-9-]/g, '');
- return `var(--wp--preset--color--${sanitizedSlug})`;
-};
-```
-
-**Updated Function:**
-```javascript
-export const resolveColorValue = ( value, palette = null ) => {
- // Now uses CSS variable conversion
- return getColorCSSVariable( value );
-};
-```
-
-### PHP Implementation
-
-**File:** `inc/class-base-css.php`
-
-**New Method:**
-```php
-public static function get_color_css_variable( $slug ) {
- if ( empty( $slug ) ) return $slug;
-
- // Pass through existing color values
- if (strpos($slug, '#') === 0 || strpos($slug, 'rgb') === 0 ||
- strpos($slug, 'hsl') === 0 || strpos($slug, 'var(') === 0) {
- return $slug;
- }
-
- // Sanitize and convert slug to CSS variable
- $sanitized_slug = strtolower(preg_replace('/[^a-z0-9-]/', '', $slug));
- return 'var(--wp--preset--color--' . $sanitized_slug . ')';
-}
-```
-
-**Updated Method:**
-```php
-public static function resolve_color_value( $value ) {
- // Now uses CSS variable conversion
- return self::get_color_css_variable( $value );
-}
-```
-
-## Security Considerations
-
-### Slug Sanitization
-
-Both JavaScript and PHP implementations sanitize slugs to prevent CSS injection:
-
-**Rules:**
-- Convert to lowercase
-- Allow only: `a-z`, `0-9`, `-` (hyphen)
-- Remove all other characters
-
-**Examples:**
-- `"Primary-Color_123"` → `"primary-color123"`
-- `"test@color!"` → `"testcolor"`
-- `"base"` → `"base"` (unchanged)
-
-**Why:** Prevents potential CSS injection if slug values come from untrusted sources.
-
-## How WordPress Generates CSS Variables
-
-When you define colors in `theme.json`:
-
-```json
-{
- "settings": {
- "color": {
- "palette": [
- { "slug": "primary", "color": "#0073aa", "name": "Primary" },
- { "slug": "base", "color": "#000000", "name": "Base" }
- ]
- }
- }
-}
-```
-
-WordPress automatically generates CSS in the document ``:
-
-```css
-:root {
- --wp--preset--color--primary: #0073aa;
- --wp--preset--color--base: #000000;
-}
-```
-
-## Usage Examples
-
-### JavaScript (Block Edit Component)
-
-```javascript
-import { useColorResolver } from '../../helpers/utility-hooks.js';
-
-const Edit = ({ attributes }) => {
- const resolveColor = useColorResolver();
-
- // Converts slugs to CSS variables
- const style = {
- color: resolveColor(attributes.headingColor),
- background: resolveColor(attributes.backgroundColor)
- };
-
- return
...
;
-};
-```
-
-**Input/Output:**
-- `attributes.headingColor = "primary"` → `style.color = "var(--wp--preset--color--primary)"`
-- `attributes.backgroundColor = "#ff0000"` → `style.background = "#ff0000"`
-
-### PHP (CSS Generation)
-
-```php
-$css->add_item(
- array(
- 'properties' => array(
- array(
- 'property' => 'color',
- 'value' => 'headingColor',
- 'format' => function ( $value ) {
- return Base_CSS::resolve_color_value( $value );
- },
- ),
- ),
- )
-);
-```
-
-**Generated CSS:**
-- Input: `headingColor = "primary"` → Output: `color: var(--wp--preset--color--primary);`
-- Input: `headingColor = "#ff0000"` → Output: `color: #ff0000;`
-
-## Backward Compatibility
-
-The implementation is fully backward compatible:
-
-| Input Type | Example | Output | Notes |
-|------------|---------|--------|-------|
-| Color Slug | `"primary"` | `"var(--wp--preset--color--primary)"` | New behavior |
-| Hex Value | `"#ff0000"` | `"#ff0000"` | Unchanged |
-| RGB Value | `"rgb(255,0,0)"` | `"rgb(255,0,0)"` | Unchanged |
-| HSL Value | `"hsl(0,100%,50%)"` | `"hsl(0,100%,50%)"` | Unchanged |
-| CSS Variable | `"var(--custom)"` | `"var(--custom)"` | Unchanged |
-
-## Affected Blocks
-
-All 15 Otter blocks with color attributes automatically benefit from this change:
-
-1. advanced-heading
-2. button-group/button
-3. section/column
-4. section/columns
-5. font-awesome-icons
-6. posts
-7. review
-8. progress-bar
-9. circle-counter
-10. countdown
-11. sharing-icons
-12. popup
-13. flip
-14. lottie
-15. modal
-
-## Testing
-
-### Automated Tests
-
-```javascript
-// Test cases verified:
-✓ "primary" → "var(--wp--preset--color--primary)"
-✓ "base" → "var(--wp--preset--color--base)"
-✓ "#ff0000" → "#ff0000"
-✓ "rgb(255, 0, 0)" → "rgb(255, 0, 0)"
-✓ "var(--custom-color)" → "var(--custom-color)"
-✓ "Primary-Color_123" → "var(--wp--preset--color--primary-color123)"
-✓ "test@color!" → "var(--wp--preset--color--testcolor)"
-```
-
-### Manual Testing Steps
-
-1. **Setup:**
- - Create/edit a theme with custom colors in `theme.json`
- - Add Otter blocks using color slugs
-
-2. **Test Color Slug:**
- ```javascript
- wp.data.dispatch('core/block-editor').insertBlocks(
- wp.blocks.createBlock('themeisle-blocks/advanced-heading', {
- headingColor: "primary",
- content: "Test Heading"
- })
- );
- ```
-
-3. **Verify:**
- - Check that block displays correctly in editor
- - Inspect CSS to see `var(--wp--preset--color--primary)`
-
-4. **Test Theme Change:**
- - Change the `primary` color value in `theme.json`
- - Refresh page
- - Verify block color updates automatically (no re-save needed)
-
-5. **Test Backward Compatibility:**
- - Use hex value: `headingColor: "#ff0000"`
- - Verify it still works correctly
-
-## Benefits
-
-1. **Auto-updates:** Theme color changes apply instantly to all blocks
-2. **No re-save needed:** Blocks maintain connection to theme palette
-3. **Core consistency:** Matches WordPress core block behavior
-4. **Maintainability:** Easier theme customization and testing
-5. **Performance:** No palette lookup needed at runtime
-6. **Security:** Slug sanitization prevents CSS injection
-
-## Migration Notes
-
-**For Existing Blocks:**
-- No migration needed! Existing hex/rgb values continue to work
-- New blocks using slugs will automatically use CSS variables
-- Old blocks can be updated by changing to slug references
-
-**For Developers:**
-- Use `getColorCSSVariable()` for new code (explicit)
-- Use `useColorResolver()` hook in React components (convenient)
-- `resolveColorValue()` maintained for backward compatibility
-
-## Related Files
-
-- `src/blocks/helpers/helper-functions.js` - Core conversion logic
-- `src/blocks/helpers/utility-hooks.js` - React hook wrapper
-- `inc/class-base-css.php` - PHP conversion logic
-- `docs/color-slug-resolution.md` - User documentation
-- All block `edit.js` files - Using the utilities
-- All block CSS PHP files - Using the utilities
-
-## References
-
-- [WordPress Block Editor Handbook - Global Settings](https://developer.wordpress.org/block-editor/how-to-guides/themes/global-settings-and-styles/)
-- [WordPress Block Supports](https://developer.wordpress.org/block-editor/reference-guides/block-api/block-supports/)
-- [theme.json Reference](https://developer.wordpress.org/block-editor/reference-guides/theme-json-reference/theme-json-living/)
diff --git a/src/blocks/test/unit/helper-functions.test.ts b/src/blocks/test/unit/helper-functions.test.ts
index c4db851be..5114758cb 100644
--- a/src/blocks/test/unit/helper-functions.test.ts
+++ b/src/blocks/test/unit/helper-functions.test.ts
@@ -1,4 +1,4 @@
-import { boxToCSS, boxValues, buildResponsiveGetAttributes, buildResponsiveSetAttributes, compactObject, getChoice, mergeBoxDefaultValues, removeBoxDefaultValues, stringToBox } from '../../helpers/helper-functions.js';
+import { boxToCSS, boxValues, buildResponsiveGetAttributes, buildResponsiveSetAttributes, compactObject, getChoice, getColorCSSVariable, mergeBoxDefaultValues, removeBoxDefaultValues, resolveColorValue, stringToBox } from '../../helpers/helper-functions.js';
describe( 'Box Values Function', () => {
@@ -239,3 +239,80 @@ describe( 'Compact Object Function', () => {
expect( compactObject({ a: {}, b: {}}) ).toBeUndefined();
});
});
+
+describe( 'Get Color CSS Variable Function', () => {
+ it( 'should convert a color slug to a CSS variable', () => {
+ expect( getColorCSSVariable( 'primary' ) ).toEqual( 'var(--wp--preset--color--primary)' );
+ expect( getColorCSSVariable( 'base' ) ).toEqual( 'var(--wp--preset--color--base)' );
+ expect( getColorCSSVariable( 'contrast' ) ).toEqual( 'var(--wp--preset--color--contrast)' );
+ });
+
+ it( 'should pass through hex color values unchanged', () => {
+ expect( getColorCSSVariable( '#ff0000' ) ).toEqual( '#ff0000' );
+ expect( getColorCSSVariable( '#0073aa' ) ).toEqual( '#0073aa' );
+ expect( getColorCSSVariable( '#000' ) ).toEqual( '#000' );
+ });
+
+ it( 'should pass through rgb color values unchanged', () => {
+ expect( getColorCSSVariable( 'rgb(255, 0, 0)' ) ).toEqual( 'rgb(255, 0, 0)' );
+ expect( getColorCSSVariable( 'rgba(255, 0, 0, 0.5)' ) ).toEqual( 'rgba(255, 0, 0, 0.5)' );
+ });
+
+ it( 'should pass through hsl color values unchanged', () => {
+ expect( getColorCSSVariable( 'hsl(0, 100%, 50%)' ) ).toEqual( 'hsl(0, 100%, 50%)' );
+ expect( getColorCSSVariable( 'hsla(0, 100%, 50%, 0.5)' ) ).toEqual( 'hsla(0, 100%, 50%, 0.5)' );
+ });
+
+ it( 'should pass through existing CSS variables unchanged', () => {
+ expect( getColorCSSVariable( 'var(--custom-color)' ) ).toEqual( 'var(--custom-color)' );
+ expect( getColorCSSVariable( 'var(--my-brand-color)' ) ).toEqual( 'var(--my-brand-color)' );
+ });
+
+ it( 'should sanitize color slugs to prevent CSS injection', () => {
+ expect( getColorCSSVariable( 'Primary-Color' ) ).toEqual( 'var(--wp--preset--color--primary-color)' );
+ expect( getColorCSSVariable( 'test@color!' ) ).toEqual( 'var(--wp--preset--color--testcolor)' );
+ expect( getColorCSSVariable( 'my_special_color' ) ).toEqual( 'var(--wp--preset--color--myspecialcolor)' );
+ expect( getColorCSSVariable( 'Color123' ) ).toEqual( 'var(--wp--preset--color--color123)' );
+ });
+
+ it( 'should handle undefined and empty values', () => {
+ expect( getColorCSSVariable( undefined ) ).toBeUndefined();
+ expect( getColorCSSVariable( '' ) ).toEqual( '' );
+ });
+
+ it( 'should preserve hyphens in slugs', () => {
+ expect( getColorCSSVariable( 'primary-blue' ) ).toEqual( 'var(--wp--preset--color--primary-blue)' );
+ expect( getColorCSSVariable( 'dark-gray-100' ) ).toEqual( 'var(--wp--preset--color--dark-gray-100)' );
+ });
+});
+
+describe( 'Resolve Color Value Function', () => {
+ it( 'should convert color slugs to CSS variables', () => {
+ expect( resolveColorValue( 'primary' ) ).toEqual( 'var(--wp--preset--color--primary)' );
+ expect( resolveColorValue( 'base' ) ).toEqual( 'var(--wp--preset--color--base)' );
+ });
+
+ it( 'should pass through hex values unchanged', () => {
+ expect( resolveColorValue( '#ff0000' ) ).toEqual( '#ff0000' );
+ expect( resolveColorValue( '#0073aa' ) ).toEqual( '#0073aa' );
+ });
+
+ it( 'should pass through rgb values unchanged', () => {
+ expect( resolveColorValue( 'rgb(255, 0, 0)' ) ).toEqual( 'rgb(255, 0, 0)' );
+ });
+
+ it( 'should work with deprecated palette parameter (ignored)', () => {
+ const palette = [
+ { slug: 'primary', color: '#0073aa', name: 'Primary' },
+ { slug: 'base', color: '#000000', name: 'Base' }
+ ];
+
+ // Palette parameter is now ignored, should still convert to CSS variable
+ expect( resolveColorValue( 'primary', palette ) ).toEqual( 'var(--wp--preset--color--primary)' );
+ expect( resolveColorValue( 'base', palette ) ).toEqual( 'var(--wp--preset--color--base)' );
+ });
+
+ it( 'should handle undefined values', () => {
+ expect( resolveColorValue( undefined ) ).toBeUndefined();
+ });
+});
From cd5785030237831afb727a53e94f88cd83817f30 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Thu, 19 Feb 2026 10:58:01 +0000
Subject: [PATCH 13/25] Allow underscores in color slug sanitization per
WordPress conventions
Co-authored-by: selul <3330746+selul@users.noreply.github.com>
---
inc/class-base-css.php | 4 ++--
src/blocks/helpers/helper-functions.js | 4 ++--
src/blocks/test/unit/helper-functions.test.ts | 6 ++++--
3 files changed, 8 insertions(+), 6 deletions(-)
diff --git a/inc/class-base-css.php b/inc/class-base-css.php
index 9200278ce..8d804cc3b 100644
--- a/inc/class-base-css.php
+++ b/inc/class-base-css.php
@@ -226,9 +226,9 @@ public static function get_color_css_variable( $slug ) {
return $slug;
}
- // Sanitize slug: WordPress slugs should only contain lowercase alphanumeric and hyphens.
+ // Sanitize slug: WordPress slugs should only contain lowercase alphanumeric, hyphens, and underscores.
// This prevents potential CSS injection if slug comes from untrusted sources.
- $sanitized_slug = strtolower( preg_replace( '/[^a-z0-9-]/', '', $slug ) );
+ $sanitized_slug = strtolower( preg_replace( '/[^a-z0-9-_]/', '', $slug ) );
// Convert slug to CSS variable.
return 'var(--wp--preset--color--' . $sanitized_slug . ')';
diff --git a/src/blocks/helpers/helper-functions.js b/src/blocks/helpers/helper-functions.js
index ecd714434..26debde47 100644
--- a/src/blocks/helpers/helper-functions.js
+++ b/src/blocks/helpers/helper-functions.js
@@ -382,9 +382,9 @@ export const getColorCSSVariable = ( slug ) => {
return slug;
}
- // Sanitize slug: WordPress slugs should only contain lowercase alphanumeric and hyphens
+ // Sanitize slug: WordPress slugs should only contain lowercase alphanumeric, hyphens, and underscores
// This prevents potential CSS injection if slug comes from untrusted sources
- const sanitizedSlug = slug.toLowerCase().replace( /[^a-z0-9-]/g, '' );
+ const sanitizedSlug = slug.toLowerCase().replace( /[^a-z0-9-_]/g, '' );
// Convert slug to CSS variable
return `var(--wp--preset--color--${ sanitizedSlug })`;
diff --git a/src/blocks/test/unit/helper-functions.test.ts b/src/blocks/test/unit/helper-functions.test.ts
index 5114758cb..c679a1b73 100644
--- a/src/blocks/test/unit/helper-functions.test.ts
+++ b/src/blocks/test/unit/helper-functions.test.ts
@@ -271,7 +271,7 @@ describe( 'Get Color CSS Variable Function', () => {
it( 'should sanitize color slugs to prevent CSS injection', () => {
expect( getColorCSSVariable( 'Primary-Color' ) ).toEqual( 'var(--wp--preset--color--primary-color)' );
expect( getColorCSSVariable( 'test@color!' ) ).toEqual( 'var(--wp--preset--color--testcolor)' );
- expect( getColorCSSVariable( 'my_special_color' ) ).toEqual( 'var(--wp--preset--color--myspecialcolor)' );
+ expect( getColorCSSVariable( 'my_special_color' ) ).toEqual( 'var(--wp--preset--color--my_special_color)' );
expect( getColorCSSVariable( 'Color123' ) ).toEqual( 'var(--wp--preset--color--color123)' );
});
@@ -280,9 +280,11 @@ describe( 'Get Color CSS Variable Function', () => {
expect( getColorCSSVariable( '' ) ).toEqual( '' );
});
- it( 'should preserve hyphens in slugs', () => {
+ it( 'should preserve hyphens and underscores in slugs', () => {
expect( getColorCSSVariable( 'primary-blue' ) ).toEqual( 'var(--wp--preset--color--primary-blue)' );
expect( getColorCSSVariable( 'dark-gray-100' ) ).toEqual( 'var(--wp--preset--color--dark-gray-100)' );
+ expect( getColorCSSVariable( 'secondary_dark' ) ).toEqual( 'var(--wp--preset--color--secondary_dark)' );
+ expect( getColorCSSVariable( 'accent_1' ) ).toEqual( 'var(--wp--preset--color--accent_1)' );
});
});
From 890c765094ef355ac92045bbd0d65a3138f545bc Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Thu, 19 Feb 2026 11:10:08 +0000
Subject: [PATCH 14/25] Remove color-slug-resolution.md documentation file
Co-authored-by: selul <3330746+selul@users.noreply.github.com>
---
docs/color-slug-resolution.md | 217 ----------------------------------
1 file changed, 217 deletions(-)
delete mode 100644 docs/color-slug-resolution.md
diff --git a/docs/color-slug-resolution.md b/docs/color-slug-resolution.md
deleted file mode 100644
index 70596a63f..000000000
--- a/docs/color-slug-resolution.md
+++ /dev/null
@@ -1,217 +0,0 @@
-# Color Slug Resolution in Otter Blocks
-
-## Problem
-
-WordPress themes define color palettes in `theme.json` using color slugs (e.g., "primary", "base", "contrast"). Core WordPress blocks can reference these colors by slug, and they automatically update when theme colors change.
-
-Otter Blocks were not properly handling color slugs - they were converting slugs to hex values at render time, which broke the connection to theme.json. When theme colors changed, blocks didn't update.
-
-## Solution
-
-We've implemented the same approach as WordPress core blocks: **using CSS variables to preserve the connection to theme.json**.
-
-### How It Works
-
-WordPress automatically generates CSS variables from `theme.json` color palette:
-```css
-/* WordPress generates these automatically */
-:root {
- --wp--preset--color--primary: #0073aa;
- --wp--preset--color--base: #000000;
- --wp--preset--color--contrast: #ffffff;
-}
-```
-
-When you use a color slug in Otter blocks, it's converted to a CSS variable reference:
-```javascript
-// Input: slug "primary"
-// Output: "var(--wp--preset--color--primary)"
-```
-
-**Key Benefit:** When theme.json changes, the CSS variable value updates automatically, and all blocks using that slug instantly reflect the new color - no block re-save needed!
-
-### JavaScript Solution
-
-#### Option 1: Use the `useColorResolver` Hook (Recommended)
-
-```javascript
-import { useColorResolver } from '../../helpers/utility-hooks.js';
-
-const Edit = ({ attributes, setAttributes }) => {
- // Get the color resolver function
- const resolveColor = useColorResolver();
-
- // Converts slugs to CSS variables
- // "primary" → "var(--wp--preset--color--primary)"
- // "#ff0000" → "#ff0000" (hex values passed through)
- const resolvedBackgroundColor = resolveColor(attributes.backgroundColor);
- const resolvedTextColor = resolveColor(attributes.textColor);
-
- // Apply to styles
- const style = {
- backgroundColor: resolvedBackgroundColor,
- color: resolvedTextColor
- };
-
- return
...
;
-};
-```
-
-#### Option 2: Use `getColorCSSVariable` Directly (Recommended for New Code)
-
-```javascript
-import { getColorCSSVariable } from '../../helpers/helper-functions';
-
-const Edit = ({ attributes, setAttributes }) => {
- // Direct conversion of slug to CSS variable
- const colorVar = getColorCSSVariable(attributes.backgroundColor);
-
- // "primary" → "var(--wp--preset--color--primary)"
- // "#ff0000" → "#ff0000"
-
- return
...
;
-};
-```
-
-**Note:** `resolveColorValue()` is also available as a wrapper for backward compatibility, but `getColorCSSVariable()` is recommended for new code as it's more explicit about its purpose.
-
-### PHP Solution
-
-In your block's CSS class (e.g., `class-my-block-css.php`), use the `Base_CSS::resolve_color_value()` method:
-
-```php
-$css->add_item(
- array(
- 'properties' => array(
- array(
- 'property' => 'background-color',
- 'value' => 'backgroundColor',
- 'format' => function ( $value, $attrs ) {
- return Base_CSS::resolve_color_value( $value );
- },
- ),
- array(
- 'property' => 'color',
- 'value' => 'textColor',
- 'format' => function ( $value, $attrs ) {
- return Base_CSS::resolve_color_value( $value );
- },
- ),
- ),
- )
-);
-```
-
-**Result:**
-- Input slug: `"primary"`
-- Output CSS: `background-color: var(--wp--preset--color--primary);`
-- Input hex: `"#ff0000"`
-- Output CSS: `background-color: #ff0000;`
-
-## How It Works
-
-### Color Resolution Logic
-
-The resolver converts color slugs to CSS variables:
-1. Checks if the value is already a color (starts with `#`, `rgb`, `hsl`, or `var(`)
-2. If it's a hex/rgb/hsl value, returns it unchanged
-3. If it's a slug (anything else), converts to CSS variable: `var(--wp--preset--color--{slug})`
-
-**No palette lookup needed!** WordPress handles the CSS variable definitions automatically.
-
-### WordPress CSS Variable Generation
-
-WordPress reads `theme.json` and automatically generates CSS variables:
-
-```json
-// theme.json
-{
- "settings": {
- "color": {
- "palette": [
- { "slug": "primary", "color": "#0073aa", "name": "Primary" },
- { "slug": "base", "color": "#000000", "name": "Base" }
- ]
- }
- }
-}
-```
-
-WordPress outputs:
-```css
-:root {
- --wp--preset--color--primary: #0073aa;
- --wp--preset--color--base: #000000;
-}
-```
-
-When you change colors in `theme.json`, WordPress updates the CSS variables, and all blocks automatically reflect the change!
-
-## Example: Advanced Heading Block
-
-The advanced-heading block has been updated as a reference implementation. See:
-- JavaScript: `/src/blocks/blocks/advanced-heading/edit.js`
-- PHP: `/inc/css/blocks/class-advanced-heading-css.php`
-
-## Blocks Updated
-
-Based on analysis, these blocks have color attributes and should be updated:
-
-**High Priority:**
-1. Posts Block (multiple color attributes)
-2. Section/Column Blocks (backgroundColor, borderColor)
-3. Font Awesome Icons Block (textColor, backgroundColor, hover colors)
-4. Button Block (color, background, border colors)
-5. Review Block (multiple color attributes)
-
-**Medium Priority:**
-6. Popup Block
-7. Progress Bar Block
-8. Circle Counter Block
-9. Countdown Block
-10. Sharing Icons Block
-
-## Testing
-
-To test color slug resolution:
-
-1. Create a theme with custom colors in `theme.json`:
-```json
-{
- "version": 2,
- "settings": {
- "color": {
- "palette": [
- {
- "slug": "base",
- "color": "#000000",
- "name": "Base"
- },
- {
- "slug": "primary",
- "color": "#0073aa",
- "name": "Primary"
- }
- ]
- }
- }
-}
-```
-
-2. Create a block programmatically with a color slug:
-```javascript
-wp.data.dispatch('core/block-editor').insertBlocks(
- wp.blocks.createBlock('themeisle-blocks/advanced-heading', {
- headingColor: "base",
- content: "Test"
- })
-);
-```
-
-3. Verify that the correct color is applied both in the editor and on the frontend.
-
-## Notes
-
-- This fix is backward compatible - hex, rgb, and other color formats still work
-- The resolver only activates when a value looks like a slug (no `#`, `rgb`, etc.)
-- The PHP resolver uses `wp_get_global_settings()` which requires WordPress 5.9+
From 8079ae9687e01cfa4722807bb6b493e791abdc9d Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Thu, 19 Feb 2026 12:17:23 +0000
Subject: [PATCH 15/25] Fix JSDoc alignment error in helper-functions.js
Co-authored-by: selul <3330746+selul@users.noreply.github.com>
---
src/blocks/helpers/helper-functions.js | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/src/blocks/helpers/helper-functions.js b/src/blocks/helpers/helper-functions.js
index 26debde47..f1fb41892 100644
--- a/src/blocks/helpers/helper-functions.js
+++ b/src/blocks/helpers/helper-functions.js
@@ -396,10 +396,10 @@ export const getColorCSSVariable = ( slug ) => {
* If the value is a slug, it returns a CSS variable reference.
* Otherwise, returns the value as-is (for hex, rgb, hsl values).
*
- * @param {string|undefined} value The color value or slug
- * @param {Array} palette Optional color palette array (deprecated, no longer used)
- * @return {string|undefined} The CSS variable or color value
* @deprecated The palette parameter is deprecated and no longer used. Use getColorCSSVariable() directly.
+ * @param {string|undefined} value The color value or slug
+ * @param {Array} palette Optional color palette array (deprecated, no longer used)
+ * @return {string|undefined} The CSS variable or color value
*/
export const resolveColorValue = ( value, palette = null ) => {
// Use CSS variable conversion for slugs
From e6c672ce6306a7119abbf9a9097ec92da77430a7 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Thu, 19 Feb 2026 16:54:08 +0000
Subject: [PATCH 16/25] Fix useDarkBackground to handle color slugs properly
Co-authored-by: selul <3330746+selul@users.noreply.github.com>
---
src/blocks/helpers/utility-hooks.js | 8 +++++---
1 file changed, 5 insertions(+), 3 deletions(-)
diff --git a/src/blocks/helpers/utility-hooks.js b/src/blocks/helpers/utility-hooks.js
index f51d45b49..23c57a860 100644
--- a/src/blocks/helpers/utility-hooks.js
+++ b/src/blocks/helpers/utility-hooks.js
@@ -10,7 +10,7 @@ import { useSetting } from '@wordpress/block-editor';
/**
* Internal dependencies
*/
-import { buildResponsiveGetAttributes, buildResponsiveSetAttributes, lightnessFromColor, resolveColorValue } from './helper-functions.js';
+import { buildResponsiveGetAttributes, buildResponsiveSetAttributes, lightnessFromColor, resolveColorValue, getColorCSSVariable } from './helper-functions.js';
/**
* Utiliy hook to get/set responsive attributes.
@@ -39,8 +39,10 @@ export const useResponsiveAttributes = ( setAttributes = () => {}) => useSelect(
*/
export const useDarkBackground = ( backgroundColor, attributes, setAttributes, darkClassName = 'has-dark-bg', lightClassName = 'has-light-bg' ) => {
useEffect( () => {
- const isDark = 'dark' === lightnessFromColor( backgroundColor );
- const isLight = 'light' === lightnessFromColor( backgroundColor );
+ // Resolve color slugs to CSS variables before checking lightness
+ const resolvedColor = getColorCSSVariable( backgroundColor );
+ const isDark = 'dark' === lightnessFromColor( resolvedColor );
+ const isLight = 'light' === lightnessFromColor( resolvedColor );
let classes = attributes.className || '';
From b66e4561fcfe09762b46afb38c3ef78f33488e3d Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Thu, 19 Feb 2026 21:24:28 +0000
Subject: [PATCH 17/25] Add robust type checking and error handling to color
functions
Co-authored-by: selul <3330746+selul@users.noreply.github.com>
---
src/blocks/helpers/helper-functions.js | 23 +++++++++++++++++++----
1 file changed, 19 insertions(+), 4 deletions(-)
diff --git a/src/blocks/helpers/helper-functions.js b/src/blocks/helpers/helper-functions.js
index f1fb41892..871f88a9e 100644
--- a/src/blocks/helpers/helper-functions.js
+++ b/src/blocks/helpers/helper-functions.js
@@ -331,14 +331,22 @@ export const hex2rgba = ( color, alpha = 100 ) => {
* @return {string|boolean}
*/
export const lightnessFromColor = color => {
- if ( ! color ) {
+ // Handle falsy values and non-string types
+ if ( ! color || typeof color !== 'string' ) {
return false;
}
let value = color;
+ // Handle CSS variables
if ( color.startsWith( 'var(' ) ) {
- value = getComputedStyle( document.documentElement ).getPropertyValue( color.slice( 4, -1 ) ).trim();
+ // Check if we're in a browser environment
+ if ( typeof document !== 'undefined' && document.documentElement ) {
+ value = getComputedStyle( document.documentElement ).getPropertyValue( color.slice( 4, -1 ) ).trim();
+ } else {
+ // In test/non-browser environment, can't resolve CSS variables
+ return false;
+ }
}
// Convert hex to RGB if necessary
@@ -351,7 +359,13 @@ export const lightnessFromColor = color => {
}
// Extract the red, green, and blue values
- const [ r, g, b ] = value.match( /\d+/g ).map( Number );
+ const matches = value.match( /\d+/g );
+ if ( ! matches || matches.length < 3 ) {
+ // Invalid color format
+ return false;
+ }
+
+ const [ r, g, b ] = matches.map( Number );
// Calculate the brightness value
const brightness = ( 0.299 * r ) + ( 0.587 * g ) + ( 0.114 * b );
@@ -368,7 +382,8 @@ export const lightnessFromColor = color => {
* @return {string|undefined} The CSS variable reference
*/
export const getColorCSSVariable = ( slug ) => {
- if ( ! slug ) {
+ // Handle all falsy values and non-string types
+ if ( ! slug || typeof slug !== 'string' ) {
return slug;
}
From 90a6c997354c616c85d13d2da97010d26cf182de Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Mon, 23 Feb 2026 09:07:05 +0000
Subject: [PATCH 18/25] Simplify useColorResolver hook - remove unused palette
dependency
Co-authored-by: selul <3330746+selul@users.noreply.github.com>
---
src/blocks/helpers/utility-hooks.js | 13 ++++++-------
1 file changed, 6 insertions(+), 7 deletions(-)
diff --git a/src/blocks/helpers/utility-hooks.js b/src/blocks/helpers/utility-hooks.js
index 23c57a860..d09217571 100644
--- a/src/blocks/helpers/utility-hooks.js
+++ b/src/blocks/helpers/utility-hooks.js
@@ -5,12 +5,10 @@ import { useSelect } from '@wordpress/data';
import { useEffect } from '@wordpress/element';
-import { useSetting } from '@wordpress/block-editor';
-
/**
* Internal dependencies
*/
-import { buildResponsiveGetAttributes, buildResponsiveSetAttributes, lightnessFromColor, resolveColorValue, getColorCSSVariable } from './helper-functions.js';
+import { buildResponsiveGetAttributes, buildResponsiveSetAttributes, lightnessFromColor, getColorCSSVariable } from './helper-functions.js';
/**
* Utiliy hook to get/set responsive attributes.
@@ -74,12 +72,13 @@ export const useDarkBackground = ( backgroundColor, attributes, setAttributes, d
/**
* Utility hook to resolve color slugs from the theme palette.
- * Returns a function that can resolve a color value (which may be a slug) to its actual color.
+ * Returns a function that can resolve a color value (which may be a slug) to a CSS variable.
+ * This preserves the connection to theme.json colors.
*
* @return {Function} A function that resolves color values/slugs.
*/
export const useColorResolver = () => {
- const colorPalette = useSetting( 'color.palette' ) || [];
-
- return ( colorValue ) => resolveColorValue( colorValue, colorPalette );
+ // Return the getColorCSSVariable function directly
+ // We don't need the palette since we use CSS variables
+ return getColorCSSVariable;
};
From 243efd229bd2793f4cee9b5ff1a1b48caa07285a Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Mon, 23 Feb 2026 09:11:39 +0000
Subject: [PATCH 19/25] Fix accordion and tabs blocks to use color slug
resolution
Co-authored-by: selul <3330746+selul@users.noreply.github.com>
---
src/blocks/blocks/accordion/group/edit.js | 15 ++++++++-------
src/blocks/blocks/tabs/group/edit.js | 21 ++++++++++++---------
2 files changed, 20 insertions(+), 16 deletions(-)
diff --git a/src/blocks/blocks/accordion/group/edit.js b/src/blocks/blocks/accordion/group/edit.js
index 5399ca46a..63e3e186e 100644
--- a/src/blocks/blocks/accordion/group/edit.js
+++ b/src/blocks/blocks/accordion/group/edit.js
@@ -25,7 +25,7 @@ import {
useCSSNode
} from '../../../helpers/block-utility.js';
-import { useDarkBackground } from '../../../helpers/utility-hooks.js';
+import { useDarkBackground, useColorResolver } from '../../../helpers/utility-hooks.js';
// @ts-ignore
import faIcons from '../../../../../assets/fontawesome/fa-icons.json';
@@ -70,12 +70,13 @@ const Edit = ({
}, []);
const getValue = field => getDefaultValueByField({ name, field, defaultAttributes, attributes });
+ const resolveColor = useColorResolver();
const inlineStyles = {
- '--title-color': getValue( 'titleColor' ),
- '--title-background': getValue( 'titleBackground' ),
- '--content-background': getValue( 'contentBackground' ),
- '--border-color': getValue( 'borderColor' ),
+ '--title-color': resolveColor( getValue( 'titleColor' ) ),
+ '--title-background': resolveColor( getValue( 'titleBackground' ) ),
+ '--content-background': resolveColor( getValue( 'contentBackground' ) ),
+ '--border-color': resolveColor( getValue( 'borderColor' ) ),
'--border-width': getValue( 'borderWidth' ),
'--box-shadow': attributes.boxShadow.active && `${attributes.boxShadow.horizontal}px ${attributes.boxShadow.vertical}px ${attributes.boxShadow.blur}px ${attributes.boxShadow.spread}px ${hex2rgba( attributes.boxShadow.color, attributes.boxShadow.colorOpacity )}`,
'--padding': boxValues( attributes.padding, { top: '18px', right: '24px', bottom: '18px', left: '24px' }),
@@ -119,8 +120,8 @@ const Edit = ({
const [ activeCSSNodeName, setActiveNodeCSS ] = useCSSNode();
useEffect( () => {
- const activeTitleColor = getValue( 'activeTitleColor' );
- const activeTitleBackground = getValue( 'activeTitleBackground' );
+ const activeTitleColor = resolveColor( getValue( 'activeTitleColor' ) );
+ const activeTitleBackground = resolveColor( getValue( 'activeTitleBackground' ) );
setActiveNodeCSS([
...( activeTitleColor ? [ `> * > * > .wp-block-themeisle-blocks-accordion-item.is-open > .wp-block-themeisle-blocks-accordion-item__title {
diff --git a/src/blocks/blocks/tabs/group/edit.js b/src/blocks/blocks/tabs/group/edit.js
index 7e942713a..2cd7fc5e2 100644
--- a/src/blocks/blocks/tabs/group/edit.js
+++ b/src/blocks/blocks/tabs/group/edit.js
@@ -38,7 +38,7 @@ import Controls from './controls.js';
import { blockInit, getDefaultValueByField } from '../../../helpers/block-utility.js';
import { boxToCSS, objectOrNumberAsBox, _px } from '../../../helpers/helper-functions';
import BlockAppender from '../../../components/block-appender-button';
-import { useDarkBackground } from '../../../helpers/utility-hooks.js';
+import { useDarkBackground, useColorResolver } from '../../../helpers/utility-hooks.js';
const { attributes: defaultAttributes } = metadata;
@@ -168,6 +168,9 @@ const Edit = ({
useDarkBackground( attributes.tabColor, attributes, setAttributes );
+ // Get color resolver to handle theme color slugs
+ const resolveColor = useColorResolver();
+
/**
* ------------ Tab Actions ------------
*/
@@ -213,14 +216,14 @@ const Edit = ({
const inlineStyles = {
'--title-border-width': boxToCSS( getSyncValue( 'titleBorderWidth' ) ),
'--border-width': boxToCSS( _px( getSyncValue( 'borderWidth' ) ) ),
- '--border-color': getSyncValue( 'borderColor' ),
- '--title-color': getSyncValue( 'titleColor' ),
- '--title-background': getSyncValue( 'titleBackgroundColor' ),
- '--tab-color': getSyncValue( 'tabColor' ),
- '--active-title-color': getSyncValue( 'activeTitleColor' ),
- '--active-title-background': getSyncValue( 'activeTitleBackgroundColor' ),
- '--active-title-border-color': getSyncValue( 'activeBorderColor' ),
- '--content-text-color': getSyncValue( 'contentTextColor' ),
+ '--border-color': resolveColor( getSyncValue( 'borderColor' ) ),
+ '--title-color': resolveColor( getSyncValue( 'titleColor' ) ),
+ '--title-background': resolveColor( getSyncValue( 'titleBackgroundColor' ) ),
+ '--tab-color': resolveColor( getSyncValue( 'tabColor' ) ),
+ '--active-title-color': resolveColor( getSyncValue( 'activeTitleColor' ) ),
+ '--active-title-background': resolveColor( getSyncValue( 'activeTitleBackgroundColor' ) ),
+ '--active-title-border-color': resolveColor( getSyncValue( 'activeBorderColor' ) ),
+ '--content-text-color': resolveColor( getSyncValue( 'contentTextColor' ) ),
'--title-padding': boxToCSS( getSyncValue( 'titlePadding' ) ),
'--content-padding': boxToCSS( getSyncValue( 'contentPadding' ) ),
'--border-side-width': 'left' === attributes.tabsPosition ? objectOrNumberAsBox( getSyncValue( 'borderWidth' ) )?.left : objectOrNumberAsBox( getSyncValue( 'borderWidth' ) )?.top,
From aaca3d9b13c9dec22bd8cd8590b8fe9fe41ba8cf Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Mon, 23 Feb 2026 09:45:46 +0000
Subject: [PATCH 20/25] Fix React Hooks dependencies in accordion block
Co-authored-by: selul <3330746+selul@users.noreply.github.com>
---
src/blocks/blocks/accordion/group/edit.js | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/src/blocks/blocks/accordion/group/edit.js b/src/blocks/blocks/accordion/group/edit.js
index 63e3e186e..eec2791e7 100644
--- a/src/blocks/blocks/accordion/group/edit.js
+++ b/src/blocks/blocks/accordion/group/edit.js
@@ -8,7 +8,7 @@ import googleFontsLoader from '../../../helpers/google-fonts.js';
* WordPress dependencies.
*/
import { InnerBlocks, useBlockProps } from '@wordpress/block-editor';
-import { Fragment, useEffect } from '@wordpress/element';
+import { Fragment, useEffect, useCallback } from '@wordpress/element';
import { useSelect } from '@wordpress/data';
import { __ } from '@wordpress/i18n';
@@ -69,7 +69,7 @@ const Edit = ({
}
}, []);
- const getValue = field => getDefaultValueByField({ name, field, defaultAttributes, attributes });
+ const getValue = useCallback( field => getDefaultValueByField({ name, field, defaultAttributes, attributes }), [ name, defaultAttributes, attributes ] );
const resolveColor = useColorResolver();
const inlineStyles = {
@@ -131,7 +131,7 @@ const Edit = ({
--title-background: ${ activeTitleBackground };
}` ] : [])
]);
- }, [ attributes.activeTitleColor, attributes.activeTitleBackground ]);
+ }, [ attributes.activeTitleColor, attributes.activeTitleBackground, getValue, resolveColor, setActiveNodeCSS ]);
useEffect( () => {
if ( attributes.fontFamily ) {
From 0c7f0b8bfae4407d079598039857594c47fab215 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Mon, 23 Feb 2026 09:48:22 +0000
Subject: [PATCH 21/25] Remove unnecessary dependency from useCallback in
accordion
Co-authored-by: selul <3330746+selul@users.noreply.github.com>
---
src/blocks/blocks/accordion/group/edit.js | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/blocks/blocks/accordion/group/edit.js b/src/blocks/blocks/accordion/group/edit.js
index eec2791e7..d8651532c 100644
--- a/src/blocks/blocks/accordion/group/edit.js
+++ b/src/blocks/blocks/accordion/group/edit.js
@@ -69,7 +69,7 @@ const Edit = ({
}
}, []);
- const getValue = useCallback( field => getDefaultValueByField({ name, field, defaultAttributes, attributes }), [ name, defaultAttributes, attributes ] );
+ const getValue = useCallback( field => getDefaultValueByField({ name, field, defaultAttributes, attributes }), [ name, attributes ] );
const resolveColor = useColorResolver();
const inlineStyles = {
From e008e63b87a7cf4d4e1a22a98b71974a415dea1d Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Mon, 23 Feb 2026 10:02:29 +0000
Subject: [PATCH 22/25] Revert useCallback wrapping of getValue to fix
accordion initialization
Co-authored-by: selul <3330746+selul@users.noreply.github.com>
---
src/blocks/blocks/accordion/group/edit.js | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/src/blocks/blocks/accordion/group/edit.js b/src/blocks/blocks/accordion/group/edit.js
index d8651532c..63e3e186e 100644
--- a/src/blocks/blocks/accordion/group/edit.js
+++ b/src/blocks/blocks/accordion/group/edit.js
@@ -8,7 +8,7 @@ import googleFontsLoader from '../../../helpers/google-fonts.js';
* WordPress dependencies.
*/
import { InnerBlocks, useBlockProps } from '@wordpress/block-editor';
-import { Fragment, useEffect, useCallback } from '@wordpress/element';
+import { Fragment, useEffect } from '@wordpress/element';
import { useSelect } from '@wordpress/data';
import { __ } from '@wordpress/i18n';
@@ -69,7 +69,7 @@ const Edit = ({
}
}, []);
- const getValue = useCallback( field => getDefaultValueByField({ name, field, defaultAttributes, attributes }), [ name, attributes ] );
+ const getValue = field => getDefaultValueByField({ name, field, defaultAttributes, attributes });
const resolveColor = useColorResolver();
const inlineStyles = {
@@ -131,7 +131,7 @@ const Edit = ({
--title-background: ${ activeTitleBackground };
}` ] : [])
]);
- }, [ attributes.activeTitleColor, attributes.activeTitleBackground, getValue, resolveColor, setActiveNodeCSS ]);
+ }, [ attributes.activeTitleColor, attributes.activeTitleBackground ]);
useEffect( () => {
if ( attributes.fontFamily ) {
From d5e0cf54bf779fdcb159271cb96f58bf7175aa40 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Mon, 23 Feb 2026 12:57:42 +0000
Subject: [PATCH 23/25] Add test infrastructure for e2e tests (emptytheme,
wp-env.json)
Co-authored-by: selul <3330746+selul@users.noreply.github.com>
---
.wp-env.json | 20 ++++++++++++++++++++
test/emptytheme/functions.php | 13 +++++++++++++
test/emptytheme/index.php | 27 +++++++++++++++++++++++++++
test/emptytheme/style.css | 7 +++++++
4 files changed, 67 insertions(+)
create mode 100644 .wp-env.json
create mode 100644 test/emptytheme/functions.php
create mode 100644 test/emptytheme/index.php
create mode 100644 test/emptytheme/style.css
diff --git a/.wp-env.json b/.wp-env.json
new file mode 100644
index 000000000..925a064a3
--- /dev/null
+++ b/.wp-env.json
@@ -0,0 +1,20 @@
+{
+ "core": "WordPress/WordPress#6.4",
+ "plugins": [
+ "."
+ ],
+ "themes": [
+ "./test/emptytheme"
+ ],
+ "config": {
+ "WP_DEBUG": true,
+ "WP_DEBUG_DISPLAY": true,
+ "SCRIPT_DEBUG": true
+ },
+ "port": 8889,
+ "env": {
+ "tests": {
+ "port": 8889
+ }
+ }
+}
diff --git a/test/emptytheme/functions.php b/test/emptytheme/functions.php
new file mode 100644
index 000000000..e4a2231db
--- /dev/null
+++ b/test/emptytheme/functions.php
@@ -0,0 +1,13 @@
+
+
+>
+
+
+
+
+
+>
+
+
+
+
+
+
+
diff --git a/test/emptytheme/style.css b/test/emptytheme/style.css
new file mode 100644
index 000000000..0c7ee5cab
--- /dev/null
+++ b/test/emptytheme/style.css
@@ -0,0 +1,7 @@
+/*
+Theme Name: Empty Theme
+Theme URI: https://example.com
+Description: A minimal empty theme for testing
+Version: 1.0
+Author: Test
+*/
From 8d9714db905e6b8339a9e4d599549dd2b9f232a6 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Mon, 23 Feb 2026 12:58:36 +0000
Subject: [PATCH 24/25] Add comprehensive E2E test setup documentation
Co-authored-by: selul <3330746+selul@users.noreply.github.com>
---
.wp-env.json | 2 +-
E2E_TEST_SETUP.md | 209 ++++++++++++++++++++++++++++++++++++++++++++++
2 files changed, 210 insertions(+), 1 deletion(-)
create mode 100644 E2E_TEST_SETUP.md
diff --git a/.wp-env.json b/.wp-env.json
index 925a064a3..446d76283 100644
--- a/.wp-env.json
+++ b/.wp-env.json
@@ -1,5 +1,5 @@
{
- "core": "WordPress/WordPress#6.4",
+ "core": null,
"plugins": [
"."
],
diff --git a/E2E_TEST_SETUP.md b/E2E_TEST_SETUP.md
new file mode 100644
index 000000000..90ede2fda
--- /dev/null
+++ b/E2E_TEST_SETUP.md
@@ -0,0 +1,209 @@
+# E2E Test Setup Guide
+
+This guide explains how to set up and run the end-to-end (e2e) tests for Otter Blocks.
+
+## Prerequisites
+
+1. **Docker**: Docker must be installed and running
+ ```bash
+ docker --version
+ docker ps
+ ```
+
+2. **Node.js**: Node.js and npm must be installed
+ ```bash
+ node --version
+ npm --version
+ ```
+
+3. **Network Access**: Internet connection is required to download WordPress and dependencies
+
+## Setup Steps
+
+### 1. Install Dependencies
+
+```bash
+npm ci
+```
+
+This installs all required packages including:
+- `@wordpress/env` - WordPress environment manager
+- `@playwright/test` - Playwright testing framework
+- All other project dependencies
+
+### 2. Install Playwright Browsers
+
+```bash
+npx playwright install chromium
+```
+
+This downloads the Chromium browser binary required for running tests.
+
+### 3. Start WordPress Environment
+
+```bash
+npm run wp-env start
+```
+
+This command:
+- Downloads WordPress (if not cached)
+- Creates Docker containers
+- Sets up a test WordPress site at http://localhost:8889
+- Installs the Otter Blocks plugin
+- Configures test themes and plugins
+
+**Note**: First run may take 5-10 minutes to download WordPress and set up containers.
+
+### 4. Build the Plugin
+
+```bash
+npm run build
+```
+
+Or for development builds:
+```bash
+npm run build-dev
+```
+
+### 5. Run E2E Tests
+
+```bash
+npm run test:e2e:playwright
+```
+
+Additional test commands:
+- `npm run test:e2e:playwright-ui` - Run tests with Playwright UI
+- `npm run test:performance` - Run performance tests
+
+## Troubleshooting
+
+### Network Issues
+
+If you see errors about network unavailability:
+```
+✖ Could not find the current WordPress version in the cache and the network is not available.
+```
+
+**Solutions:**
+1. Ensure you have internet connectivity
+2. Check if your firewall allows Docker to access the internet
+3. Try clearing the wp-env cache: `npm run wp-env clean all`
+
+### Docker Issues
+
+If Docker is not running:
+```bash
+# On Linux/Mac
+sudo service docker start
+
+# Or check Docker Desktop application
+```
+
+### Port Conflicts
+
+If port 8889 is already in use, you can:
+1. Stop the conflicting service
+2. Change the port in `.wp-env.json` (update both `port` and `env.tests.port`)
+
+### Reset Environment
+
+To completely reset the test environment:
+```bash
+npm run wp-env clean all
+npm run wp-env start
+```
+
+## Test Structure
+
+- **E2E Tests**: `src/blocks/test/e2e/blocks/`
+- **Test Configuration**: `src/blocks/test/e2e/playwright.config.js`
+- **Test Theme**: `test/emptytheme/`
+- **Test Plugins**: `packages/e2e-tests/plugins/`
+- **MU Plugins**: `packages/e2e-tests/mu-plugins/`
+
+## Configuration Files
+
+- `.wp-env.json` - Base WordPress environment configuration
+- `.wp-env.override.json` - Override configuration with additional settings
+- `playwright.config.js` - Playwright test configuration
+
+## Continuous Integration
+
+In CI environments (GitHub Actions), the workflow:
+1. Installs dependencies
+2. Installs Playwright browsers
+3. Starts wp-env
+4. Builds the plugin
+5. Runs e2e tests
+6. Uploads test artifacts
+
+See `.github/workflows/e2e-js.yml` for the complete CI configuration.
+
+## Development Tips
+
+### Watch Mode
+
+For development, you can run tests in watch mode:
+```bash
+npx playwright test --config src/blocks/test/e2e/playwright.config.js --ui
+```
+
+### Debug Mode
+
+To debug failing tests:
+```bash
+npx playwright test --config src/blocks/test/e2e/playwright.config.js --debug
+```
+
+### Specific Tests
+
+To run a specific test file:
+```bash
+npx playwright test src/blocks/test/e2e/blocks/accordion.spec.js
+```
+
+### Screenshots and Videos
+
+Test artifacts (screenshots, videos) are saved to:
+- `artifacts/test-results/` - Test results and failure screenshots
+- `artifacts/storage-states/` - Authentication state
+
+## Common Issues
+
+### Tests Failing After Code Changes
+
+1. Rebuild the plugin: `npm run build`
+2. Restart wp-env: `npm run wp-env restart`
+3. Clear browser cache if needed
+
+### Authentication Issues
+
+The tests use stored authentication state. If you see login failures:
+1. Check `artifacts/storage-states/admin.json` exists
+2. Verify the global setup script runs: `src/blocks/test/e2e/global-setup.ts`
+
+### Timeout Errors
+
+If tests timeout, you can increase the timeout in `playwright.config.js`:
+```javascript
+timeout: 100_000, // 100 seconds (default)
+```
+
+## Getting Help
+
+- Check test output for specific error messages
+- Review test artifacts in `artifacts/` directory
+- Check Docker logs: `docker logs
`
+- Verify WordPress is running: http://localhost:8889
+
+## Cleaning Up
+
+After testing, to stop and remove containers:
+```bash
+npm run wp-env stop
+```
+
+To completely remove all wp-env data:
+```bash
+npm run wp-env destroy
+```
From dda1c64e8d970bc3d29deb34ca3e3ea3e2444698 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Mon, 23 Feb 2026 13:02:45 +0000
Subject: [PATCH 25/25] Revert e2e testing infrastructure additions (already
exists in repo)
Co-authored-by: selul <3330746+selul@users.noreply.github.com>
---
.wp-env.json | 20 ----
E2E_TEST_SETUP.md | 209 ----------------------------------
test/emptytheme/functions.php | 13 ---
test/emptytheme/index.php | 27 -----
test/emptytheme/style.css | 7 --
5 files changed, 276 deletions(-)
delete mode 100644 .wp-env.json
delete mode 100644 E2E_TEST_SETUP.md
delete mode 100644 test/emptytheme/functions.php
delete mode 100644 test/emptytheme/index.php
delete mode 100644 test/emptytheme/style.css
diff --git a/.wp-env.json b/.wp-env.json
deleted file mode 100644
index 446d76283..000000000
--- a/.wp-env.json
+++ /dev/null
@@ -1,20 +0,0 @@
-{
- "core": null,
- "plugins": [
- "."
- ],
- "themes": [
- "./test/emptytheme"
- ],
- "config": {
- "WP_DEBUG": true,
- "WP_DEBUG_DISPLAY": true,
- "SCRIPT_DEBUG": true
- },
- "port": 8889,
- "env": {
- "tests": {
- "port": 8889
- }
- }
-}
diff --git a/E2E_TEST_SETUP.md b/E2E_TEST_SETUP.md
deleted file mode 100644
index 90ede2fda..000000000
--- a/E2E_TEST_SETUP.md
+++ /dev/null
@@ -1,209 +0,0 @@
-# E2E Test Setup Guide
-
-This guide explains how to set up and run the end-to-end (e2e) tests for Otter Blocks.
-
-## Prerequisites
-
-1. **Docker**: Docker must be installed and running
- ```bash
- docker --version
- docker ps
- ```
-
-2. **Node.js**: Node.js and npm must be installed
- ```bash
- node --version
- npm --version
- ```
-
-3. **Network Access**: Internet connection is required to download WordPress and dependencies
-
-## Setup Steps
-
-### 1. Install Dependencies
-
-```bash
-npm ci
-```
-
-This installs all required packages including:
-- `@wordpress/env` - WordPress environment manager
-- `@playwright/test` - Playwright testing framework
-- All other project dependencies
-
-### 2. Install Playwright Browsers
-
-```bash
-npx playwright install chromium
-```
-
-This downloads the Chromium browser binary required for running tests.
-
-### 3. Start WordPress Environment
-
-```bash
-npm run wp-env start
-```
-
-This command:
-- Downloads WordPress (if not cached)
-- Creates Docker containers
-- Sets up a test WordPress site at http://localhost:8889
-- Installs the Otter Blocks plugin
-- Configures test themes and plugins
-
-**Note**: First run may take 5-10 minutes to download WordPress and set up containers.
-
-### 4. Build the Plugin
-
-```bash
-npm run build
-```
-
-Or for development builds:
-```bash
-npm run build-dev
-```
-
-### 5. Run E2E Tests
-
-```bash
-npm run test:e2e:playwright
-```
-
-Additional test commands:
-- `npm run test:e2e:playwright-ui` - Run tests with Playwright UI
-- `npm run test:performance` - Run performance tests
-
-## Troubleshooting
-
-### Network Issues
-
-If you see errors about network unavailability:
-```
-✖ Could not find the current WordPress version in the cache and the network is not available.
-```
-
-**Solutions:**
-1. Ensure you have internet connectivity
-2. Check if your firewall allows Docker to access the internet
-3. Try clearing the wp-env cache: `npm run wp-env clean all`
-
-### Docker Issues
-
-If Docker is not running:
-```bash
-# On Linux/Mac
-sudo service docker start
-
-# Or check Docker Desktop application
-```
-
-### Port Conflicts
-
-If port 8889 is already in use, you can:
-1. Stop the conflicting service
-2. Change the port in `.wp-env.json` (update both `port` and `env.tests.port`)
-
-### Reset Environment
-
-To completely reset the test environment:
-```bash
-npm run wp-env clean all
-npm run wp-env start
-```
-
-## Test Structure
-
-- **E2E Tests**: `src/blocks/test/e2e/blocks/`
-- **Test Configuration**: `src/blocks/test/e2e/playwright.config.js`
-- **Test Theme**: `test/emptytheme/`
-- **Test Plugins**: `packages/e2e-tests/plugins/`
-- **MU Plugins**: `packages/e2e-tests/mu-plugins/`
-
-## Configuration Files
-
-- `.wp-env.json` - Base WordPress environment configuration
-- `.wp-env.override.json` - Override configuration with additional settings
-- `playwright.config.js` - Playwright test configuration
-
-## Continuous Integration
-
-In CI environments (GitHub Actions), the workflow:
-1. Installs dependencies
-2. Installs Playwright browsers
-3. Starts wp-env
-4. Builds the plugin
-5. Runs e2e tests
-6. Uploads test artifacts
-
-See `.github/workflows/e2e-js.yml` for the complete CI configuration.
-
-## Development Tips
-
-### Watch Mode
-
-For development, you can run tests in watch mode:
-```bash
-npx playwright test --config src/blocks/test/e2e/playwright.config.js --ui
-```
-
-### Debug Mode
-
-To debug failing tests:
-```bash
-npx playwright test --config src/blocks/test/e2e/playwright.config.js --debug
-```
-
-### Specific Tests
-
-To run a specific test file:
-```bash
-npx playwright test src/blocks/test/e2e/blocks/accordion.spec.js
-```
-
-### Screenshots and Videos
-
-Test artifacts (screenshots, videos) are saved to:
-- `artifacts/test-results/` - Test results and failure screenshots
-- `artifacts/storage-states/` - Authentication state
-
-## Common Issues
-
-### Tests Failing After Code Changes
-
-1. Rebuild the plugin: `npm run build`
-2. Restart wp-env: `npm run wp-env restart`
-3. Clear browser cache if needed
-
-### Authentication Issues
-
-The tests use stored authentication state. If you see login failures:
-1. Check `artifacts/storage-states/admin.json` exists
-2. Verify the global setup script runs: `src/blocks/test/e2e/global-setup.ts`
-
-### Timeout Errors
-
-If tests timeout, you can increase the timeout in `playwright.config.js`:
-```javascript
-timeout: 100_000, // 100 seconds (default)
-```
-
-## Getting Help
-
-- Check test output for specific error messages
-- Review test artifacts in `artifacts/` directory
-- Check Docker logs: `docker logs `
-- Verify WordPress is running: http://localhost:8889
-
-## Cleaning Up
-
-After testing, to stop and remove containers:
-```bash
-npm run wp-env stop
-```
-
-To completely remove all wp-env data:
-```bash
-npm run wp-env destroy
-```
diff --git a/test/emptytheme/functions.php b/test/emptytheme/functions.php
deleted file mode 100644
index e4a2231db..000000000
--- a/test/emptytheme/functions.php
+++ /dev/null
@@ -1,13 +0,0 @@
-
-
->
-
-
-
-
-
->
-
-
-
-
-
-
-
diff --git a/test/emptytheme/style.css b/test/emptytheme/style.css
deleted file mode 100644
index 0c7ee5cab..000000000
--- a/test/emptytheme/style.css
+++ /dev/null
@@ -1,7 +0,0 @@
-/*
-Theme Name: Empty Theme
-Theme URI: https://example.com
-Description: A minimal empty theme for testing
-Version: 1.0
-Author: Test
-*/