-
Notifications
You must be signed in to change notification settings - Fork 10
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.
Shared hook available: useBlockColors({ attributes, setAttributes, entries }) (src/hooks/useBlockColors.js) wraps steps 4–6 below (color extraction, settings array construction, encode/decode of preset vs. custom values) and is the preferred entry point for new blocks. It is currently used by Section, Card, Text Path, Hotspot, Slider, Section Divider, and Star Rating. The manual pattern documented here (used by Icon Button and other existing blocks) remains valid — prefer the hook for anything new.
- ❌ 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 using the manual ColorGradientSettingsDropdown pattern documented above include Icon Button, Row, Grid, Tabs, Accordion, Image Accordion, Counter, Modal, Timeline, Breadcrumbs, and others (grep -rl "ColorGradientSettingsDropdown" src/blocks --include="edit.js"). Blocks using the newer useBlockColors hook instead: Section, Card, Text Path, Hotspot, Slider, Section Divider, and Star Rating — see the hook note above.
- 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
- Advanced Heading
- Blobs
- Breadcrumbs
- Card
- Chart
- Comparison Table
- Countdown Timer
- Counter Group
- Divider
- Dynamic Image
- Fifty Fifty
- Flip Card
- Form Builder
- Grid
- Hotspot
- Icon
- Icon Button
- Icon List
- Image Accordion
- Map
- Modal
- Modal Api Reference
- Modal Auto Triggers
- Modal Fse Compatibility
- Modal Gallery Navigation
- Modal Trigger
- Pill
- Product Categories Grid
- Product Showcase Hero
- Progress Bar
- Query
- Query Filter
- Query Group Header
- Query No Results
- Query Pagination
- Query Results
- Row
- Scroll Accordion
- Scroll Marquee
- Scroll Slides
- Section
- Section Divider
- Slider
- Star Rating
- Sticky Sections
- Table Of Contents
- Tabs
- Text Path
- Timeline
- Background Video
- Block Animations
- Clickable Group
- Conditional Visibility
- Custom Css
- Draft Mode
- Dynamic Tags
- Expanding Background
- Grid Mobile Order
- Grid Span
- Hover Effects
- Max Width
- Responsive Visibility
- Reveal Control
- Scroll Parallax
- Sticky Header
- Style Binding
- Svg Patterns
- Text Alignment Inheritance
- Text Reveal
- Abilities Api
- Abilities Api Guide
- Block Bindings
- Draft Mode Api
- Interactive Blocks
- Markdown Content Negotiation
- Rest Api Reference
- Wp Cli Reference
- Ai Assisted Development
- Best Practices Summary
- Block Controls Organization
- Block Development Best Practices Comprehensive
- Block Exclusion Guide
- Deploying To Wordpress Org
- Design System
- 2026 04 16 Blocks Editor Ux Design
- 2026 04 17 Theme 3 Inspector Ia
- 2026 04 21 Query Block Onboarding
- 2026 04 21 Query Capable Layout Blocks Design
- 2026 04 27 Block Roadmap Ideas
- 2026 07 01 Shape Divider Theme Inheritance
- 2026 07 02 Icon Block Dynamic Render Pilot
- 2026 07 06 Section Divider Block Design
- 2026 07 06 Section Style Variations Fse Design
- 2026 07 06 Section Styles Editor Preview
- 2026 07 07 Row Grid Overlay Hover Parity Design
- 2026 07 21 Conditional And Dup Label Sourcing
- 2026 07 23 Theme Block Type Animation Defaults
- 2026 08 16 Chart Block
- 2026 08 16 Greenshift Gap Roadmap
- 2026 08 17 Woocommerce Surface
- 2026 08 24 Loop Carousel
- 2026 08 24 Star Rating Block
- 2026 09 17 Abilities Drift Prevention
- 2026 09 17 Abilities Drift Prevention Implementation
- 2026 09 29 Existing User Regression Fixes
- 2026 09 29 Release Preflight Fixes
- Readme
- 2026 07 02 Block Authorability Audit
- 2026 09 29 Regression Fixes
- 2026 09 29 Release 2.9.0
- 2026 09 29 Release Fixes