-
Notifications
You must be signed in to change notification settings - Fork 5
Patterns Color Controls Pattern
Last Updated: 2025-11-08 WordPress Version: 6.4+
This document defines the standardized approach for implementing color controls across all DesignSetGo blocks, using WordPress's modern ColorGradientSettingsDropdown component.
- ❌ Colors appear in Settings tab instead of Styles tab
- ❌ Inconsistent with WordPress core blocks
- ❌ Users expect colors in the Styles tab
- ❌ Requires custom clear button implementation
- ✅ Colors appear in Styles tab where users expect them
- ✅ Matches WordPress core block patterns
- ✅ Native clear/reset functionality built-in
- ✅ Better theme integration
- ✅ Automatic theme color palette support
Color attributes should be stored as simple strings with empty string defaults:
{
"attributes": {
"hoverBackgroundColor": {
"type": "string",
"default": ""
},
"hoverTextColor": {
"type": "string",
"default": ""
}
}
}import {
useBlockProps,
InspectorControls,
// eslint-disable-next-line @wordpress/no-unsafe-wp-apis
__experimentalColorGradientSettingsDropdown as ColorGradientSettingsDropdown,
// eslint-disable-next-line @wordpress/no-unsafe-wp-apis
__experimentalUseMultipleOriginColorsAndGradients as useMultipleOriginColorsAndGradients,
} from '@wordpress/block-editor';Add clientId prop to edit component:
export default function MyBlockEdit({
attributes,
setAttributes,
clientId, // ← Required for ColorGradientSettingsDropdown
}) {
// ...
}// Get theme color palette and gradient settings
const colorGradientSettings = useMultipleOriginColorsAndGradients();Split color controls from other settings using group="color":
return (
<>
{/* Color controls - appear in STYLES tab */}
<InspectorControls group="color">
<ColorGradientSettingsDropdown
panelId={clientId}
title={__('Hover Colors', 'designsetgo')}
settings={[
{
label: __('Hover Background', 'designsetgo'),
colorValue: hoverBackgroundColor,
onColorChange: (color) =>
setAttributes({
hoverBackgroundColor: color || '',
}),
clearable: true, // ← Enables native clear button
},
{
label: __('Hover Text', 'designsetgo'),
colorValue: hoverTextColor,
onColorChange: (color) =>
setAttributes({ hoverTextColor: color || '' }),
clearable: true,
},
]}
{...colorGradientSettings}
/>
</InspectorControls>
{/* Other controls - appear in SETTINGS tab */}
<InspectorControls>
{/* Non-color settings here */}
</InspectorControls>
</>
);WordPress stores colors in two ways:
-
Custom colors:
attributes.style.color.background(hex/rgb values) -
Preset colors:
attributes.backgroundColor(theme color slugs)
Extract both formats:
const {
style,
backgroundColor,
textColor,
hoverBackgroundColor,
hoverTextColor,
} = attributes;
// Extract WordPress color values
// Custom colors come from style.color.background (hex/rgb)
// Preset colors come from backgroundColor/textColor (slugs that need conversion)
const bgColor =
style?.color?.background ||
(backgroundColor && `var(--wp--preset--color--${backgroundColor})`);
const txtColor =
style?.color?.text ||
(textColor && `var(--wp--preset--color--${textColor})`);Apply colors directly to elements using inline styles:
const buttonStyles = {
// Apply extracted colors
...(bgColor && { backgroundColor: bgColor }),
...(txtColor && { color: txtColor }),
// Apply custom hover colors as CSS variables
...(hoverBackgroundColor && {
'--dsgo-button-hover-bg': hoverBackgroundColor,
}),
...(hoverTextColor && {
'--dsgo-button-hover-color': hoverTextColor,
}),
};
return (
<div className="my-block__wrapper" style={buttonStyles}>
{/* Content */}
</div>
);Use :not() selector to apply defaults only when user hasn't set colors:
.my-block__wrapper {
// Only apply default if no inline background style
&:not([style*="background"]) {
background-color: var(--wp--preset--color--primary, #2563eb);
}
// Only apply default if no inline color style
&:not([style*="color"]) {
color: var(--wp--preset--color--base, #fff);
}
}Target elements with CSS variables and apply on hover:
.my-block__wrapper[style*="--dsgo-button-hover-bg"]:hover {
background-color: var(--dsgo-button-hover-bg) !important;
opacity: 1; // Override default opacity change
}
.my-block__wrapper[style*="--dsgo-button-hover-color"]:hover {
color: var(--dsgo-button-hover-color) !important;
}CRITICAL: Color extraction logic MUST match between edit.js and save.js:
export default function MyBlockSave({ attributes }) {
const {
style,
backgroundColor,
textColor,
hoverBackgroundColor,
hoverTextColor,
} = attributes;
// Extract WordPress color values (must match edit.js)
const bgColor =
style?.color?.background ||
(backgroundColor && `var(--wp--preset--color--${backgroundColor})`);
const txtColor =
style?.color?.text ||
(textColor && `var(--wp--preset--color--${textColor})`);
// Calculate styles (must match edit.js)
const buttonStyles = {
...(bgColor && { backgroundColor: bgColor }),
...(txtColor && { color: txtColor }),
...(hoverBackgroundColor && {
'--dsgo-button-hover-bg': hoverBackgroundColor,
}),
...(hoverTextColor && {
'--dsgo-button-hover-color': hoverTextColor,
}),
};
return (
<div className="my-block__wrapper" style={buttonStyles}>
{/* Content */}
</div>
);
}{
"attributes": {
"hoverBackgroundColor": {
"type": "string",
"default": ""
},
"hoverTextColor": {
"type": "string",
"default": ""
}
},
"supports": {
"color": {
"background": true,
"text": true
}
}
}import { __ } from '@wordpress/i18n';
import {
useBlockProps,
InspectorControls,
__experimentalColorGradientSettingsDropdown as ColorGradientSettingsDropdown,
__experimentalUseMultipleOriginColorsAndGradients as useMultipleOriginColorsAndGradients,
} from '@wordpress/block-editor';
export default function IconButtonEdit({
attributes,
setAttributes,
clientId,
}) {
const {
hoverBackgroundColor,
hoverTextColor,
style,
backgroundColor,
textColor,
} = attributes;
// Get theme color palette and gradient settings
const colorGradientSettings = useMultipleOriginColorsAndGradients();
// Extract WordPress color values
const bgColor =
style?.color?.background ||
(backgroundColor && `var(--wp--preset--color--${backgroundColor})`);
const txtColor =
style?.color?.text ||
(textColor && `var(--wp--preset--color--${textColor})`);
const buttonStyles = {
...(bgColor && { backgroundColor: bgColor }),
...(txtColor && { color: txtColor }),
...(hoverBackgroundColor && {
'--dsgo-button-hover-bg': hoverBackgroundColor,
}),
...(hoverTextColor && {
'--dsgo-button-hover-color': hoverTextColor,
}),
};
return (
<>
<InspectorControls group="color">
<ColorGradientSettingsDropdown
panelId={clientId}
title={__('Hover Colors', 'designsetgo')}
settings={[
{
label: __('Hover Background', 'designsetgo'),
colorValue: hoverBackgroundColor,
onColorChange: (color) =>
setAttributes({
hoverBackgroundColor: color || '',
}),
clearable: true,
},
{
label: __('Hover Text', 'designsetgo'),
colorValue: hoverTextColor,
onColorChange: (color) =>
setAttributes({ hoverTextColor: color || '' }),
clearable: true,
},
]}
{...colorGradientSettings}
/>
</InspectorControls>
<InspectorControls>
{/* Other settings panels */}
</InspectorControls>
<div {...useBlockProps()}>
<div className="dsgo-icon-button__wrapper" style={buttonStyles}>
{/* Button content */}
</div>
</div>
</>
);
}.dsgo-icon-button__wrapper {
// Default colors only when no inline styles
&:not([style*="background"]) {
background-color: var(--wp--preset--color--primary, #2563eb);
}
&:not([style*="color"]) {
color: var(--wp--preset--color--base, #fff);
}
// Default hover effect
&:hover {
opacity: 0.9;
}
}
// Hover colors via CSS variables (override default hover)
.dsgo-icon-button__wrapper[style*="--dsgo-button-hover-bg"]:hover {
background-color: var(--dsgo-button-hover-bg) !important;
opacity: 1; // Override default opacity
}
.dsgo-icon-button__wrapper[style*="--dsgo-button-hover-color"]:hover {
color: var(--dsgo-button-hover-color) !important;
}When converting a block from PanelColorSettings to ColorGradientSettingsDropdown:
- Add
clientIdprop to edit component signature - Import
ColorGradientSettingsDropdownanduseMultipleOriginColorsAndGradients - Add
colorGradientSettingshook call - Split
InspectorControls- addgroup="color"for color controls - Replace
PanelColorSettingswithColorGradientSettingsDropdown - Add
clearable: trueto each color setting - Add color extraction logic for both custom and preset colors
- Update inline styles to use extracted colors
- Update SCSS to use
:not()selectors for defaults - Ensure save.js matches edit.js color extraction exactly
- Test with both custom colors and theme preset colors
- Test clear functionality in color picker
- Build and verify in both editor and frontend
<ColorGradientSettingsDropdown
panelId={clientId}
title={__('Icon Color', 'designsetgo')}
settings={[
{
label: __('Icon Color', 'designsetgo'),
colorValue: iconColor,
onColorChange: (color) =>
setAttributes({ iconColor: color || '' }),
clearable: true,
},
]}
{...colorGradientSettings}
/><InspectorControls group="color">
<ColorGradientSettingsDropdown
panelId={clientId}
title={__('Normal Colors', 'designsetgo')}
settings={[
{
label: __('Background', 'designsetgo'),
colorValue: backgroundColor,
onColorChange: (color) =>
setAttributes({ backgroundColor: color || '' }),
clearable: true,
},
{
label: __('Text', 'designsetgo'),
colorValue: textColor,
onColorChange: (color) =>
setAttributes({ textColor: color || '' }),
clearable: true,
},
]}
{...colorGradientSettings}
/>
<ColorGradientSettingsDropdown
panelId={clientId}
title={__('Hover Colors', 'designsetgo')}
settings={[
{
label: __('Hover Background', 'designsetgo'),
colorValue: hoverBackgroundColor,
onColorChange: (color) =>
setAttributes({ hoverBackgroundColor: color || '' }),
clearable: true,
},
{
label: __('Hover Text', 'designsetgo'),
colorValue: hoverTextColor,
onColorChange: (color) =>
setAttributes({ hoverTextColor: color || '' }),
clearable: true,
},
]}
{...colorGradientSettings}
/>
</InspectorControls>Solution: Ensure InspectorControls has group="color" prop
Solution: Add clearable: true to each setting in the settings array
Solution: Check color extraction logic converts slugs to CSS variables:
(backgroundColor && `var(--wp--preset--color--${backgroundColor})`)Solution: Ensure save.js has identical color extraction logic as edit.js
Solution: Use :not([style*="background"]) selector to only apply when no inline style
Blocks that have been migrated to this pattern:
- ✅ Icon Button (src/blocks/icon-button)
- WordPress Block Editor Handbook - InspectorControls
- WordPress Block Color Support
- Theme.json Color Palette
Auto-generated from
docs/patterns/COLOR-CONTROLS-PATTERN.md. To update, edit the source file and changes will sync on next push to main.
- Accordion
- Blobs
- Breadcrumbs
- Card
- Comparison Table
- Countdown Timer
- Counter Group
- Divider
- Flip Card
- Form Builder
- Grid
- Icon
- Icon Button
- Icon List
- Image Accordion
- Map
- Modal
- Modal Api Reference
- Modal Auto Triggers
- Modal Fse Compatibility
- Modal Gallery Navigation
- Modal Next Phase
- Modal Performance Fixes
- Modal Security Audit
- Modal Security Fixes Summary
- Modal Trigger
- Pill
- Progress Bar
- Reveal
- Row
- Scroll Accordion
- Scroll Gallery
- Section
- Slider
- Table Of Contents
- Tabs
- Timeline
- Animation
- Background Video
- Block Animations
- Clickable Group
- Custom Css
- Expanding Background
- Grid Mobile Order
- Grid Span
- Max Width
- Responsive Visibility
- Reveal Control
- Scroll Parallax
- Sticky Header
- Text Alignment Inheritance
- Text Reveal
- Ai Assisted Development
- Best Practices Summary
- Block Controls Organization
- Block Development Best Practices Comprehensive
- Block Exclusion Guide
- Control Reorganization
- Design System
- Wordpress Block Editor Best Practices
- Color Controls Pattern
- Custom Css Filters
- Performance Css Strategy
- Width Css Strategy Implementation
- Width Layout Patterns
- Antigravity Audit
- Card Block Audit
- Claude Audit
- Comprehensive Audit
- Cursor Audit
- Scroll Accordion Stacking Notes
- Security Review 1.2.1
- 2026 02 11 Icon Search Aliases Design
- 2026 02 14 Overlay Header Design
- 2026 02 15 Deactivation Block Migrator Design