This post will go into depth in how to add color settings to your custom WordPress Gutenberg block. We will learn how to add the same color settings component, which features choosing from palette colors and custom color, as many of WordPress’ default blocks use.
This is what we will add to our custom block:
By using Gutenberg’s components we can very easily implement this palette/color-section to our own custom block. You can define the color settings to affect any color you want; background, text color, border-color or whatever else. You can also add as many color settings as you want to inside this panel.
Before we dive into the code, there’s some things you need to be aware of. Don’t skip ahead to the code as the next section will explain a lot why the code needs to do it like it does.
What you need to know first
The components for implementing palette/color settings are PanelColorSettings
and withColors
from the wp.blockEditor
package. The component withColors
is a so-called higher-order-component and needs to be implemented a little bit differently than simply outputting a normal component. I’ll go into a little more detail later on. But first we need to be aware of the basics in how Gutenberg handles block color settings.
How Gutenberg blocks handle color settings
There are certain rules for how Gutenberg handles color settings in blocks. If you’ve ever styled a Gutenberg-supported theme before you are probably already familiar with these rules. We’ll quickly go through them nonetheless because we have to handle this in our block code.
- When a palette color is chosen, the node block element will get a class of a certain pattern. The class starts with “
has-
“, and then the palette’s slug comes after. The ending depends on what element the color should affect. For text color it ends with “-color
“. For background-color it ends with “-background-color
“. As an example, a block with a palette color “red” chosen as background-color will get the class “has-red-background-color
“. - When a custom color is chosen (from the colorpicker), the node block element will get inline style with the hex code. For example a custom color #DD0000 chosen for background color will get the attribute “
style="background-color: #DD0000;
“.
When we implement color settings for our block we will need to implement the correct class and inline style. We’ll do that at the end of this tutorial.
How to work with withColors
As mentioned earlier withColors
is a higher-order-component. What that basically means it’s a component that takes a component and returns a new component. In the returned component we get useful props from the higher-order-component. To put it simply, we will use withColors
to return our own component for our custom block. Our block component gets the necessary props for working with colors from withColors
.
The component withColors
handles state and a lot of functionality for working with colors. And we get a lot of automation in this process. This is very handy for figuring out if the chosen color is a palette color (we need to add a class) or a custom color (we need to add inline style). withColors
simplify a lot of the process of storing the chosen color, whatever it is, into our block’s attributes. Speaking of attributes..
Attributes and withColors
Obviously your block needs attributes to store the chosen color. In order to benefit from withColor’s automation for storing the correct color you actually need to define two attributes for each color setting. One for storing the palette color’s slug, and another for storing the hex code. There are some rules though.
Say you want to add a color setting for the block’s text color – so you decide to define an attribute aptly named “textColor
“. You will then need to define another attribute in the pattern “customYourOriginalAttribute
“. In this example the second attribute will need to be named “customTextColor
“. Mind the camelCase (capitalization) here. Following this pattern withColors
will automatically:
- If a palette color has been chosen, the attribute “
textColor
” will contain the palette’s slug. - If a custom color has been chosen, “
customTextColor
” will be populated with the hex code.
These two work in tandem. If a custom color is chosen, textColor
will automatically be undefined
, and vice versa.
And finally, one last thing to remember: you will not need to use setAttributes()
for updating your color attributes! The provided props from withColors
include a function that automatically updates your attributes for you. All you need to do is to pass this function to the onChange
event to the PanelColorSettings
component, and your attributes will automatically be updated.
Ok, it’s time to see all of this in practice!
Implementing color settings in a custom block
To start things off I have a pretty useless custom block that does nothing else than display a hardcoded text. This just makes it easy to separate out what we need to code for adding color settings. I have a tutorial series on how to create your own custom blocks if you’re interested in learning more.
This is my basic custom block before doing anything with color settings:
const { registerBlockType } = wp.blocks; const { __ } = wp.i18n; const BlockWithColorSettings = (props) => { return( <div> PanelColorSettings Demo </div> ); } registerBlockType('awp/colorsettings', { title: __('Color Settings Demo'), icon: 'carrot', category: 'common', edit: BlockWithColorSettings, save: (props) => { return( <div> PanelColorSettings Demo </div> ); } });
It’s pretty basic. Note that the edit
function is simply referring to a separate component, BlockWithColorSettings
, instead of writing it inline. This makes it easier to implement withColors
later.
Okay, time to implement color settings to our block! As an example I want to set up text color.
Adding attributes
As mentioned earlier we need to define two attributes for each color setting. And we need to take extra care in their naming. I want to add a text color attribute, so I’m naming the attribute textColor
. Which means I will also add an attribute customTextColor
. Both should be of type string.
...
registerBlockType('awp/colorsettings', {
title: __('Color Settings Demo'),
icon: 'carrot',
category: 'common',
attributes: {
textColor: {
type: 'string'
},
customTextColor: {
type: 'string'
},
},
edit: BlockWithColorSettings,
save: (props) => {
...
Our attributes are ready for storing the block’s text color setting. Now it’s time to implement withColors
and PanelColorSettings
.
Implementing withColors
As mentioned earlier withColors
is a higher-order-component that should take a component to return. We obviously want it to return our edit component, BlockWithColorSettings
. But as parameter to withColors
we provide an object.
In the object passed to withColors
we need to tell withColors
what attribute we want to use for storing the color and what kind of element it will color (in our case text color, which means CSS rule “color”). Informing about the CSS rule makes sure that the class names returned are correct. Because this is text color we want class names like “has-<palettecolor>-color”.
First some destructuring at the top. withColors
resides in the wp.blockEditor
package.
const { withColors } = wp.blockEditor;
We’ll change the edit
function into:
...
attributes: {
...
},
edit: withColors({textColor: 'color'})(BlockWithColorSettings),
save: (props) => {
...
With this code our component BlockWithColorSettings
will receive some additional props:
props.textColor
: Is an object that consists of all information about the chosen color. If a palette color was chosen it will store properties for hex code, palette slug, class name, and more. If a custom color was chosen the object will contain the hex code. The hex code is always found in the propertycolor
. And the class name, (only if a palette color was chosen) will be set in the propertyclass
.props.setTextColor
: A function that will update our attributes for us. We provide this for the color settings’sonChange
event as we’ll see later. The function will update bothtextColor
andcustomTextColor
attributes. Because we followed the naming rules it will automatically updatecustomTextColor
even though we never provided this attribute name.
Note that the “set”-function provided as prop will follow the rule: “setYourAttributeName
“. Because we provided textColor
, the function is named setTextColor
. If we instead named our attribute awesomeColor
and provided this in the withColors
object, the set-function would be named setAwesomeColor()
.
Implementing PanelColorSettings
The next step is implementing the actual Inspector section. To do this we add PanelColorSettings
inside a InspectorControls
component. Because React requires everything to be inside one root node, we also use Fragment
(from wp.elements
) to wrap everything inside.
First some destructuring at the top of the file:
const { Fragment } = wp.element; const { InspectorControls, PanelColorSettings, withColors } = wp.blockEditor;
And we update our BlockWithColorSettings
component into something like this:
const BlockWithColorSettings = (props) => { const { textColor, setTextColor } = props; // Props received from withColors return( <Fragment> <InspectorControls> <PanelColorSettings title={__('Color settings')} colorSettings={[ { value: textColor.color, onChange: setTextColor, label: __('Text color') }, ]} /> </InspectorControls> <div> PanelColorSettings Demo </div> </Fragment> ); }
As you can see in line #2
we destructure the props received from withColors
, textColor
and setTextColor
. Keep in mind that props.textColor
is the object received from withColors
, and props.attributes.textColor
is the attribute. We have no need to refer to the attribute though.
As props to the component PanelColorSettings
we can provide a title for the section (title in the collapsible box in Inspector). The important thing here is the prop colorSettings
where we need to provide an array of color setting objects. For each color setting (we currently only have one) we need to provide some properties. The property value
expects the currently chosen hex code (even though a palette color was chosen). This is provided for us in the textColor
prop, inside the property color
. For the onChange
property we simply provide the “set” function provided by withColors
, setTextColor
. And finally we should give a label
so that the user knows what element this color will affect. It will appear right above the area of choosing a color.
This is how our component appears in Gutenberg editor right now:
It is now successfully updating our attributes when choosing colors. You can see that it remembers your color choice when saving the post.
However, nothing visually happens when you change colors. The color choice is stored in the attributes, but no color change happens in the editor, nor when previewing the post. This is because we need to add code for the block’s classes and styles. We need to do this for both edit
function (which is for the editor) and save
function (frontend). That’s the next step.
Handling class and inline styles in edit
We need to build the block’s node class and style attributes according to the chosen color setting. Luckily with withColors
we get some automation in this. Remember that props.textColor
is an object that contains all information we need, including the class name.
We could do something like this:
... const BlockWithColorSettings = (props) => { const { textColor, setTextColor } = props; // Props received from withColors let divClass; let divStyles = {}; if (textColor != undefined) { if (textColor.class != undefined) { divClass = textColor.class; } else { divStyles.color = textColor.color; } } return( <Fragment> <InspectorControls> ... </InspectorControls> <div className={divClass} style={divStyles}> PanelColorSettings Demo </div> ...
At line #20
we apply the critical class and inline style to the root node of our block. Before that we build the class and inline style attribute by checking the props.textColor
object.
After this change your custom block should now be fully functional in the editor. Whenever you switch color the block will change the text color. Awesome! The final step is doing this for the save
function as well, so that we get these classes and styles in frontend as well.
Handling class and inline styles in save
The concept of building the class and inline styles and applying them to the root node is the same in save
as in edit
. But there is a crucial difference. In save
we don’t have the props provided by withColors
. And we cannot add higher-order-components to the save
function. So in the save
function all information we have are the attributes.
It’s a good rule of thumb to avoid hardcoding “has-” classnames. What if WordPress decides to change this rule in the future? Luckily we have a function that can help us generate the proper classnames for us: getColorClassName()
.
Before we forget, let’s destructure it. It’s also in the wp.blockEditor
package.
const { InspectorControls, PanelColorSettings, withColors, getColorClassName } = wp.blockEditor;
Using the getColorClassName()
function requries two parameters. First a string for the CSS rule. Because our color setting is for text color, we provide ‘color
‘. This tells the function that it should return a classname of “has-something-color” and not “has-something-background-color” for instance. As second parameter we need to provide the value of the attribute.
The style attribute is simply built by setting “color” to the value of the attribute customTextColor
, if it’s defined.
save: (props) => { const { textColor, customTextColor } = props.attributes; let divClass; let divStyles = {}; if (textColor != undefined) { divClass = getColorClassName('color', textColor); } if (customTextColor != undefined) { divStyles.color = customTextColor; } return( <div className={divClass} style={divStyles}> PanelColorSettings Demo </div> ); }
And of course, don’t forget to apply the class and style onto the root node of your block; as in line #12
.
After this change, your block should now properly apply the chosen text color in frontend as well.
And that’s it! You have now successfully implemented color settings to your block. If you’re interested in adding multiple color settings (not just text color), read on.
A note on multiple color settings
By now you should be able to implement multiple color settings. You may want to add settings for background color, text color, border color, or whatever else, for your block. In this section I’ll do a quick overview of what we need to do in order to implement multiple color settings.
Let’s assume that I want to add background color to my block as well.
First I need to define a new attribute creatively named backgroundColor
. I define another attribute customBackgroundColor
as well.
In the edit
function I change the object provided to withColors
as such:
withColors({textColor: 'color', backgroundColor: 'background-color'})
This tells withColors
that my textColor
attribute is for the CSS rule “color
” (for text color), and the attribute backgroundColor
is for the CSS rule “background-color
“. withColors
will automatically recognize and update the customTextColor
and customBackgroundColor
attributes as well.
In the PanelColorSettings
component, I provide another object to the array to the prop colorSettings
. Like so:
<PanelColorSettings title={__('Color settings')} colorSettings={[ { value: textColor.color, onChange: setTextColor, label: __('Text color') }, { value: backgroundColor.color, onChange: setBackgroundColor, label: __('Background color') }, ]} />
With this you should get two separate color settings inside the Inspector box for color settings.
The last step is building the appropiate class names and styles in both edit
and save
. This is a pretty simple step as it’s just building a string or style object correctly. Remember that your class name must support multiple color classes (e.g. if both text color and background color was chosen). Simply add a space between each class name.
Conclusion and full code
You should now have learned how to implement color settings in your custom block. I hope this was of some use to you! I had to add this feature to my project, and I really couldn’t find any information or good documentation. So this is the result of consolidating everything I learned about this topic, after a lot of trial and error.
Here is the final code, alltogether, for the example custom block with text color setting:
const { registerBlockType } = wp.blocks; const { __ } = wp.i18n; const { Fragment } = wp.element; const { InspectorControls, PanelColorSettings, withColors, getColorClassName } = wp.blockEditor; const BlockWithColorSettings = (props) => { const { textColor, setTextColor } = props; // Props received from withColors let divClass; let divStyles = {}; if (textColor != undefined) { if (textColor.class != undefined) { divClass = textColor.class; } else { divStyles.color = textColor.color; } } return( <Fragment> <InspectorControls> <PanelColorSettings title={__('Color settings')} colorSettings={[ { value: textColor.color, onChange: setTextColor, label: __('Text color') }, ]} /> </InspectorControls> <div className={divClass} style={divStyles}> PanelColorSettings Demo </div> </Fragment> ); } registerBlockType('awp/colorsettings', { title: __('Color Settings Demo'), icon: 'carrot', category: 'common', attributes: { textColor: { type: 'string' }, customTextColor: { type: 'string' }, }, edit: withColors({textColor: 'color'})(BlockWithColorSettings), save: (props) => { const { textColor, customTextColor } = props.attributes; let divClass; let divStyles = {}; if (textColor != undefined) { divClass = getColorClassName('color', textColor); } if (customTextColor != undefined) { divStyles.color = customTextColor; } return( <div className={divClass} style={divStyles}> PanelColorSettings Demo </div> ); } });