diff --git a/src/editor/components/BBContrastChecker.js b/src/editor/components/BBContrastChecker.js
new file mode 100644
index 00000000..d85a51df
--- /dev/null
+++ b/src/editor/components/BBContrastChecker.js
@@ -0,0 +1,102 @@
+import { Notice } from '@wordpress/components';
+import { __ } from '@wordpress/i18n';
+
+const BBContrastChecker = ( { background, foreground } ) => {
+ /**
+ * If either colour is not provided, don't render anything
+ */
+ if ( ! background || ! foreground ) {
+ return;
+ }
+
+ /**
+ * Get the luminance of a colour
+ *
+ * @param {string} colour The colour to convert to luminance value
+ *
+ * @return {number} The luminance value
+ */
+ const getLuminance = ( colour ) => {
+ const rgb = colour
+ .match( /\w\w/g )
+ .map( ( c ) => parseInt( c, 16 ) / 255 );
+ const [ r, g, b ] = rgb.map( ( c ) => {
+ return c <= 0.03928
+ ? c / 12.92
+ : Math.pow( ( c + 0.055 ) / 1.055, 2.4 );
+ } );
+ return 0.2126 * r + 0.7152 * g + 0.0722 * b;
+ };
+
+ /**
+ * Compare two colours and return their contrast ratio.
+ *
+ * @param {string} colour1 This is an example function/method parameter description.
+ * @param {string} colour2 This is a second example.
+ *
+ * @return {number} The contrast ratio between the colours
+ */
+ const getContrastRatio = ( colour1, colour2 ) => {
+ const luminance1 = getLuminance( colour1 );
+ const luminance2 = getLuminance( colour2 );
+ return (
+ ( Math.max( luminance1, luminance2 ) + 0.05 ) /
+ ( Math.min( luminance1, luminance2 ) + 0.05 )
+ );
+ };
+
+ /**
+ * Determine the colour contrast ratio
+ */
+ const contrastRatio = getContrastRatio( background, foreground );
+
+ /**
+ * Determine the message to output
+ */
+ const displayMessage =
+ contrastRatio >= 4.5
+ ? __(
+ 'The selected colours do not meet the colour contrast ratio for AAA (7:1) accessibility standards',
+ 'themer'
+ )
+ : __(
+ 'The selected colours do not meet the colour contrast ratio for AA (4.5:1) accessibility standards',
+ 'themer'
+ );
+
+ /**
+ * Set the notice display level based on the contrast ratio
+ */
+ const displayMessageImportance = contrastRatio >= 4.5 ? 'info' : 'warning';
+
+ return (
+ <>
+
+
+ { __( 'WCAG Check:', 'themer' ) }
+
+
+ { contrastRatio.toFixed( 1 ) } : 1
+
+
+
= 4.5 ? 'pass' : ' fail' }>
+ ✓ { __( 'AA', 'themer' ) }
+
+
= 7 ? 'pass' : ' fail' }>
+ ✓ { __( 'AAA ', 'themer' ) }
+
+
+
+ { contrastRatio < 7 && (
+
+ { displayMessage }
+
+ ) }
+ >
+ );
+};
+
+export default BBContrastChecker;
diff --git a/src/editor/components/StylesColor.js b/src/editor/components/StylesColor.js
index f47cefdd..83c0ac38 100644
--- a/src/editor/components/StylesColor.js
+++ b/src/editor/components/StylesColor.js
@@ -1,4 +1,4 @@
-import { set } from 'lodash';
+import { set, debounce } from 'lodash';
import { __ } from '@wordpress/i18n';
import { useContext } from '@wordpress/element';
import { ColorPalette } from '@wordpress/components';
@@ -8,6 +8,7 @@ import getThemeOption from '../../utils/get-theme-option';
import EditorContext from '../context/EditorContext';
import StylesContext from '../context/StylesContext';
import Gradient from './StylesGradient';
+import BBContrastChecker from './BBContrastChecker';
/**
* Reusable color control style component
@@ -24,7 +25,13 @@ const Color = ( { selector } ) => {
themeConfig
);
- const onChange = ( newValue, key ) => {
+ /**
+ * Function to handle the colour palette changes
+ *
+ * @param {string} newValue The value of the setting
+ * @param {string} key The key of the setting
+ */
+ const onChange = debounce( ( newValue, key ) => {
let config = structuredClone( userConfig );
config = set(
config,
@@ -32,14 +39,14 @@ const Color = ( { selector } ) => {
hexToVar( newValue, themePalette ) ?? ''
);
setUserConfig( config );
- };
+ }, 50 );
const colorPalettes = [ 'background', 'text' ].map( ( key ) => (
{ key }
onChange( value, key ) }
value={ varToHex( colorStyles[ key ], themePalette ) }
/>
@@ -51,6 +58,10 @@ const Color = ( { selector } ) => {
{ __( 'Color', 'themer' ) }
+
{ colorPalettes }
diff --git a/src/editor/styles/components/bb-contrast-checker.scss b/src/editor/styles/components/bb-contrast-checker.scss
new file mode 100644
index 00000000..e90c7e0a
--- /dev/null
+++ b/src/editor/styles/components/bb-contrast-checker.scss
@@ -0,0 +1,82 @@
+.themer--styles__item:has(.contrast-checker) {
+ container-type: inline-size;
+ container-name: contrast-checker-outer;
+}
+
+.contrast-checker {
+ align-items: center;
+ border: 1px solid green;
+ border-radius: 0.5rem;
+ display: flex;
+ gap: 1rem;
+ justify-content: flex-start;
+ padding: 0.5rem;
+
+ transition: border-color 0.25s ease-in-out;
+}
+
+.contrast-checker:has(p.fail) {
+ border-color: blue;
+}
+
+.contrast-checker:has(p.fail + p.fail) {
+ border-color: red;
+}
+
+.contrast-checker p {
+ margin-block: 0;
+}
+
+.contrast-checker-title {
+ font-size: 0.75rem;
+ font-weight: bold;
+ text-transform: uppercase;
+}
+
+.contrast-checker-ratio {
+ font-size: 1.25rem;
+ font-weight: bold;
+ margin-inline-start: auto;
+}
+
+.contrast-checker-badges {
+ align-items: center;
+ display: flex;
+ gap: 0.5rem;
+}
+
+.contrast-checker-badges > p {
+ background-color: green;
+ border-radius: 0.25rem;
+ color: #fff;
+ display: block;
+ padding: 0.25rem 0.5rem;
+
+ transition: background-color 0.25s ease-in-out,
+ color 0.25s ease-in-out;
+}
+
+.contrast-checker-badges > p.fail {
+ background-color: red;
+ color: #000;
+}
+
+
+@container contrast-checker-outer (max-width: 350px) {
+
+ .contrast-checker {
+ align-items: center;
+ flex-wrap: wrap;
+ justify-content: space-between;
+ }
+
+ .contrast-checker-title {
+ text-align: center;
+ width: 100%;
+ }
+
+ .contrast-checker-ratio {
+ margin-inline-start: 0;
+ }
+
+}
diff --git a/src/editor/styles/index.scss b/src/editor/styles/index.scss
index 3b72351e..2b488a1f 100644
--- a/src/editor/styles/index.scss
+++ b/src/editor/styles/index.scss
@@ -10,3 +10,4 @@
@import "./components/breadcrumbs";
@import "./components/nav-list";
@import "./components/fontPicker.scss";
+@import "./components/bb-contrast-checker";